Home | History | Annotate | Download | only in shaping
      1 /*
      2  * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies)
      3  *
      4  * This is part of HarfBuzz, an OpenType Layout engine library.
      5  *
      6  * Permission is hereby granted, without written agreement and without
      7  * license or royalty fees, to use, copy, modify, and distribute this
      8  * software and its documentation for any purpose, provided that the
      9  * above copyright notice and the following two paragraphs appear in
     10  * all copies of this software.
     11  *
     12  * IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
     13  * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
     14  * ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
     15  * IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
     16  * DAMAGE.
     17  *
     18  * THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
     19  * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
     20  * FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
     21  * ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
     22  * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
     23  */
     24 
     25 #include <QtTest/QtTest>
     26 
     27 #include <ft2build.h>
     28 #include FT_FREETYPE_H
     29 #include FT_TRUETYPE_TABLES_H
     30 
     31 #include <harfbuzz-shaper.h>
     32 #include <harfbuzz-global.h>
     33 #include <harfbuzz-gpos.h>
     34 
     35 static FT_Library freetype;
     36 
     37 static FT_Face loadFace(const char *name)
     38 {
     39     FT_Face face;
     40     char path[256];
     41 
     42     strcpy(path, SRCDIR);
     43     strcat(path, "/fonts/");
     44     strcat(path, name);
     45 
     46     if (FT_New_Face(freetype, path, /*index*/0, &face))
     47         return 0;
     48     return face;
     49 }
     50 
     51 static HB_UChar32 getChar(const HB_UChar16 *string, hb_uint32 length, hb_uint32 &i)
     52 {
     53     HB_UChar32 ch;
     54     if (HB_IsHighSurrogate(string[i])
     55         && i < length - 1
     56         && HB_IsLowSurrogate(string[i + 1])) {
     57         ch = HB_SurrogateToUcs4(string[i], string[i + 1]);
     58         ++i;
     59     } else {
     60         ch = string[i];
     61     }
     62     return ch;
     63 }
     64 
     65 static HB_Bool hb_stringToGlyphs(HB_Font font, const HB_UChar16 *string, hb_uint32 length, HB_Glyph *glyphs, hb_uint32 *numGlyphs, HB_Bool /*rightToLeft*/)
     66 {
     67     FT_Face face = (FT_Face)font->userData;
     68     if (length > *numGlyphs)
     69         return false;
     70 
     71     int glyph_pos = 0;
     72     for (hb_uint32 i = 0; i < length; ++i) {
     73         glyphs[glyph_pos] = FT_Get_Char_Index(face, getChar(string, length, i));
     74         ++glyph_pos;
     75     }
     76 
     77     *numGlyphs = glyph_pos;
     78 
     79     return true;
     80 }
     81 
     82 static void hb_getAdvances(HB_Font /*font*/, const HB_Glyph * /*glyphs*/, hb_uint32 numGlyphs, HB_Fixed *advances, int /*flags*/)
     83 {
     84     for (hb_uint32 i = 0; i < numGlyphs; ++i)
     85         advances[i] = 0; // ### not tested right now
     86 }
     87 
     88 static HB_Bool hb_canRender(HB_Font font, const HB_UChar16 *string, hb_uint32 length)
     89 {
     90     FT_Face face = (FT_Face)font->userData;
     91 
     92     for (hb_uint32 i = 0; i < length; ++i)
     93         if (!FT_Get_Char_Index(face, getChar(string, length, i)))
     94             return false;
     95 
     96     return true;
     97 }
     98 
     99 static HB_Error hb_getSFntTable(void *font, HB_Tag tableTag, HB_Byte *buffer, HB_UInt *length)
    100 {
    101     FT_Face face = (FT_Face)font;
    102     FT_ULong ftlen = *length;
    103     FT_Error error = 0;
    104 
    105     if (!FT_IS_SFNT(face))
    106         return HB_Err_Invalid_Argument;
    107 
    108     error = FT_Load_Sfnt_Table(face, tableTag, 0, buffer, &ftlen);
    109     *length = ftlen;
    110     return (HB_Error)error;
    111 }
    112 
    113 HB_Error hb_getPointInOutline(HB_Font font, HB_Glyph glyph, int flags, hb_uint32 point, HB_Fixed *xpos, HB_Fixed *ypos, hb_uint32 *nPoints)
    114 {
    115     HB_Error error = HB_Err_Ok;
    116     FT_Face face = (FT_Face)font->userData;
    117 
    118     int load_flags = (flags & HB_ShaperFlag_UseDesignMetrics) ? FT_LOAD_NO_HINTING : FT_LOAD_DEFAULT;
    119 
    120     if ((error = (HB_Error)FT_Load_Glyph(face, glyph, load_flags)))
    121         return error;
    122 
    123     if (face->glyph->format != ft_glyph_format_outline)
    124         return (HB_Error)HB_Err_Invalid_SubTable;
    125 
    126     *nPoints = face->glyph->outline.n_points;
    127     if (!(*nPoints))
    128         return HB_Err_Ok;
    129 
    130     if (point > *nPoints)
    131         return (HB_Error)HB_Err_Invalid_SubTable;
    132 
    133     *xpos = face->glyph->outline.points[point].x;
    134     *ypos = face->glyph->outline.points[point].y;
    135 
    136     return HB_Err_Ok;
    137 }
    138 
    139 void hb_getGlyphMetrics(HB_Font font, HB_Glyph glyph, HB_GlyphMetrics *metrics)
    140 {
    141     // ###
    142     metrics->x = metrics->y = metrics->width = metrics->height = metrics->xOffset = metrics->yOffset = 0;
    143 }
    144 
    145 HB_Fixed hb_getFontMetric(HB_Font font, HB_FontMetric metric)
    146 {
    147     return 0; // ####
    148 }
    149 
    150 const HB_FontClass hb_fontClass = {
    151     hb_stringToGlyphs, hb_getAdvances, hb_canRender,
    152     hb_getPointInOutline, hb_getGlyphMetrics, hb_getFontMetric
    153 };
    154 
    155 
    156 //TESTED_CLASS=
    157 //TESTED_FILES= gui/text/qscriptengine.cpp
    158 
    159 class tst_QScriptEngine : public QObject
    160 {
    161 Q_OBJECT
    162 
    163 public:
    164     tst_QScriptEngine();
    165     virtual ~tst_QScriptEngine();
    166 
    167 
    168 public slots:
    169     void initTestCase();
    170     void cleanupTestCase();
    171 private slots:
    172     void devanagari();
    173     void bengali();
    174     void gurmukhi();
    175     // gujarati missing
    176     void oriya();
    177     void tamil();
    178     void telugu();
    179     void kannada();
    180     void malayalam();
    181     // sinhala missing
    182 
    183     void khmer();
    184     void linearB();
    185 };
    186 
    187 tst_QScriptEngine::tst_QScriptEngine()
    188 {
    189 }
    190 
    191 tst_QScriptEngine::~tst_QScriptEngine()
    192 {
    193 }
    194 
    195 void tst_QScriptEngine::initTestCase()
    196 {
    197     FT_Init_FreeType(&freetype);
    198 }
    199 
    200 void tst_QScriptEngine::cleanupTestCase()
    201 {
    202     FT_Done_FreeType(freetype);
    203 }
    204 
    205 struct ShapeTable {
    206     unsigned short unicode[16];
    207     unsigned short glyphs[16];
    208 };
    209 
    210 static bool shaping(FT_Face face, const ShapeTable *s, HB_Script script)
    211 {
    212     QString str = QString::fromUtf16( s->unicode );
    213 
    214     HB_Face hbFace = HB_NewFace(face, hb_getSFntTable);
    215 
    216     HB_FontRec hbFont;
    217     hbFont.klass = &hb_fontClass;
    218     hbFont.userData = face;
    219     hbFont.x_ppem  = face->size->metrics.x_ppem;
    220     hbFont.y_ppem  = face->size->metrics.y_ppem;
    221     hbFont.x_scale = face->size->metrics.x_scale;
    222     hbFont.y_scale = face->size->metrics.y_scale;
    223 
    224     HB_ShaperItem shaper_item;
    225     shaper_item.kerning_applied = false;
    226     shaper_item.string = reinterpret_cast<const HB_UChar16 *>(str.constData());
    227     shaper_item.stringLength = str.length();
    228     shaper_item.item.script = script;
    229     shaper_item.item.pos = 0;
    230     shaper_item.item.length = shaper_item.stringLength;
    231     shaper_item.item.bidiLevel = 0; // ###
    232     shaper_item.shaperFlags = 0;
    233     shaper_item.font = &hbFont;
    234     shaper_item.face = hbFace;
    235     shaper_item.num_glyphs = shaper_item.item.length;
    236     shaper_item.glyphIndicesPresent = false;
    237     shaper_item.initialGlyphCount = 0;
    238 
    239     QVarLengthArray<HB_Glyph> hb_glyphs(shaper_item.num_glyphs);
    240     QVarLengthArray<HB_GlyphAttributes> hb_attributes(shaper_item.num_glyphs);
    241     QVarLengthArray<HB_Fixed> hb_advances(shaper_item.num_glyphs);
    242     QVarLengthArray<HB_FixedPoint> hb_offsets(shaper_item.num_glyphs);
    243     QVarLengthArray<unsigned short> hb_logClusters(shaper_item.num_glyphs);
    244 
    245     while (1) {
    246         hb_glyphs.resize(shaper_item.num_glyphs);
    247         hb_attributes.resize(shaper_item.num_glyphs);
    248         hb_advances.resize(shaper_item.num_glyphs);
    249         hb_offsets.resize(shaper_item.num_glyphs);
    250         hb_logClusters.resize(shaper_item.num_glyphs);
    251 
    252         memset(hb_glyphs.data(), 0, hb_glyphs.size() * sizeof(HB_Glyph));
    253         memset(hb_attributes.data(), 0, hb_attributes.size() * sizeof(HB_GlyphAttributes));
    254         memset(hb_advances.data(), 0, hb_advances.size() * sizeof(HB_Fixed));
    255         memset(hb_offsets.data(), 0, hb_offsets.size() * sizeof(HB_FixedPoint));
    256 
    257         shaper_item.glyphs = hb_glyphs.data();
    258         shaper_item.attributes = hb_attributes.data();
    259         shaper_item.advances = hb_advances.data();
    260         shaper_item.offsets = hb_offsets.data();
    261         shaper_item.log_clusters = hb_logClusters.data();
    262 
    263         if (HB_ShapeItem(&shaper_item))
    264             break;
    265 
    266     }
    267 
    268     HB_FreeFace(hbFace);
    269 
    270     hb_uint32 nglyphs = 0;
    271     const unsigned short *g = s->glyphs;
    272     while ( *g ) {
    273 	nglyphs++;
    274 	g++;
    275     }
    276 
    277     if( nglyphs != shaper_item.num_glyphs )
    278 	goto error;
    279 
    280     for (hb_uint32 i = 0; i < nglyphs; ++i) {
    281 	if ((shaper_item.glyphs[i]&0xffffff) != s->glyphs[i])
    282 	    goto error;
    283     }
    284     return true;
    285  error:
    286     str = "";
    287     const unsigned short *uc = s->unicode;
    288     while (*uc) {
    289 	str += QString("%1 ").arg(*uc, 4, 16);
    290 	++uc;
    291     }
    292     qDebug("%s: shaping of string %s failed, nglyphs=%d, expected %d",
    293            face->family_name,
    294            str.toLatin1().constData(),
    295            shaper_item.num_glyphs, nglyphs);
    296 
    297     str = "";
    298     hb_uint32 i = 0;
    299     while (i < shaper_item.num_glyphs) {
    300 	str += QString("%1 ").arg(shaper_item.glyphs[i], 4, 16);
    301 	++i;
    302     }
    303     qDebug("    glyph result = %s", str.toLatin1().constData());
    304     return false;
    305 }
    306 
    307 void tst_QScriptEngine::devanagari()
    308 {
    309     {
    310         FT_Face face = loadFace("raghu.ttf");
    311         if (face) {
    312 	    const ShapeTable shape_table [] = {
    313 		// Ka
    314 		{ { 0x0915, 0x0 },
    315 		  { 0x0080, 0x0 } },
    316 		// Ka Halant
    317 		{ { 0x0915, 0x094d, 0x0 },
    318 		  { 0x0080, 0x0051, 0x0 } },
    319 		// Ka Halant Ka
    320 		{ { 0x0915, 0x094d, 0x0915, 0x0 },
    321 		  { 0x00c8, 0x0080, 0x0 } },
    322 		// Ka MatraI
    323 		{ { 0x0915, 0x093f, 0x0 },
    324 		  { 0x01d1, 0x0080, 0x0 } },
    325 		// Ra Halant Ka
    326 		{ { 0x0930, 0x094d, 0x0915, 0x0 },
    327 		  { 0x0080, 0x005b, 0x0 } },
    328 		// Ra Halant Ka MatraI
    329 		{ { 0x0930, 0x094d, 0x0915, 0x093f, 0x0 },
    330 		  { 0x01d1, 0x0080, 0x005b, 0x0 } },
    331 		// MatraI
    332 		{ { 0x093f, 0x0 },
    333 		  { 0x01d4, 0x029c, 0x0 } },
    334 		// Ka Nukta
    335 		{ { 0x0915, 0x093c, 0x0 },
    336 		  { 0x00a4, 0x0 } },
    337 		// Ka Halant Ra
    338 		{ { 0x0915, 0x094d, 0x0930, 0x0 },
    339 		  { 0x0110, 0x0 } },
    340 		// Ka Halant Ra Halant Ka
    341 		{ { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0x0 },
    342 		  { 0x0158, 0x0080, 0x0 } },
    343 		{ { 0x0930, 0x094d, 0x200d, 0x0 },
    344 		  { 0x00e2, 0x0 } },
    345 		{ { 0x0915, 0x094d, 0x0930, 0x094d, 0x200d, 0x0 },
    346 		  { 0x0158, 0x0 } },
    347 
    348 		{ {0}, {0} }
    349 	    };
    350 
    351 
    352 	    const ShapeTable *s = shape_table;
    353 	    while (s->unicode[0]) {
    354 		QVERIFY( shaping(face, s, HB_Script_Devanagari) );
    355 		++s;
    356 	    }
    357 
    358             FT_Done_Face(face);
    359 	} else {
    360 	    QSKIP("couln't find raghu.ttf", SkipAll);
    361 	}
    362     }
    363 
    364     {
    365         FT_Face face = loadFace("mangal.ttf");
    366         if (face) {
    367 	    const ShapeTable shape_table [] = {
    368 		// Ka
    369 		{ { 0x0915, 0x0 },
    370 		  { 0x0080, 0x0 } },
    371 		// Ka Halant
    372 		{ { 0x0915, 0x094d, 0x0 },
    373 		  { 0x0080, 0x0051, 0x0 } },
    374 		// Ka Halant Ka
    375 		{ { 0x0915, 0x094d, 0x0915, 0x0 },
    376 		  { 0x00c8, 0x0080, 0x0 } },
    377 		// Ka MatraI
    378 		{ { 0x0915, 0x093f, 0x0 },
    379 		  { 0x01d1, 0x0080, 0x0 } },
    380 		// Ra Halant Ka
    381 		{ { 0x0930, 0x094d, 0x0915, 0x0 },
    382 		  { 0x0080, 0x005b, 0x0 } },
    383 		// Ra Halant Ka MatraI
    384 		{ { 0x0930, 0x094d, 0x0915, 0x093f, 0x0 },
    385 		  { 0x01d1, 0x0080, 0x005b, 0x0 } },
    386 		// MatraI
    387 		{ { 0x093f, 0x0 },
    388 		  { 0x01d4, 0x029c, 0x0 } },
    389 		// Ka Nukta
    390 		{ { 0x0915, 0x093c, 0x0 },
    391 		  { 0x00a4, 0x0 } },
    392 		// Ka Halant Ra
    393 		{ { 0x0915, 0x094d, 0x0930, 0x0 },
    394 		  { 0x0110, 0x0 } },
    395 		// Ka Halant Ra Halant Ka
    396 		{ { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0x0 },
    397 		  { 0x0158, 0x0080, 0x0 } },
    398 
    399                 { { 0x92b, 0x94d, 0x930, 0x0 },
    400                   { 0x125, 0x0 } },
    401                 { { 0x92b, 0x93c, 0x94d, 0x930, 0x0 },
    402                   { 0x149, 0x0 } },
    403 		{ {0}, {0} }
    404 	    };
    405 
    406 	    const ShapeTable *s = shape_table;
    407 	    while (s->unicode[0]) {
    408 		QVERIFY( shaping(face, s, HB_Script_Devanagari) );
    409 		++s;
    410 	    }
    411 
    412             FT_Done_Face(face);
    413 	} else {
    414 	    QSKIP("couldn't find mangal.ttf", SkipAll);
    415 	}
    416     }
    417 }
    418 
    419 void tst_QScriptEngine::bengali()
    420 {
    421     {
    422         FT_Face face = loadFace("AkaashNormal.ttf");
    423         if (face) {
    424 	    const ShapeTable shape_table [] = {
    425 		// Ka
    426 		{ { 0x0995, 0x0 },
    427 		  { 0x0151, 0x0 } },
    428 		// Ka Halant
    429 		{ { 0x0995, 0x09cd, 0x0 },
    430 		  { 0x0151, 0x017d, 0x0 } },
    431 		// Ka Halant Ka
    432 		{ { 0x0995, 0x09cd, 0x0995, 0x0 },
    433 		  { 0x019b, 0x0 } },
    434 		// Ka MatraI
    435 		{ { 0x0995, 0x09bf, 0x0 },
    436 		  { 0x0173, 0x0151, 0x0 } },
    437 		// Ra Halant Ka
    438 		{ { 0x09b0, 0x09cd, 0x0995, 0x0 },
    439 		  { 0x0151, 0x0276, 0x0 } },
    440 		// Ra Halant Ka MatraI
    441 		{ { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0x0 },
    442 		  { 0x0173, 0x0151, 0x0276, 0x0 } },
    443 		// Ka Nukta
    444 		{ { 0x0995, 0x09bc, 0x0 },
    445 		  { 0x0151, 0x0171, 0x0 } },
    446 		// Ka Halant Ra
    447 		{ { 0x0995, 0x09cd, 0x09b0, 0x0 },
    448 		  { 0x01f4, 0x0 } },
    449 		// Ka Halant Ra Halant Ka
    450 		{ { 0x0995, 0x09cd, 0x09b0, 0x09cd, 0x0995, 0x0 },
    451 		  { 0x025c, 0x0276, 0x0151, 0x0 } },
    452 		// Ya + Halant
    453 		{ { 0x09af, 0x09cd, 0x0 },
    454 		  { 0x016a, 0x017d, 0x0 } },
    455 		// Da Halant Ya -> Da Ya-Phala
    456 		{ { 0x09a6, 0x09cd, 0x09af, 0x0 },
    457 		  { 0x01e5, 0x0 } },
    458 		// A Halant Ya -> A Ya-phala
    459 		{ { 0x0985, 0x09cd, 0x09af, 0x0 },
    460 		  { 0x0145, 0x01cf, 0x0 } },
    461 		// Na Halant Ka
    462 		{ { 0x09a8, 0x09cd, 0x0995, 0x0 },
    463 		  { 0x026f, 0x0151, 0x0 } },
    464 		// Na Halant ZWNJ Ka
    465 		{ { 0x09a8, 0x09cd, 0x200c, 0x0995, 0x0 },
    466 		  { 0x0164, 0x017d, 0x0151, 0x0 } },
    467 		// Na Halant ZWJ Ka
    468 		{ { 0x09a8, 0x09cd, 0x200d, 0x0995, 0x0 },
    469 		  { 0x026f, 0x0151, 0x0 } },
    470 		// Ka Halant ZWNJ Ka
    471 		{ { 0x0995, 0x09cd, 0x200c, 0x0995, 0x0 },
    472 		  { 0x0151, 0x017d, 0x0151, 0x0 } },
    473 		// Ka Halant ZWJ Ka
    474 		{ { 0x0995, 0x09cd, 0x200d, 0x0995, 0x0 },
    475 		  { 0x025c, 0x0151, 0x0 } },
    476 		// Na Halant Ra
    477 		{ { 0x09a8, 0x09cd, 0x09b0, 0x0 },
    478 		  { 0x0207, 0x0 } },
    479 		// Na Halant ZWNJ Ra
    480 		{ { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0x0 },
    481 		  { 0x0164, 0x017d, 0x016b, 0x0 } },
    482 		// Na Halant ZWJ Ra
    483 		{ { 0x09a8, 0x09cd, 0x200d, 0x09b0, 0x0 },
    484 		  { 0x026f, 0x016b, 0x0 } },
    485 		// Na Halant Ba
    486 		{ { 0x09a8, 0x09cd, 0x09ac, 0x0 },
    487 		  { 0x022f, 0x0 } },
    488 		// Na Halant ZWNJ Ba
    489 		{ { 0x09a8, 0x09cd, 0x200c, 0x09ac, 0x0 },
    490 		  { 0x0164, 0x017d, 0x0167, 0x0 } },
    491 		// Na Halant ZWJ Ba
    492 		{ { 0x09a8, 0x09cd, 0x200d, 0x09ac, 0x0 },
    493 		  { 0x026f, 0x0167, 0x0 } },
    494 		// Na Halant Dha
    495 		{ { 0x09a8, 0x09cd, 0x09a7, 0x0 },
    496 		  { 0x01d3, 0x0 } },
    497 		// Na Halant ZWNJ Dha
    498 		{ { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0x0 },
    499 		  { 0x0164, 0x017d, 0x0163, 0x0 } },
    500 		// Na Halant ZWJ Dha
    501 		{ { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0x0 },
    502 		  { 0x026f, 0x0163, 0x0 } },
    503 		// Ra Halant Ka MatraAU
    504 		{ { 0x09b0, 0x09cd, 0x0995, 0x09cc, 0x0 },
    505 		  { 0x0179, 0x0151, 0x0276, 0x017e, 0x0 } },
    506 		// Ra Halant Ba Halant Ba
    507 		{ { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0x0 },
    508 		  { 0x0232, 0x0276, 0x0 } },
    509                 { { 0x9b0, 0x9cd, 0x995, 0x9be, 0x982, 0x0 },
    510                   { 0x151, 0x276, 0x172, 0x143, 0x0 } },
    511                 { { 0x9b0, 0x9cd, 0x995, 0x9be, 0x983, 0x0 },
    512                   { 0x151, 0x276, 0x172, 0x144, 0x0 } },
    513 
    514 		{ {0}, {0} }
    515 	    };
    516 
    517 
    518 	    const ShapeTable *s = shape_table;
    519 	    while (s->unicode[0]) {
    520 		QVERIFY( shaping(face, s, HB_Script_Bengali) );
    521 		++s;
    522 	    }
    523 
    524             FT_Done_Face(face);
    525 	} else {
    526 	    QSKIP("couln't find AkaashNormal.ttf", SkipAll);
    527 	}
    528     }
    529     {
    530         FT_Face face = loadFace("MuktiNarrow.ttf");
    531         if (face) {
    532 	    const ShapeTable shape_table [] = {
    533 		// Ka
    534 		{ { 0x0995, 0x0 },
    535 		  { 0x0073, 0x0 } },
    536 		// Ka Halant
    537 		{ { 0x0995, 0x09cd, 0x0 },
    538 		  { 0x00b9, 0x0 } },
    539 		// Ka Halant Ka
    540 		{ { 0x0995, 0x09cd, 0x0995, 0x0 },
    541 		  { 0x0109, 0x0 } },
    542 		// Ka MatraI
    543 		{ { 0x0995, 0x09bf, 0x0 },
    544 		  { 0x0095, 0x0073, 0x0 } },
    545 		// Ra Halant Ka
    546 		{ { 0x09b0, 0x09cd, 0x0995, 0x0 },
    547 		  { 0x0073, 0x00e1, 0x0 } },
    548 		// Ra Halant Ka MatraI
    549 		{ { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0x0 },
    550 		  { 0x0095, 0x0073, 0x00e1, 0x0 } },
    551 		// MatraI
    552  		{ { 0x09bf, 0x0 },
    553 		  { 0x0095, 0x01c8, 0x0 } },
    554 		// Ka Nukta
    555 		{ { 0x0995, 0x09bc, 0x0 },
    556 		  { 0x0073, 0x0093, 0x0 } },
    557 		// Ka Halant Ra
    558 		{ { 0x0995, 0x09cd, 0x09b0, 0x0 },
    559 		  { 0x00e5, 0x0 } },
    560 		// Ka Halant Ra Halant Ka
    561                 { { 0x995, 0x9cd, 0x9b0, 0x9cd, 0x995, 0x0 },
    562                   { 0x234, 0x24e, 0x73, 0x0 } },
    563 		// Ya + Halant
    564 		{ { 0x09af, 0x09cd, 0x0 },
    565 		  { 0x00d2, 0x0 } },
    566 		// Da Halant Ya -> Da Ya-Phala
    567 		{ { 0x09a6, 0x09cd, 0x09af, 0x0 },
    568 		  { 0x0084, 0x00e2, 0x0 } },
    569 		// A Halant Ya -> A Ya-phala
    570 		{ { 0x0985, 0x09cd, 0x09af, 0x0 },
    571 		  { 0x0067, 0x00e2, 0x0 } },
    572 		// Na Halant Ka
    573 		{ { 0x09a8, 0x09cd, 0x0995, 0x0 },
    574 		  { 0x0188, 0x0 } },
    575 		// Na Halant ZWNJ Ka
    576                 { { 0x9a8, 0x9cd, 0x200c, 0x995, 0x0 },
    577                   { 0xcc, 0x73, 0x0 } },
    578 		// Na Halant ZWJ Ka
    579                 { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 },
    580                   { 0x247, 0x73, 0x0 } },
    581 		// Ka Halant ZWNJ Ka
    582                 { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 },
    583                   { 0x247, 0x73, 0x0 } },
    584 		// Ka Halant ZWJ Ka
    585                 { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 },
    586                   { 0x247, 0x73, 0x0 } },
    587 		// Na Halant Ra
    588 		{ { 0x09a8, 0x09cd, 0x09b0, 0x0 },
    589 		  { 0x00f8, 0x0 } },
    590 		// Na Halant ZWNJ Ra
    591 		{ { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0x0 },
    592 		  { 0xcc, 0x8d, 0x0 } },
    593 		// Na Halant ZWJ Ra
    594                 { { 0x9a8, 0x9cd, 0x200d, 0x9b0, 0x0 },
    595                   { 0x247, 0x8d, 0x0 } },
    596 		// Na Halant Ba
    597 		{ { 0x09a8, 0x09cd, 0x09ac, 0x0 },
    598 		  { 0x0139, 0x0 } },
    599 		// Na Halant ZWNJ Ba
    600                 { { 0x9a8, 0x9cd, 0x200c, 0x9ac, 0x0 },
    601                   { 0xcc, 0x89, 0x0 } },
    602 		// Na Halant ZWJ Ba
    603                 { { 0x9a8, 0x9cd, 0x200d, 0x9ac, 0x0 },
    604                   { 0x247, 0x89, 0x0 } },
    605 		// Na Halant Dha
    606 		{ { 0x09a8, 0x09cd, 0x09a7, 0x0 },
    607 		  { 0x0145, 0x0 } },
    608 		// Na Halant ZWNJ Dha
    609 		{ { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0x0 },
    610 		  { 0xcc, 0x85, 0x0 } },
    611 		// Na Halant ZWJ Dha
    612 		{ { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0x0 },
    613 		  { 0x247, 0x85, 0x0 } },
    614 		// Ra Halant Ka MatraAU
    615                 { { 0x9b0, 0x9cd, 0x995, 0x9cc, 0x0 },
    616                   { 0x232, 0x73, 0xe1, 0xa0, 0x0 } },
    617 		// Ra Halant Ba Halant Ba
    618 		{ { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0x0 },
    619 		  { 0x013b, 0x00e1, 0x0 } },
    620 
    621 		{ {0}, {0} }
    622 	    };
    623 
    624 
    625 	    const ShapeTable *s = shape_table;
    626 	    while (s->unicode[0]) {
    627 		QVERIFY( shaping(face, s, HB_Script_Bengali) );
    628 		++s;
    629 	    }
    630 
    631             FT_Done_Face(face);
    632 	} else {
    633 	    QSKIP("couln't find MuktiNarrow.ttf", SkipAll);
    634 	}
    635     }
    636     {
    637         FT_Face face = loadFace("LikhanNormal.ttf");
    638         if (face) {
    639 	    const ShapeTable shape_table [] = {
    640 		{ { 0x09a8, 0x09cd, 0x09af, 0x0 },
    641 		  { 0x0192, 0x0 } },
    642 		{ { 0x09b8, 0x09cd, 0x09af, 0x0 },
    643 		  { 0x01d6, 0x0 } },
    644 		{ { 0x09b6, 0x09cd, 0x09af, 0x0 },
    645 		  { 0x01bc, 0x0 } },
    646 		{ { 0x09b7, 0x09cd, 0x09af, 0x0 },
    647 		  { 0x01c6, 0x0 } },
    648 		{ { 0x09b0, 0x09cd, 0x09a8, 0x09cd, 0x200d, 0x0 },
    649 		  { 0xd3, 0x12f, 0x0 } },
    650 
    651 		{ {0}, {0} }
    652 	    };
    653 
    654 
    655 	    const ShapeTable *s = shape_table;
    656 	    while (s->unicode[0]) {
    657 		QVERIFY( shaping(face, s, HB_Script_Bengali) );
    658 		++s;
    659 	    }
    660 
    661             FT_Done_Face(face);
    662 	} else {
    663 	    QSKIP("couln't find LikhanNormal.ttf", SkipAll);
    664 	}
    665     }
    666 }
    667 
    668 void tst_QScriptEngine::gurmukhi()
    669 {
    670     {
    671         FT_Face face = loadFace("lohit.punjabi.1.1.ttf");
    672         if (face) {
    673 	    const ShapeTable shape_table [] = {
    674 		{ { 0xA15, 0xA4D, 0xa39, 0x0 },
    675 		  { 0x3b, 0x8b, 0x0 } },
    676 		{ {0}, {0} }
    677 	    };
    678 
    679 
    680 	    const ShapeTable *s = shape_table;
    681 	    while (s->unicode[0]) {
    682 		QVERIFY( shaping(face, s, HB_Script_Gurmukhi) );
    683 		++s;
    684 	    }
    685 
    686             FT_Done_Face(face);
    687 	} else {
    688 	    QSKIP("couln't find lohit.punjabi.1.1.ttf", SkipAll);
    689 	}
    690     }
    691 }
    692 
    693 void tst_QScriptEngine::oriya()
    694 {
    695     {
    696         FT_Face face = loadFace("utkalm.ttf");
    697         if (face) {
    698 	    const ShapeTable shape_table [] = {
    699                 { { 0xb15, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 },
    700                   { 0x150, 0x125, 0x0 } },
    701                 { { 0xb24, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 },
    702                   { 0x151, 0x120, 0x0 } },
    703                 { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 },
    704                   { 0x152, 0x120, 0x0 } },
    705                 { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 },
    706                   { 0x152, 0x120, 0x0 } },
    707                 { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 },
    708                   { 0x176, 0x0 } },
    709                 { { 0xb38, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 },
    710                   { 0x177, 0x0 } },
    711                 { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0xb4d, 0xb2f, 0x0 },
    712                   { 0x176, 0x124, 0x0 } },
    713                 { {0}, {0} }
    714 
    715             };
    716 
    717 	    const ShapeTable *s = shape_table;
    718 	    while (s->unicode[0]) {
    719 		QVERIFY( shaping(face, s, HB_Script_Oriya) );
    720 		++s;
    721 	    }
    722 
    723             FT_Done_Face(face);
    724 	} else {
    725 	    QSKIP("couln't find utkalm.ttf", SkipAll);
    726 	}
    727     }
    728 }
    729 
    730 
    731 void tst_QScriptEngine::tamil()
    732 {
    733     {
    734         FT_Face face = loadFace("akruti1.ttf");
    735         if (face) {
    736 	    const ShapeTable shape_table [] = {
    737 		{ { 0x0b95, 0x0bc2, 0x0 },
    738 		  { 0x004e, 0x0 } },
    739 		{ { 0x0bae, 0x0bc2, 0x0 },
    740 		  { 0x009e, 0x0 } },
    741 		{ { 0x0b9a, 0x0bc2, 0x0 },
    742 		  { 0x0058, 0x0 } },
    743 		{ { 0x0b99, 0x0bc2, 0x0 },
    744 		  { 0x0053, 0x0 } },
    745 		{ { 0x0bb0, 0x0bc2, 0x0 },
    746 		  { 0x00a8, 0x0 } },
    747 		{ { 0x0ba4, 0x0bc2, 0x0 },
    748 		  { 0x008e, 0x0 } },
    749 		{ { 0x0b9f, 0x0bc2, 0x0 },
    750 		  { 0x0062, 0x0 } },
    751 		{ { 0x0b95, 0x0bc6, 0x0 },
    752 		  { 0x000a, 0x0031, 0x0 } },
    753 		{ { 0x0b95, 0x0bca, 0x0 },
    754 		  { 0x000a, 0x0031, 0x0007, 0x0 } },
    755 		{ { 0x0b95, 0x0bc6, 0x0bbe, 0x0 },
    756 		  { 0x000a, 0x0031, 0x007, 0x0 } },
    757 		{ { 0x0b95, 0x0bcd, 0x0bb7, 0x0 },
    758 		  { 0x0049, 0x0 } },
    759 		{ { 0x0b95, 0x0bcd, 0x0bb7, 0x0bca, 0x0 },
    760 		  { 0x000a, 0x0049, 0x007, 0x0 } },
    761 		{ { 0x0b95, 0x0bcd, 0x0bb7, 0x0bc6, 0x0bbe, 0x0 },
    762 		  { 0x000a, 0x0049, 0x007, 0x0 } },
    763 		{ { 0x0b9f, 0x0bbf, 0x0 },
    764 		  { 0x005f, 0x0 } },
    765 		{ { 0x0b9f, 0x0bc0, 0x0 },
    766 		  { 0x0060, 0x0 } },
    767 		{ { 0x0bb2, 0x0bc0, 0x0 },
    768 		  { 0x00ab, 0x0 } },
    769 		{ { 0x0bb2, 0x0bbf, 0x0 },
    770 		  { 0x00aa, 0x0 } },
    771 		{ { 0x0bb0, 0x0bcd, 0x0 },
    772 		  { 0x00a4, 0x0 } },
    773 		{ { 0x0bb0, 0x0bbf, 0x0 },
    774 		  { 0x00a5, 0x0 } },
    775 		{ { 0x0bb0, 0x0bc0, 0x0 },
    776 		  { 0x00a6, 0x0 } },
    777 		{ { 0x0b83, 0x0 },
    778 		  { 0x0025, 0x0 } },
    779 		{ { 0x0b83, 0x0b95, 0x0 },
    780 		  { 0x0025, 0x0031, 0x0 } },
    781 
    782 		{ {0}, {0} }
    783 	    };
    784 
    785 
    786 	    const ShapeTable *s = shape_table;
    787 	    while (s->unicode[0]) {
    788 		QVERIFY( shaping(face, s, HB_Script_Tamil) );
    789 		++s;
    790 	    }
    791 
    792             FT_Done_Face(face);
    793 	} else {
    794 	    QSKIP("couln't find akruti1.ttf", SkipAll);
    795 	}
    796     }
    797 }
    798 
    799 
    800 void tst_QScriptEngine::telugu()
    801 {
    802     {
    803         FT_Face face = loadFace("Pothana2000.ttf");
    804         if (face) {
    805 	    const ShapeTable shape_table [] = {
    806                 { { 0xc15, 0xc4d, 0x0 },
    807                   { 0xbb, 0x0 } },
    808                 { { 0xc15, 0xc4d, 0xc37, 0x0 },
    809                   { 0x4b, 0x0 } },
    810                 { { 0xc15, 0xc4d, 0xc37, 0xc4d, 0x0 },
    811                   { 0xe0, 0x0 } },
    812                 { { 0xc15, 0xc4d, 0xc37, 0xc4d, 0xc23, 0x0 },
    813                   { 0x4b, 0x91, 0x0 } },
    814                 { { 0xc15, 0xc4d, 0xc30, 0x0 },
    815                   { 0x5a, 0xb2, 0x0 } },
    816                 { { 0xc15, 0xc4d, 0xc30, 0xc4d, 0x0 },
    817                   { 0xbb, 0xb2, 0x0 } },
    818                 { { 0xc15, 0xc4d, 0xc30, 0xc4d, 0xc15, 0x0 },
    819                   { 0x5a, 0xb2, 0x83, 0x0 } },
    820                 { { 0xc15, 0xc4d, 0xc30, 0xc3f, 0x0 },
    821                   { 0xe2, 0xb2, 0x0 } },
    822                 { { 0xc15, 0xc4d, 0xc15, 0xc48, 0x0 },
    823                   { 0xe6, 0xb3, 0x83, 0x0 } },
    824                 { { 0xc15, 0xc4d, 0xc30, 0xc48, 0x0 },
    825                   { 0xe6, 0xb3, 0x9f, 0x0 } },
    826 		{ {0}, {0} }
    827 
    828             };
    829 
    830 	    const ShapeTable *s = shape_table;
    831 	    while (s->unicode[0]) {
    832 		QVERIFY( shaping(face, s, HB_Script_Telugu) );
    833 		++s;
    834 	    }
    835 
    836             FT_Done_Face(face);
    837 	} else {
    838 	    QSKIP("couln't find Pothana2000.ttf", SkipAll);
    839 	}
    840     }
    841 }
    842 
    843 
    844 void tst_QScriptEngine::kannada()
    845 {
    846     {
    847         FT_Face face = loadFace("Sampige.ttf");
    848         if (face) {
    849 	    const ShapeTable shape_table [] = {
    850 		{ { 0x0ca8, 0x0ccd, 0x0ca8, 0x0 },
    851 		  { 0x0049, 0x00ba, 0x0 } },
    852 		{ { 0x0ca8, 0x0ccd, 0x0ca1, 0x0 },
    853 		  { 0x0049, 0x00b3, 0x0 } },
    854 		{ { 0x0caf, 0x0cc2, 0x0 },
    855 		  { 0x004f, 0x005d, 0x0 } },
    856 		{ { 0x0ce0, 0x0 },
    857 		  { 0x006a, 0x0 } },
    858 		{ { 0x0ce6, 0x0ce7, 0x0ce8, 0x0 },
    859 		  { 0x006b, 0x006c, 0x006d, 0x0 } },
    860 		{ { 0x0cb5, 0x0ccb, 0x0 },
    861 		  { 0x015f, 0x0067, 0x0 } },
    862 		{ { 0x0cb0, 0x0ccd, 0x0cae, 0x0 },
    863 		  { 0x004e, 0x0082, 0x0 } },
    864 		{ { 0x0cb0, 0x0ccd, 0x0c95, 0x0 },
    865 		  { 0x0036, 0x0082, 0x0 } },
    866 		{ { 0x0c95, 0x0ccd, 0x0cb0, 0x0 },
    867 		  { 0x0036, 0x00c1, 0x0 } },
    868 		{ { 0x0cb0, 0x0ccd, 0x200d, 0x0c95, 0x0 },
    869 		  { 0x0050, 0x00a7, 0x0 } },
    870 
    871 		{ {0}, {0} }
    872 	    };
    873 
    874 
    875 	    const ShapeTable *s = shape_table;
    876 	    while (s->unicode[0]) {
    877 		QVERIFY( shaping(face, s, HB_Script_Kannada) );
    878 		++s;
    879 	    }
    880 
    881             FT_Done_Face(face);
    882 	} else {
    883 	    QSKIP("couln't find Sampige.ttf", SkipAll);
    884 	}
    885     }
    886     {
    887         FT_Face face = loadFace("tunga.ttf");
    888         if (face) {
    889 	    const ShapeTable shape_table [] = {
    890 		{ { 0x0cb7, 0x0cc6, 0x0 },
    891 		  { 0x00b0, 0x006c, 0x0 } },
    892 		{ { 0x0cb7, 0x0ccd, 0x0 },
    893 		  { 0x0163, 0x0 } },
    894 
    895 		{ {0}, {0} }
    896 	    };
    897 
    898 
    899 	    const ShapeTable *s = shape_table;
    900 	    while (s->unicode[0]) {
    901 		QVERIFY( shaping(face, s, HB_Script_Kannada) );
    902 		++s;
    903 	    }
    904 
    905             FT_Done_Face(face);
    906 	} else {
    907 	    QSKIP("couln't find tunga.ttf", SkipAll);
    908 	}
    909     }
    910 }
    911 
    912 
    913 
    914 void tst_QScriptEngine::malayalam()
    915 {
    916     {
    917         FT_Face face = loadFace("AkrutiMal2Normal.ttf");
    918         if (face) {
    919 	    const ShapeTable shape_table [] = {
    920 		{ { 0x0d15, 0x0d46, 0x0 },
    921 		  { 0x005e, 0x0034, 0x0 } },
    922 		{ { 0x0d15, 0x0d47, 0x0 },
    923 		  { 0x005f, 0x0034, 0x0 } },
    924 		{ { 0x0d15, 0x0d4b, 0x0 },
    925 		  { 0x005f, 0x0034, 0x0058, 0x0 } },
    926 		{ { 0x0d15, 0x0d48, 0x0 },
    927 		  { 0x0060, 0x0034, 0x0 } },
    928 		{ { 0x0d15, 0x0d4a, 0x0 },
    929 		  { 0x005e, 0x0034, 0x0058, 0x0 } },
    930 		{ { 0x0d30, 0x0d4d, 0x0d15, 0x0 },
    931 		  { 0x009e, 0x0034, 0x0 } },
    932 		{ { 0x0d15, 0x0d4d, 0x0d35, 0x0 },
    933 		  { 0x0034, 0x007a, 0x0 } },
    934 		{ { 0x0d15, 0x0d4d, 0x0d2f, 0x0 },
    935 		  { 0x0034, 0x00a2, 0x0 } },
    936 		{ { 0x0d1f, 0x0d4d, 0x0d1f, 0x0 },
    937 		  { 0x0069, 0x0 } },
    938 		{ { 0x0d26, 0x0d4d, 0x0d26, 0x0 },
    939 		  { 0x0074, 0x0 } },
    940 		{ { 0x0d30, 0x0d4d, 0x0 },
    941 		  { 0x009e, 0x0 } },
    942 		{ { 0x0d30, 0x0d4d, 0x200c, 0x0 },
    943 		  { 0x009e, 0x0 } },
    944 		{ { 0x0d30, 0x0d4d, 0x200d, 0x0 },
    945 		  { 0x009e, 0x0 } },
    946 
    947 
    948 		{ {0}, {0} }
    949 	    };
    950 
    951 
    952 	    const ShapeTable *s = shape_table;
    953 	    while (s->unicode[0]) {
    954 		QVERIFY( shaping(face, s, HB_Script_Malayalam) );
    955 		++s;
    956 	    }
    957 
    958             FT_Done_Face(face);
    959 	} else {
    960 	    QSKIP("couln't find AkrutiMal2Normal.ttf", SkipAll);
    961 	}
    962     }
    963 }
    964 
    965 
    966 
    967 void tst_QScriptEngine::khmer()
    968 {
    969     {
    970         FT_Face face = loadFace("KhmerOS.ttf");
    971         if (face) {
    972 	    const ShapeTable shape_table [] = {
    973 		{ { 0x179a, 0x17cd, 0x0 },
    974 		  { 0x24c, 0x27f, 0x0 } },
    975 		{ { 0x179f, 0x17c5, 0x0 },
    976 		  { 0x273, 0x203, 0x0 } },
    977 		{ { 0x1790, 0x17d2, 0x1784, 0x17c3, 0x0 },
    978 		  { 0x275, 0x242, 0x182, 0x0 } },
    979 		{ { 0x179a, 0x0 },
    980 		  { 0x24c, 0x0 } },
    981 		{ { 0x1781, 0x17d2, 0x1798, 0x17c2, 0x0 },
    982 		  { 0x274, 0x233, 0x197, 0x0 } },
    983 		{ { 0x1798, 0x17b6, 0x0 },
    984 		  { 0x1cb, 0x0 } },
    985 		{ { 0x179a, 0x17b8, 0x0 },
    986 		  { 0x24c, 0x26a, 0x0 } },
    987 		{ { 0x1787, 0x17b6, 0x0 },
    988 		  { 0x1ba, 0x0 } },
    989 		{ { 0x1798, 0x17d2, 0x1796, 0x17bb, 0x0 },
    990 		  { 0x24a, 0x195, 0x26d, 0x0 } },
    991 		{ {0}, {0} }
    992 	    };
    993 
    994 
    995 	    const ShapeTable *s = shape_table;
    996 	    while (s->unicode[0]) {
    997 		QVERIFY( shaping(face, s, HB_Script_Khmer) );
    998 		++s;
    999 	    }
   1000 
   1001             FT_Done_Face(face);
   1002 	} else {
   1003 	    QSKIP("couln't find KhmerOS.ttf", SkipAll);
   1004 	}
   1005     }
   1006 }
   1007 
   1008 void tst_QScriptEngine::linearB()
   1009 {
   1010     {
   1011         FT_Face face = loadFace("PENUTURE.TTF");
   1012         if (face) {
   1013 	    const ShapeTable shape_table [] = {
   1014 		{ { 0xd800, 0xdc01, 0xd800, 0xdc02, 0xd800, 0xdc03,  0 },
   1015                   { 0x5, 0x6, 0x7, 0 } },
   1016 		{ {0}, {0} }
   1017 	    };
   1018 
   1019 
   1020 	    const ShapeTable *s = shape_table;
   1021 	    while (s->unicode[0]) {
   1022 		QVERIFY( shaping(face, s, HB_Script_Common) );
   1023 		++s;
   1024 	    }
   1025 
   1026             FT_Done_Face(face);
   1027 	} else {
   1028 	    QSKIP("couln't find PENUTURE.TTF", SkipAll);
   1029 	}
   1030     }
   1031 }
   1032 
   1033 
   1034 QTEST_MAIN(tst_QScriptEngine)
   1035 #include "main.moc"
   1036