From f3a0bd8827f5d0de575b0f8c044b4bf65405b69c Mon Sep 17 00:00:00 2001 From: Lars Knoll Date: Wed, 14 Apr 2010 17:15:49 +0200 Subject: merge commit c638ddc70f6a8196f2c8b11808ab01510233a0ee from harfbuzz: commit c638ddc70f6a8196f2c8b11808ab01510233a0ee Author: Lars Knoll Date: Wed Apr 14 17:01:49 2010 +0200 Fix a bug in malayalam shaping See http://bugreports.qt.nokia.com/browse/QTBUG-1887. We were not finding the base character correctly in the case where the syllable contained a ZWJ. In addition, the indic OT specs require us to also apply the 'loca', 'cjct' and 'calt' features. They seem to be mostly unused by todays fonts, but we should better apply them anyways. Task-number: QTBUG-1887 Reviewed-by: Eskil --- src/3rdparty/harfbuzz/src/harfbuzz-indic.cpp | 23 ++- .../harfbuzz/src/harfbuzz-shaper-private.h | 55 +++---- src/3rdparty/harfbuzz/tests/shaping/main.cpp | 165 ++++++++++++++++++--- .../qtextscriptengine/tst_qtextscriptengine.cpp | 2 + 4 files changed, 195 insertions(+), 50 deletions(-) diff --git a/src/3rdparty/harfbuzz/src/harfbuzz-indic.cpp b/src/3rdparty/harfbuzz/src/harfbuzz-indic.cpp index 3c9df93..4d8418b 100644 --- a/src/3rdparty/harfbuzz/src/harfbuzz-indic.cpp +++ b/src/3rdparty/harfbuzz/src/harfbuzz-indic.cpp @@ -1107,6 +1107,7 @@ static inline void splitMatra(unsigned short *reordered, int matra, int &len) #ifndef NO_OPENTYPE static const HB_OpenTypeFeature indic_features[] = { + { HB_MAKE_TAG('l', 'o', 'c', 'a'), LocaProperty }, { HB_MAKE_TAG('c', 'c', 'm', 'p'), CcmpProperty }, { HB_MAKE_TAG('i', 'n', 'i', 't'), InitProperty }, { HB_MAKE_TAG('n', 'u', 'k', 't'), NuktaProperty }, @@ -1115,12 +1116,14 @@ static const HB_OpenTypeFeature indic_features[] = { { HB_MAKE_TAG('b', 'l', 'w', 'f'), BelowFormProperty }, { HB_MAKE_TAG('h', 'a', 'l', 'f'), HalfFormProperty }, { HB_MAKE_TAG('p', 's', 't', 'f'), PostFormProperty }, + { HB_MAKE_TAG('c', 'j', 'c', 't'), ConjunctFormProperty }, { HB_MAKE_TAG('v', 'a', 't', 'u'), VattuProperty }, { HB_MAKE_TAG('p', 'r', 'e', 's'), PreSubstProperty }, { HB_MAKE_TAG('b', 'l', 'w', 's'), BelowSubstProperty }, { HB_MAKE_TAG('a', 'b', 'v', 's'), AboveSubstProperty }, { HB_MAKE_TAG('p', 's', 't', 's'), PostSubstProperty }, { HB_MAKE_TAG('h', 'a', 'l', 'n'), HalantProperty }, + { HB_MAKE_TAG('c', 'a', 'l', 't'), IndicCaltProperty }, { 0, 0 } }; #endif @@ -1148,6 +1151,8 @@ static QString propertiesToString(int properties) { QString res; properties = ~properties; + if (properties & LocaProperty) + res += "Loca "; if (properties & CcmpProperty) res += "Ccmp "; if (properties & InitProperty) @@ -1168,6 +1173,8 @@ static QString propertiesToString(int properties) res += "HalfForm "; if (properties & PostFormProperty) res += "PostForm "; + if (properties & ConjunctFormProperty) + res += "PostForm "; if (properties & VattuProperty) res += "Vattu "; if (properties & PreSubstProperty) @@ -1182,6 +1189,8 @@ static QString propertiesToString(int properties) res += "Halant "; if (properties & CligProperty) res += "Clig "; + if (properties & IndicCaltProperty) + res += "Calt "; return res; } #endif @@ -1296,10 +1305,15 @@ static bool indic_shape_syllable(HB_Bool openType, HB_ShaperItem *item, bool inv } int skipped = 0; Position pos = Post; - for (i = len-1; i > base; i--) { + for (i = len-1; i >= base; i--) { if (position[i] != Consonant && (position[i] != Control || script == HB_Script_Kannada)) continue; + if (i < len-1 && position[i] == Control && position[i+1] == Consonant) { + base = i+1; + break; + } + Position charPosition = indic_position(uc[i]); if (pos == Post && charPosition == Post) { pos = Post; @@ -1545,16 +1559,20 @@ static bool indic_shape_syllable(HB_Bool openType, HB_ShaperItem *item, bool inv // features we should always apply for (i = 0; i < len; ++i) - properties[i] = ~(CcmpProperty + properties[i] = ~(LocaProperty + | CcmpProperty | NuktaProperty | VattuProperty + | ConjunctFormProperty | PreSubstProperty | BelowSubstProperty | AboveSubstProperty | PostSubstProperty | HalantProperty + | IndicCaltProperty | PositioningProperties); + // Loca always applies // Ccmp always applies // Init if (item->item.pos == 0 @@ -1611,6 +1629,7 @@ static bool indic_shape_syllable(HB_Bool openType, HB_ShaperItem *item, bool inv // abvs always applies // psts always applies // halant always applies + // calt always applies #ifdef INDIC_DEBUG // { diff --git a/src/3rdparty/harfbuzz/src/harfbuzz-shaper-private.h b/src/3rdparty/harfbuzz/src/harfbuzz-shaper-private.h index 80bccf8..2fce7df 100644 --- a/src/3rdparty/harfbuzz/src/harfbuzz-shaper-private.h +++ b/src/3rdparty/harfbuzz/src/harfbuzz-shaper-private.h @@ -57,34 +57,37 @@ typedef enum } HB_CombiningClass; typedef enum { - CcmpProperty = 0x1, - InitProperty = 0x2, - IsolProperty = 0x4, - FinaProperty = 0x8, - MediProperty = 0x10, - RligProperty = 0x20, - CaltProperty = 0x40, - LigaProperty = 0x80, - DligProperty = 0x100, - CswhProperty = 0x200, - MsetProperty = 0x400, + LocaProperty = 0x1, + CcmpProperty = 0x2, + InitProperty = 0x4, + IsolProperty = 0x8, + FinaProperty = 0x10, + MediProperty = 0x20, + RligProperty = 0x40, + CaltProperty = 0x80, + LigaProperty = 0x100, + DligProperty = 0x200, + CswhProperty = 0x400, + MsetProperty = 0x800, /* used by indic and myanmar shaper */ - NuktaProperty = 0x4, - AkhantProperty = 0x8, - RephProperty = 0x10, - PreFormProperty = 0x20, - BelowFormProperty = 0x40, - AboveFormProperty = 0x80, - HalfFormProperty = 0x100, - PostFormProperty = 0x200, - VattuProperty = 0x400, - PreSubstProperty = 0x800, - BelowSubstProperty = 0x1000, - AboveSubstProperty = 0x2000, - PostSubstProperty = 0x4000, - HalantProperty = 0x8000, - CligProperty = 0x10000 + NuktaProperty = 0x8, + AkhantProperty = 0x10, + RephProperty = 0x20, + PreFormProperty = 0x40, + BelowFormProperty = 0x80, + AboveFormProperty = 0x100, + HalfFormProperty = 0x200, + PostFormProperty = 0x400, + ConjunctFormProperty = 0x800, + VattuProperty = 0x1000, + PreSubstProperty = 0x2000, + BelowSubstProperty = 0x4000, + AboveSubstProperty = 0x8000, + PostSubstProperty = 0x10000, + HalantProperty = 0x20000, + CligProperty = 0x40000, + IndicCaltProperty = 0x80000 } HB_OpenTypeProperty; diff --git a/src/3rdparty/harfbuzz/tests/shaping/main.cpp b/src/3rdparty/harfbuzz/tests/shaping/main.cpp index 827ac30..28f8e07 100644 --- a/src/3rdparty/harfbuzz/tests/shaping/main.cpp +++ b/src/3rdparty/harfbuzz/tests/shaping/main.cpp @@ -136,13 +136,13 @@ HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 return HB_Err_Ok; } -void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics) +void hb_getGlyphMetrics(HB_Font, HB_Glyph, HB_GlyphMetrics *metrics) { // ### metrics->x = metrics->y = metrics->width = metrics->height = metrics->xOffset = metrics->yOffset = 0; } -HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric) +HB_Fixed hb_getFontMetric(HB_Font, HB_FontMetric ) { return 0; // #### } @@ -169,6 +169,8 @@ public slots: void initTestCase(); void cleanupTestCase(); private slots: + void greek(); + void devanagari(); void bengali(); void gurmukhi(); @@ -203,18 +205,25 @@ void tst_QScriptEngine::cleanupTestCase() FT_Done_FreeType(freetype); } -struct ShapeTable { - unsigned short unicode[16]; - unsigned short glyphs[16]; +class Shaper +{ +public: + Shaper(FT_Face face, HB_Script script, const QString &str); + + HB_FontRec hbFont; + HB_ShaperItem shaper_item; + QVarLengthArray hb_glyphs; + QVarLengthArray hb_attributes; + QVarLengthArray hb_advances; + QVarLengthArray hb_offsets; + QVarLengthArray hb_logClusters; + }; -static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) +Shaper::Shaper(FT_Face face, HB_Script script, const QString &str) { - QString str = QString::fromUtf16( s->unicode ); - HB_Face hbFace = HB_NewFace(face, hb_getSFntTable); - HB_FontRec hbFont; hbFont.klass = &hb_fontClass; hbFont.userData = face; hbFont.x_ppem = face->size->metrics.x_ppem; @@ -222,7 +231,6 @@ static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) hbFont.x_scale = face->size->metrics.x_scale; hbFont.y_scale = face->size->metrics.y_scale; - HB_ShaperItem shaper_item; shaper_item.kerning_applied = false; shaper_item.string = reinterpret_cast(str.constData()); shaper_item.stringLength = str.length(); @@ -237,11 +245,6 @@ static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) shaper_item.glyphIndicesPresent = false; shaper_item.initialGlyphCount = 0; - QVarLengthArray hb_glyphs(shaper_item.num_glyphs); - QVarLengthArray hb_attributes(shaper_item.num_glyphs); - QVarLengthArray hb_advances(shaper_item.num_glyphs); - QVarLengthArray hb_offsets(shaper_item.num_glyphs); - QVarLengthArray hb_logClusters(shaper_item.num_glyphs); while (1) { hb_glyphs.resize(shaper_item.num_glyphs); @@ -263,10 +266,66 @@ static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) if (HB_ShapeItem(&shaper_item)) break; - } HB_FreeFace(hbFace); +} + + +static bool decomposedShaping(FT_Face face, HB_Script script, const QChar &ch) +{ + QString uc = QString().append(ch); + Shaper shaper(face, script, uc); + + uc = uc.normalized(QString::NormalizationForm_D); + Shaper decomposed(face, script, uc); + + if( shaper.shaper_item.num_glyphs != decomposed.shaper_item.num_glyphs ) + goto error; + + for (unsigned int i = 0; i < shaper.shaper_item.num_glyphs; ++i) { + if ((shaper.shaper_item.glyphs[i]&0xffffff) != (decomposed.shaper_item.glyphs[i]&0xffffff)) + goto error; + } + return true; + error: + QString str = ""; + int i = 0; + while (i < uc.length()) { + str += QString("%1 ").arg(uc[i].unicode(), 4, 16); + ++i; + } + qDebug("%s: decomposedShaping of char %4x failed\n decomposedString: %s\n nglyphs=%d, decomposed nglyphs %d", + face->family_name, + ch.unicode(), str.toLatin1().data(), + shaper.shaper_item.num_glyphs, + decomposed.shaper_item.num_glyphs); + + str = ""; + i = 0; + while (i < shaper.shaper_item.num_glyphs) { + str += QString("%1 ").arg(shaper.shaper_item.glyphs[i], 4, 16); + ++i; + } + qDebug(" composed glyph result = %s", str.toLatin1().constData()); + str = ""; + i = 0; + while (i < decomposed.shaper_item.num_glyphs) { + str += QString("%1 ").arg(decomposed.shaper_item.glyphs[i], 4, 16); + ++i; + } + qDebug(" decomposed glyph result = %s", str.toLatin1().constData()); + return false; +} + +struct ShapeTable { + unsigned short unicode[16]; + unsigned short glyphs[16]; +}; + +static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) +{ + Shaper shaper(face, script, QString::fromUtf16( s->unicode )); hb_uint32 nglyphs = 0; const unsigned short *g = s->glyphs; @@ -275,16 +334,16 @@ static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) g++; } - if( nglyphs != shaper_item.num_glyphs ) + if( nglyphs != shaper.shaper_item.num_glyphs ) goto error; for (hb_uint32 i = 0; i < nglyphs; ++i) { - if ((shaper_item.glyphs[i]&0xffffff) != s->glyphs[i]) + if ((shaper.shaper_item.glyphs[i]&0xffffff) != s->glyphs[i]) goto error; } return true; error: - str = ""; + QString str = ""; const unsigned short *uc = s->unicode; while (*uc) { str += QString("%1 ").arg(*uc, 4, 16); @@ -293,18 +352,78 @@ static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script) qDebug("%s: shaping of string %s failed, nglyphs=%d, expected %d", face->family_name, str.toLatin1().constData(), - shaper_item.num_glyphs, nglyphs); + shaper.shaper_item.num_glyphs, nglyphs); str = ""; hb_uint32 i = 0; - while (i < shaper_item.num_glyphs) { - str += QString("%1 ").arg(shaper_item.glyphs[i], 4, 16); + while (i < shaper.shaper_item.num_glyphs) { + str += QString("%1 ").arg(shaper.shaper_item.glyphs[i], 4, 16); ++i; } qDebug(" glyph result = %s", str.toLatin1().constData()); return false; } + +void tst_QScriptEngine::greek() +{ + FT_Face face = loadFace("DejaVuSans.ttf"); + if (face) { + for (int uc = 0x1f00; uc <= 0x1fff; ++uc) { + QString str; + str.append(uc); + if (str.normalized(QString::NormalizationForm_D).normalized(QString::NormalizationForm_C) != str) { + //qDebug() << "skipping" << hex << uc; + continue; + } + if (uc == 0x1fc1 || uc == 0x1fed) + continue; + QVERIFY( decomposedShaping(face, HB_Script_Greek, QChar(uc)) ); + } + FT_Done_Face(face); + } else { + QSKIP("couln't find DejaVu Sans", SkipAll); + } + + + face = loadFace("SBL_grk.ttf"); + if (face) { + for (int uc = 0x1f00; uc <= 0x1fff; ++uc) { + QString str; + str.append(uc); + if (str.normalized(QString::NormalizationForm_D).normalized(QString::NormalizationForm_C) != str) { + //qDebug() << "skipping" << hex << uc; + continue; + } + if (uc == 0x1fc1 || uc == 0x1fed) + continue; + QVERIFY( decomposedShaping(face, HB_Script_Greek, QChar(uc)) ); + + } + + const ShapeTable shape_table [] = { + { { 0x3b1, 0x300, 0x313, 0x0 }, + { 0xb8, 0x3d3, 0x3c7, 0x0 } }, + { { 0x3b1, 0x313, 0x300, 0x0 }, + { 0xd4, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(face, s, HB_Script_Greek) ); + ++s; + } + + FT_Done_Face(face); + } else { + QSKIP("couln't find DejaVu Sans", SkipAll); + } +} + + void tst_QScriptEngine::devanagari() { { @@ -1011,6 +1130,8 @@ void tst_QScriptEngine::malayalam() { 0x3f8, 0x0 } }, { { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0x0 }, { 0x2ff, 0x0 } }, + { { 0xd30, 0xd4d, 0x200d, 0xd35, 0xd4d, 0xd35, 0x0 }, + { 0xf3, 0x350, 0x0 } }, { {0}, {0} } }; diff --git a/tests/auto/qtextscriptengine/tst_qtextscriptengine.cpp b/tests/auto/qtextscriptengine/tst_qtextscriptengine.cpp index 6de3f59..940e78d 100644 --- a/tests/auto/qtextscriptengine/tst_qtextscriptengine.cpp +++ b/tests/auto/qtextscriptengine/tst_qtextscriptengine.cpp @@ -870,6 +870,8 @@ void tst_QTextScriptEngine::malayalam() { 0x3f8, 0x0 } }, { { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0x0 }, { 0x2ff, 0x0 } }, + { { 0xd30, 0xd4d, 0x200d, 0xd35, 0xd4d, 0xd35, 0x0 }, + { 0xf3, 0x350, 0x0 } }, { {0}, {0} } }; -- cgit v0.12