summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKhaled Hosny <khaledhosny@eglug.org>2013-04-20 19:16:54 +0200
committerFridrich Strba <fridrich@documentfoundation.org>2013-04-23 09:23:27 +0000
commitb760eff8cb65aba99b4527232140926bb4e11e82 (patch)
treed19321203d063cccaad72c5705d9c50b60d13111
parentrestore SdrTableObj::getColumnCount (diff)
downloadcore-b760eff8cb65aba99b4527232140926bb4e11e82.tar.gz
core-b760eff8cb65aba99b4527232140926bb4e11e82.zip
Add support for using HarfBuzz instead of ICU LE
HarfBuzz is an up to date, actively maintained OpenType layout engine, while the ICU LayoutEngine we use now has been unmaintained for a while now, and existing users are encouraged to switch to HarfBuzz. This is work in progress, text layout mostly works, but the handling of combining marks is broken and needs further work, so it is kept optional for now, with SAL_USE_HARFBUZZ env variable to enable it at runtime. Change-Id: I07e698f7486379bae68329771695cd94d6e561b5 Reviewed-on: https://gerrit.libreoffice.org/3518 Reviewed-by: Fridrich Strba <fridrich@documentfoundation.org> Tested-by: Fridrich Strba <fridrich@documentfoundation.org>
-rw-r--r--vcl/generic/glyphs/gcach_layout.cxx395
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;
}