diff options
-rw-r--r-- | vcl/generic/glyphs/gcach_layout.cxx | 395 |
1 files changed, 379 insertions, 16 deletions
diff --git a/vcl/generic/glyphs/gcach_layout.cxx b/vcl/generic/glyphs/gcach_layout.cxx index ddf6d7cfc22f..1f9f3d0b98c0 100644 --- a/vcl/generic/glyphs/gcach_layout.cxx +++ b/vcl/generic/glyphs/gcach_layout.cxx @@ -17,6 +17,7 @@ * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ +#include <config_harfbuzz.h> #include <gcach_ftyp.hxx> #include <sallayout.hxx> #include <salgdi.hxx> @@ -30,6 +31,10 @@ #include <sal/alloca.h> #include <rtl/instance.hxx> +#if ENABLE_HARFBUZZ +#include <harfbuzz/hb-icu.h> +#include <harfbuzz/hb-ot.h> +#endif #include <layout/LayoutEngine.h> #include <layout/LEFontInstance.h> #include <layout/LELanguages.h> @@ -87,6 +92,371 @@ void ServerFontLayout::AdjustLayout( ImplLayoutArgs& rArgs ) } // ======================================================================= + +static bool lcl_CharIsJoiner(sal_Unicode cChar) +{ + return ((cChar == 0x200C) || (cChar == 0x200D)); +} + +static bool needPreviousCode(sal_Unicode cChar) +{ + return lcl_CharIsJoiner(cChar) || U16_IS_LEAD(cChar); +} + +static bool needNextCode(sal_Unicode cChar) +{ + return lcl_CharIsJoiner(cChar) || U16_IS_TRAIL(cChar); +} + +#if ENABLE_HARFBUZZ +static hb_blob_t *getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* userData) +{ + char pTagName[5]; + pTagName[0] = (char)(nTableTag >> 24); + pTagName[1] = (char)(nTableTag >> 16); + pTagName[2] = (char)(nTableTag >> 8); + pTagName[3] = (char)(nTableTag); + pTagName[4] = 0; + + ServerFont* rFont = (ServerFont*) userData; + sal_uLong nLength; + const unsigned char* pBuffer = rFont->GetTable(pTagName, &nLength); + + hb_blob_t* pBlob = NULL; + if (pBuffer != NULL) + pBlob = hb_blob_create((const char*) pBuffer, nLength, HB_MEMORY_MODE_WRITABLE, (void*) pBuffer, free); + + return pBlob; +} + +static hb_bool_t getFontGlyph(hb_font_t* /*font*/, void* fontData, + hb_codepoint_t ch, hb_codepoint_t vs, + hb_codepoint_t* nGlyphIndex, + void* /*userData*/) +{ + ServerFont* rFont = (ServerFont*) fontData; + *nGlyphIndex = 0; + + if (vs) + *nGlyphIndex = rFont->GetRawGlyphIndex(ch /*, vs*/); // XXX handle variation selectors + + if (*nGlyphIndex == 0) + *nGlyphIndex = rFont->GetRawGlyphIndex(ch); + + return *nGlyphIndex != 0; +} + +static hb_position_t getGlyphAdvanceH(hb_font_t* /*font*/, void* fontData, + hb_codepoint_t nGlyphIndex, + void* /*userData*/) +{ + ServerFont* rFont = (ServerFont*) fontData; + const GlyphMetric& rGM = rFont->GetGlyphMetric(nGlyphIndex); + return rGM.GetCharWidth(); +} + +static hb_position_t getGlyphAdvanceV(hb_font_t* /*font*/, void* /*fontData*/, + hb_codepoint_t /*nGlyphIndex*/, + void* /*userData*/) +{ + // XXX: vertical metrics + return 0; +} + +static hb_bool_t getGlyphOriginH(hb_font_t* /*font*/, void* /*fontData*/, + hb_codepoint_t /*nGlyphIndex*/, + hb_position_t* /*x*/, hb_position_t* /*y*/, + void* /*userData*/) +{ + // the horizontal origin is always (0, 0) + return true; +} + +static hb_bool_t getGlyphOriginV(hb_font_t* /*font*/, void* /*fontData*/, + hb_codepoint_t /*nGlyphIndex*/, + hb_position_t* /*x*/, hb_position_t* /*y*/, + void* /*userData*/) +{ + // XXX: vertical origin + return true; +} + +static hb_position_t getGlyphKerningH(hb_font_t* /*font*/, void* fontData, + hb_codepoint_t nGlyphIndex1, hb_codepoint_t nGlyphIndex2, + void* /*userData*/) +{ + // This callback is for old style 'kern' table, GPOS kerning is handled by HarfBuzz directly + + // XXX: there is ServerFont::GetKernPairs() but it does many "smart" things + // that I'm not sure about, so I'm using FreeType directly + // P.S. if we decided not to use ServerFont::GetKernPairs() then it and all + // other implementattions should be removed, don't seem to be used + // anywhere. + + ServerFont* rFont = (ServerFont*) fontData; + FT_Face aFace = rFont->GetFtFace(); + + FT_Error error; + FT_Vector kerning; + hb_position_t ret; + + error = FT_Get_Kerning(aFace, nGlyphIndex1, nGlyphIndex2, FT_KERNING_DEFAULT, &kerning); + if (error) + ret = 0; + else + ret = kerning.x; + + return ret; +} + +static hb_position_t getGlyphKerningV(hb_font_t* /*font*/, void* /*fontData*/, + hb_codepoint_t /*nGlyphIndex1*/, hb_codepoint_t /*nGlyphIndex2*/, + void* /*userData*/) +{ + // XXX vertical kerning + return 0; +} + +static hb_bool_t getGlyphExtents(hb_font_t* /*font*/, void* fontData, + hb_codepoint_t nGlyphIndex, + hb_glyph_extents_t* extents, + void* /*userData*/) +{ + ServerFont* rFont = (ServerFont*) fontData; + FT_Face aFace = rFont->GetFtFace(); + FT_Error error; + + error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT); + if (!error) { + extents->x_bearing = aFace->glyph->metrics.horiBearingX; + extents->y_bearing = aFace->glyph->metrics.horiBearingY; + extents->width = aFace->glyph->metrics.width; + extents->height = -aFace->glyph->metrics.height; + } + + return !error; +} + +static hb_bool_t getGlyphContourPoint(hb_font_t* /*font*/, void* fontData, + hb_codepoint_t nGlyphIndex, unsigned int nPointIndex, + hb_position_t *x, hb_position_t *y, + void* /*userData*/) +{ + ServerFont* rFont = (ServerFont*) fontData; + FT_Face aFace = rFont->GetFtFace(); + FT_Error error; + bool ret = false; + + error = FT_Load_Glyph(aFace, nGlyphIndex, FT_LOAD_DEFAULT); + if (!error) { + if (aFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + if (nPointIndex < (unsigned int) aFace->glyph->outline.n_points) { + *x = aFace->glyph->outline.points[nPointIndex].x; + *y = aFace->glyph->outline.points[nPointIndex].y; + ret = true; + } + } + } + + return ret; +} + +static hb_font_funcs_t* getFontFuncs(void) +{ + static hb_font_funcs_t* funcs = hb_font_funcs_create(); + + hb_font_funcs_set_glyph_func (funcs, getFontGlyph, NULL, NULL); + hb_font_funcs_set_glyph_h_advance_func (funcs, getGlyphAdvanceH, NULL, NULL); + hb_font_funcs_set_glyph_v_advance_func (funcs, getGlyphAdvanceV, NULL, NULL); + hb_font_funcs_set_glyph_h_origin_func (funcs, getGlyphOriginH, NULL, NULL); + hb_font_funcs_set_glyph_v_origin_func (funcs, getGlyphOriginV, NULL, NULL); + hb_font_funcs_set_glyph_h_kerning_func (funcs, getGlyphKerningH, NULL, NULL); + hb_font_funcs_set_glyph_v_kerning_func (funcs, getGlyphKerningV, NULL, NULL); + hb_font_funcs_set_glyph_extents_func (funcs, getGlyphExtents, NULL, NULL); + hb_font_funcs_set_glyph_contour_point_func (funcs, getGlyphContourPoint, NULL, NULL); + + return funcs; +} + +class HbLayoutEngine : public ServerFontLayoutEngine +{ +private: + UScriptCode meScriptCode; + hb_face_t* maHbFace; + int fUnitsPerEM; + +public: + HbLayoutEngine(ServerFont&); + virtual ~HbLayoutEngine(); + + virtual bool layout(ServerFontLayout&, ImplLayoutArgs&); +}; + +HbLayoutEngine::HbLayoutEngine(ServerFont& rServerFont) +: meScriptCode(USCRIPT_INVALID_CODE), + maHbFace(NULL), + fUnitsPerEM(0) +{ + FT_Face aFtFace = rServerFont.GetFtFace(); + fUnitsPerEM = rServerFont.GetEmUnits(); + + maHbFace = hb_face_create_for_tables(getFontTable, &rServerFont, NULL); + hb_face_set_index(maHbFace, aFtFace->face_index); + hb_face_set_upem(maHbFace, fUnitsPerEM); +} + +HbLayoutEngine::~HbLayoutEngine() +{ + hb_face_destroy(maHbFace); +} + +bool HbLayoutEngine::layout(ServerFontLayout& rLayout, ImplLayoutArgs& rArgs) +{ + ServerFont& rFont = rLayout.GetServerFont(); + FT_Face aFtFace = rFont.GetFtFace(); + + hb_font_t *aHbFont = hb_font_create(maHbFace); + hb_font_set_funcs(aHbFont, getFontFuncs(), &rFont, NULL); + hb_font_set_scale(aHbFont, + ((uint64_t) aFtFace->size->metrics.x_scale * (uint64_t) fUnitsPerEM) >> 16, + ((uint64_t) aFtFace->size->metrics.y_scale * (uint64_t) fUnitsPerEM) >> 16); + hb_font_set_ppem(aHbFont, aFtFace->size->metrics.x_ppem, aFtFace->size->metrics.y_ppem); + + // allocate temporary arrays, note: round to even + int nGlyphCapacity = (3 * (rArgs.mnEndCharPos - rArgs.mnMinCharPos) | 15) + 1; + + rLayout.Reserve(nGlyphCapacity); + + Point aNewPos(0, 0); + while (true) + { + int nMinRunPos, nEndRunPos; + bool bRightToLeft; + if (!rArgs.GetNextRun(&nMinRunPos, &nEndRunPos, &bRightToLeft)) + break; + + int nRunLen = nEndRunPos - nMinRunPos; + + // find matching script + // TODO: use ICU's UScriptRun API to properly resolves "common" and + // "inherited" script codes, probably use it in GetNextRun() and return + // the script there + UScriptCode eScriptCode = USCRIPT_INVALID_CODE; + for (int i = nMinRunPos; i < nEndRunPos; ++i) + { + UErrorCode rcI18n = U_ZERO_ERROR; + UScriptCode eNextScriptCode = uscript_getScript(rArgs.mpStr[i], &rcI18n); + if ((eNextScriptCode > USCRIPT_INHERITED)) + { + eScriptCode = eNextScriptCode; + if (eNextScriptCode != USCRIPT_LATIN) + break; + } + } + if (eScriptCode < 0) // TODO: handle errors better + eScriptCode = USCRIPT_LATIN; + + meScriptCode = eScriptCode; + + LanguageTag aLangTag(rArgs.meLanguage); + OString sLanguage = OUStringToOString(aLangTag.getLanguage(), RTL_TEXTENCODING_UTF8); + + hb_buffer_t *aHbBuffer = hb_buffer_create(); + hb_buffer_set_direction(aHbBuffer, bRightToLeft ? HB_DIRECTION_RTL: HB_DIRECTION_LTR); + hb_buffer_set_script(aHbBuffer, hb_icu_script_to_script(eScriptCode)); + hb_buffer_set_language(aHbBuffer, hb_language_from_string(sLanguage.getStr(), -1)); + hb_buffer_add_utf16(aHbBuffer, rArgs.mpStr, nRunLen, nMinRunPos, nRunLen); + hb_shape(aHbFont, aHbBuffer, NULL, 0); + + int nRunGlyphCount = hb_buffer_get_length(aHbBuffer); + hb_glyph_info_t *aHbGlyphInfos = hb_buffer_get_glyph_infos(aHbBuffer, NULL); + hb_glyph_position_t *aHbPositions = hb_buffer_get_glyph_positions(aHbBuffer, NULL); + + int32_t nLastCluster = -1; + for (int i = 0; i < nRunGlyphCount; ++i) { + int32_t nGlyphIndex = aHbGlyphInfos[i].codepoint; + int32_t nCluster = aHbGlyphInfos[i].cluster; + int32_t nCharPos = nCluster; + + // if needed request glyph fallback by updating LayoutArgs + if (!nGlyphIndex) + { + if (nCharPos >= 0) + { + rArgs.NeedFallback(nCharPos, bRightToLeft); + // XXX: do we need this? HarfBuzz can take context into + // account when shaping + if ((nCharPos > 0) && needPreviousCode(rArgs.mpStr[nCharPos-1])) + rArgs.NeedFallback(nCharPos-1, bRightToLeft); + else if ((nCharPos + 1 < nEndRunPos) && needNextCode(rArgs.mpStr[nCharPos+1])) + rArgs.NeedFallback(nCharPos+1, bRightToLeft); + } + + if (SAL_LAYOUT_FOR_FALLBACK & rArgs.mnFlags) + continue; + } + + const GlyphMetric& rGM = rFont.GetGlyphMetric(nGlyphIndex); + int nGlyphWidth = rGM.GetCharWidth(); + + long nGlyphFlags = 0; + if (bRightToLeft) + nGlyphFlags |= GlyphItem::IS_RTL_GLYPH; + + // what is this for? + // XXX: rtl clusters + bool bInCluster = false; + if (nCluster == nLastCluster) + bInCluster = true; + nLastCluster = nCluster; + if (bInCluster) + nGlyphFlags |= GlyphItem::IS_IN_CLUSTER; + + if (hb_ot_layout_get_glyph_class(maHbFace, nGlyphIndex) == HB_OT_LAYOUT_GLYPH_CLASS_MARK) + nGlyphFlags |= GlyphItem::IS_DIACRITIC; + + aHbPositions[i].x_offset /= 64; + aHbPositions[i].y_offset /= 64; + aHbPositions[i].x_advance /= 64; + aHbPositions[i].y_advance /= 64; + + aNewPos = Point(aNewPos.X() + aHbPositions[i].x_offset, aNewPos.Y() - aHbPositions[i].y_offset); + + GlyphItem aGI(nCharPos, nGlyphIndex, aNewPos, nGlyphFlags, nGlyphWidth); + + // This is a hack to compensate for assumptions made elsewhere in + // the codebase, the right way is to use aHbPositions[i].x_advance + // instead of nGlyphWidth above, and leave mnNewWidth alone + // (whatever it is meant for) + if (i + 1 < nRunGlyphCount) + aGI.mnNewWidth = nGlyphWidth + (aHbPositions[i + 1].x_offset / 64); + + rLayout.AppendGlyph(aGI); + + aNewPos.X() += aHbPositions[i].x_advance; + aNewPos.Y() += aHbPositions[i].y_advance; + } + + hb_buffer_destroy(aHbBuffer); + } + + hb_font_destroy(aHbFont); + + // sort glyphs in visual order + // and then in logical order (e.g. diacritics after cluster start) + // XXX: why? + rLayout.SortGlyphItems(); + + // determine need for kashida justification + if((rArgs.mpDXArray || rArgs.mnLayoutWidth) + && ((meScriptCode == USCRIPT_ARABIC) || (meScriptCode == USCRIPT_SYRIAC))) + rArgs.mnFlags |= SAL_LAYOUT_KASHIDA_JUSTIFICATON; + + return true; +} +#endif // ENABLE_HARFBUZZ + +// ======================================================================= // bridge to ICU LayoutEngine // ======================================================================= @@ -302,21 +672,6 @@ IcuLayoutEngine::~IcuLayoutEngine() // ----------------------------------------------------------------------- -static bool lcl_CharIsJoiner(sal_Unicode cChar) -{ - return ((cChar == 0x200C) || (cChar == 0x200D)); -} - -static bool needPreviousCode(sal_Unicode cChar) -{ - return lcl_CharIsJoiner(cChar) || U16_IS_LEAD(cChar); -} - -static bool needNextCode(sal_Unicode cChar) -{ - return lcl_CharIsJoiner(cChar) || U16_IS_TRAIL(cChar); -} - namespace { LanguageCodes mapLanguageTypetoICU(LanguageType eLangCode) @@ -742,6 +1097,7 @@ bool IcuLayoutEngine::layout(ServerFontLayout& rLayout, ImplLayoutArgs& rArgs) #ifdef ARABIC_BANDAID aGI.mnNewWidth = nNewWidth; #endif + rLayout.AppendGlyph( aGI ); ++nFilteredRunGlyphCount; nLastCharPos = nCharPos; @@ -768,8 +1124,15 @@ bool IcuLayoutEngine::layout(ServerFontLayout& rLayout, ImplLayoutArgs& rArgs) ServerFontLayoutEngine* ServerFont::GetLayoutEngine() { // find best layout engine for font, platform, script and language - if (!mpLayoutEngine) + if (!mpLayoutEngine) { +#if ENABLE_HARFBUZZ + const char* pUseHarfBuzz = getenv("SAL_USE_HARFBUZZ"); + if (pUseHarfBuzz) + mpLayoutEngine = new HbLayoutEngine(*this); + else +#endif mpLayoutEngine = new IcuLayoutEngine(*this); + } return mpLayoutEngine; } |