1
2/*
3 *
4 * (C) Copyright IBM Corp. and others 1998-2013 - All Rights Reserved
5 *
6 */
7
8#include "LETypes.h"
9#include "LEScripts.h"
10#include "LELanguages.h"
11
12#include "LayoutEngine.h"
13#include "CanonShaping.h"
14#include "OpenTypeLayoutEngine.h"
15#include "ScriptAndLanguageTags.h"
16#include "CharSubstitutionFilter.h"
17
18#include "GlyphSubstitutionTables.h"
19#include "GlyphDefinitionTables.h"
20#include "GlyphPositioningTables.h"
21
22#include "LEGlyphStorage.h"
23#include "GlyphPositionAdjustments.h"
24
25#include "GDEFMarkFilter.h"
26
27#include "KernTable.h"
28
29U_NAMESPACE_BEGIN
30
31UOBJECT_DEFINE_RTTI_IMPLEMENTATION(OpenTypeLayoutEngine)
32
33#define ccmpFeatureTag LE_CCMP_FEATURE_TAG
34#define ligaFeatureTag LE_LIGA_FEATURE_TAG
35#define cligFeatureTag LE_CLIG_FEATURE_TAG
36#define kernFeatureTag LE_KERN_FEATURE_TAG
37#define markFeatureTag LE_MARK_FEATURE_TAG
38#define mkmkFeatureTag LE_MKMK_FEATURE_TAG
39#define loclFeatureTag LE_LOCL_FEATURE_TAG
40#define caltFeatureTag LE_CALT_FEATURE_TAG
41
42#define dligFeatureTag LE_DLIG_FEATURE_TAG
43#define rligFeatureTag LE_RLIG_FEATURE_TAG
44#define paltFeatureTag LE_PALT_FEATURE_TAG
45
46#define hligFeatureTag LE_HLIG_FEATURE_TAG
47#define smcpFeatureTag LE_SMCP_FEATURE_TAG
48#define fracFeatureTag LE_FRAC_FEATURE_TAG
49#define afrcFeatureTag LE_AFRC_FEATURE_TAG
50#define zeroFeatureTag LE_ZERO_FEATURE_TAG
51#define swshFeatureTag LE_SWSH_FEATURE_TAG
52#define cswhFeatureTag LE_CSWH_FEATURE_TAG
53#define saltFeatureTag LE_SALT_FEATURE_TAG
54#define naltFeatureTag LE_NALT_FEATURE_TAG
55#define rubyFeatureTag LE_RUBY_FEATURE_TAG
56#define ss01FeatureTag LE_SS01_FEATURE_TAG
57#define ss02FeatureTag LE_SS02_FEATURE_TAG
58#define ss03FeatureTag LE_SS03_FEATURE_TAG
59#define ss04FeatureTag LE_SS04_FEATURE_TAG
60#define ss05FeatureTag LE_SS05_FEATURE_TAG
61#define ss06FeatureTag LE_SS06_FEATURE_TAG
62#define ss07FeatureTag LE_SS07_FEATURE_TAG
63
64#define ccmpFeatureMask 0x80000000UL
65#define ligaFeatureMask 0x40000000UL
66#define cligFeatureMask 0x20000000UL
67#define kernFeatureMask 0x10000000UL
68#define paltFeatureMask 0x08000000UL
69#define markFeatureMask 0x04000000UL
70#define mkmkFeatureMask 0x02000000UL
71#define loclFeatureMask 0x01000000UL
72#define caltFeatureMask 0x00800000UL
73
74#define dligFeatureMask 0x00400000UL
75#define rligFeatureMask 0x00200000UL
76#define hligFeatureMask 0x00100000UL
77#define smcpFeatureMask 0x00080000UL
78#define fracFeatureMask 0x00040000UL
79#define afrcFeatureMask 0x00020000UL
80#define zeroFeatureMask 0x00010000UL
81#define swshFeatureMask 0x00008000UL
82#define cswhFeatureMask 0x00004000UL
83#define saltFeatureMask 0x00002000UL
84#define naltFeatureMask 0x00001000UL
85#define rubyFeatureMask 0x00000800UL
86#define ss01FeatureMask 0x00000400UL
87#define ss02FeatureMask 0x00000200UL
88#define ss03FeatureMask 0x00000100UL
89#define ss04FeatureMask 0x00000080UL
90#define ss05FeatureMask 0x00000040UL
91#define ss06FeatureMask 0x00000020UL
92#define ss07FeatureMask 0x00000010UL
93
94#define minimalFeatures     (ccmpFeatureMask | markFeatureMask | mkmkFeatureMask | loclFeatureMask | caltFeatureMask)
95
96static const FeatureMap featureMap[] =
97{
98    {ccmpFeatureTag, ccmpFeatureMask},
99    {ligaFeatureTag, ligaFeatureMask},
100    {cligFeatureTag, cligFeatureMask},
101    {kernFeatureTag, kernFeatureMask},
102    {paltFeatureTag, paltFeatureMask},
103    {markFeatureTag, markFeatureMask},
104    {mkmkFeatureTag, mkmkFeatureMask},
105    {loclFeatureTag, loclFeatureMask},
106    {caltFeatureTag, caltFeatureMask},
107    {hligFeatureTag, hligFeatureMask},
108    {smcpFeatureTag, smcpFeatureMask},
109    {fracFeatureTag, fracFeatureMask},
110    {afrcFeatureTag, afrcFeatureMask},
111    {zeroFeatureTag, zeroFeatureMask},
112    {swshFeatureTag, swshFeatureMask},
113    {cswhFeatureTag, cswhFeatureMask},
114    {saltFeatureTag, saltFeatureMask},
115    {naltFeatureTag, naltFeatureMask},
116    {rubyFeatureTag, rubyFeatureMask},
117    {ss01FeatureTag, ss01FeatureMask},
118    {ss02FeatureTag, ss02FeatureMask},
119    {ss03FeatureTag, ss03FeatureMask},
120    {ss04FeatureTag, ss04FeatureMask},
121    {ss05FeatureTag, ss05FeatureMask},
122    {ss06FeatureTag, ss06FeatureMask},
123    {ss07FeatureTag, ss07FeatureMask}
124};
125
126static const le_int32 featureMapCount = LE_ARRAY_SIZE(featureMap);
127
128OpenTypeLayoutEngine::OpenTypeLayoutEngine(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode,
129                        le_int32 typoFlags, const GlyphSubstitutionTableHeader *gsubTable, LEErrorCode &success)
130    : LayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success), fFeatureMask(minimalFeatures),
131      fFeatureMap(featureMap), fFeatureMapCount(featureMapCount), fFeatureOrder(FALSE),
132      fGSUBTable(gsubTable), fGDEFTable(NULL), fGPOSTable(NULL), fSubstitutionFilter(NULL)
133{
134    static const le_uint32 gdefTableTag = LE_GDEF_TABLE_TAG;
135    static const le_uint32 gposTableTag = LE_GPOS_TABLE_TAG;
136    const GlyphPositioningTableHeader *gposTable = (const GlyphPositioningTableHeader *) getFontTable(gposTableTag);
137
138    switch (typoFlags & (LE_SS01_FEATURE_FLAG
139                         | LE_SS02_FEATURE_FLAG
140                         | LE_SS03_FEATURE_FLAG
141                         | LE_SS04_FEATURE_FLAG
142                         | LE_SS05_FEATURE_FLAG
143                         | LE_SS06_FEATURE_FLAG
144                         | LE_SS07_FEATURE_FLAG)) {
145        case LE_SS01_FEATURE_FLAG:
146            fFeatureMask |= ss01FeatureMask;
147            break;
148        case LE_SS02_FEATURE_FLAG:
149            fFeatureMask |= ss02FeatureMask;
150            break;
151        case LE_SS03_FEATURE_FLAG:
152            fFeatureMask |= ss03FeatureMask;
153            break;
154        case LE_SS04_FEATURE_FLAG:
155            fFeatureMask |= ss04FeatureMask;
156            break;
157        case LE_SS05_FEATURE_FLAG:
158            fFeatureMask |= ss05FeatureMask;
159            break;
160        case LE_SS06_FEATURE_FLAG:
161            fFeatureMask |= ss06FeatureMask;
162            break;
163        case LE_SS07_FEATURE_FLAG:
164            fFeatureMask |= ss07FeatureMask;
165            break;
166    }
167
168    if (typoFlags & LE_Kerning_FEATURE_FLAG) {
169      fFeatureMask |= (kernFeatureMask | paltFeatureMask);
170      // Convenience.
171    }
172    if (typoFlags & LE_Ligatures_FEATURE_FLAG) {
173      fFeatureMask |= (ligaFeatureMask | cligFeatureMask);
174      // Convenience TODO: should add: .. dligFeatureMask | rligFeatureMask ?
175    }
176    if (typoFlags & LE_CLIG_FEATURE_FLAG) fFeatureMask |= cligFeatureMask;
177    if (typoFlags & LE_DLIG_FEATURE_FLAG) fFeatureMask |= dligFeatureMask;
178    if (typoFlags & LE_HLIG_FEATURE_FLAG) fFeatureMask |= hligFeatureMask;
179    if (typoFlags & LE_LIGA_FEATURE_FLAG) fFeatureMask |= ligaFeatureMask;
180    if (typoFlags & LE_RLIG_FEATURE_FLAG) fFeatureMask |= rligFeatureMask;
181    if (typoFlags & LE_SMCP_FEATURE_FLAG) fFeatureMask |= smcpFeatureMask;
182    if (typoFlags & LE_FRAC_FEATURE_FLAG) fFeatureMask |= fracFeatureMask;
183    if (typoFlags & LE_AFRC_FEATURE_FLAG) fFeatureMask |= afrcFeatureMask;
184    if (typoFlags & LE_ZERO_FEATURE_FLAG) fFeatureMask |= zeroFeatureMask;
185    if (typoFlags & LE_SWSH_FEATURE_FLAG) fFeatureMask |= swshFeatureMask;
186    if (typoFlags & LE_CSWH_FEATURE_FLAG) fFeatureMask |= cswhFeatureMask;
187    if (typoFlags & LE_SALT_FEATURE_FLAG) fFeatureMask |= saltFeatureMask;
188    if (typoFlags & LE_RUBY_FEATURE_FLAG) fFeatureMask |= rubyFeatureMask;
189    if (typoFlags & LE_NALT_FEATURE_FLAG) {
190      // Mutually exclusive with ALL other features. http://www.microsoft.com/typography/otspec/features_ko.htm
191      fFeatureMask = naltFeatureMask;
192    }
193
194    if (typoFlags & LE_CHAR_FILTER_FEATURE_FLAG) {
195      // This isn't a font feature, but requests a Char Substitution Filter
196      fSubstitutionFilter = new CharSubstitutionFilter(fontInstance);
197    }
198
199    setScriptAndLanguageTags();
200
201    fGDEFTable = (const GlyphDefinitionTableHeader *) getFontTable(gdefTableTag);
202
203// JK patch, 2008-05-30 - see Sinhala bug report and LKLUG font
204//    if (gposTable != NULL && gposTable->coversScriptAndLanguage(fScriptTag, fLangSysTag)) {
205    if (gposTable != NULL && gposTable->coversScript(fScriptTag)) {
206        fGPOSTable = gposTable;
207    }
208}
209
210void OpenTypeLayoutEngine::reset()
211{
212    // NOTE: if we're called from
213    // the destructor, LayoutEngine;:reset()
214    // will have been called already by
215    // LayoutEngine::~LayoutEngine()
216    LayoutEngine::reset();
217}
218
219OpenTypeLayoutEngine::OpenTypeLayoutEngine(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode,
220                       le_int32 typoFlags, LEErrorCode &success)
221    : LayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success), fFeatureOrder(FALSE),
222      fGSUBTable(NULL), fGDEFTable(NULL), fGPOSTable(NULL), fSubstitutionFilter(NULL)
223{
224    setScriptAndLanguageTags();
225}
226
227OpenTypeLayoutEngine::~OpenTypeLayoutEngine()
228{
229    if (fTypoFlags & 0x80000000L) {
230        delete fSubstitutionFilter;
231    }
232
233    reset();
234}
235
236LETag OpenTypeLayoutEngine::getScriptTag(le_int32 scriptCode)
237{
238    if (scriptCode < 0 || scriptCode >= scriptCodeCount) {
239        return 0xFFFFFFFF;
240    }
241    return scriptTags[scriptCode];
242}
243
244LETag OpenTypeLayoutEngine::getV2ScriptTag(le_int32 scriptCode)
245{
246	switch (scriptCode) {
247		case bengScriptCode :    return bng2ScriptTag;
248		case devaScriptCode :    return dev2ScriptTag;
249		case gujrScriptCode :    return gjr2ScriptTag;
250		case guruScriptCode :    return gur2ScriptTag;
251		case kndaScriptCode :    return knd2ScriptTag;
252		case mlymScriptCode :    return mlm2ScriptTag;
253		case oryaScriptCode :    return ory2ScriptTag;
254		case tamlScriptCode :    return tml2ScriptTag;
255		case teluScriptCode :    return tel2ScriptTag;
256		default:                 return nullScriptTag;
257	}
258}
259
260LETag OpenTypeLayoutEngine::getLangSysTag(le_int32 languageCode)
261{
262    if (languageCode < 0 || languageCode >= languageCodeCount) {
263        return 0xFFFFFFFF;
264    }
265
266    return languageTags[languageCode];
267}
268
269void OpenTypeLayoutEngine::setScriptAndLanguageTags()
270{
271    fScriptTag  = getScriptTag(fScriptCode);
272    fScriptTagV2 = getV2ScriptTag(fScriptCode);
273    fLangSysTag = getLangSysTag(fLanguageCode);
274}
275
276le_int32 OpenTypeLayoutEngine::characterProcessing(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft,
277                LEUnicode *&outChars, LEGlyphStorage &glyphStorage, LEErrorCode &success)
278{
279    if (LE_FAILURE(success)) {
280        return 0;
281    }
282
283    if (offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) {
284        success = LE_ILLEGAL_ARGUMENT_ERROR;
285        return 0;
286    }
287
288    // This is the cheapest way to get mark reordering only for Hebrew.
289    // We could just do the mark reordering for all scripts, but most
290    // of them probably don't need it... Another option would be to
291    // add a HebrewOpenTypeLayoutEngine subclass, but the only thing it
292    // would need to do is mark reordering, so that seems like overkill.
293    if (fScriptCode == hebrScriptCode) {
294        outChars = LE_NEW_ARRAY(LEUnicode, count);
295
296        if (outChars == NULL) {
297            success = LE_MEMORY_ALLOCATION_ERROR;
298            return 0;
299        }
300
301        if (LE_FAILURE(success)) {
302            LE_DELETE_ARRAY(outChars);
303            return 0;
304        }
305
306        CanonShaping::reorderMarks(&chars[offset], count, rightToLeft, outChars, glyphStorage);
307    }
308
309    if (LE_FAILURE(success)) {
310        return 0;
311    }
312
313    glyphStorage.allocateGlyphArray(count, rightToLeft, success);
314    glyphStorage.allocateAuxData(success);
315
316    for (le_int32 i = 0; i < count; i += 1) {
317        glyphStorage.setAuxData(i, fFeatureMask, success);
318    }
319
320    return count;
321}
322
323// Input: characters, tags
324// Output: glyphs, char indices
325le_int32 OpenTypeLayoutEngine::glyphProcessing(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft,
326                                               LEGlyphStorage &glyphStorage, LEErrorCode &success)
327{
328    if (LE_FAILURE(success)) {
329        return 0;
330    }
331
332    if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) {
333        success = LE_ILLEGAL_ARGUMENT_ERROR;
334        return 0;
335    }
336
337    mapCharsToGlyphs(chars, offset, count, rightToLeft, rightToLeft, glyphStorage, success);
338
339    if (LE_FAILURE(success)) {
340        return 0;
341    }
342
343    if (fGSUBTable != NULL) {
344        if (fScriptTagV2 != nullScriptTag && fGSUBTable->coversScriptAndLanguage(fScriptTagV2,fLangSysTag)) {
345            count = fGSUBTable->process(glyphStorage, rightToLeft, fScriptTagV2, fLangSysTag, fGDEFTable, fSubstitutionFilter,
346                                    fFeatureMap, fFeatureMapCount, fFeatureOrder, success);
347
348        } else {
349        count = fGSUBTable->process(glyphStorage, rightToLeft, fScriptTag, fLangSysTag, fGDEFTable, fSubstitutionFilter,
350                                    fFeatureMap, fFeatureMapCount, fFeatureOrder, success);
351        }
352    }
353
354    return count;
355}
356// Input: characters, tags
357// Output: glyphs, char indices
358le_int32 OpenTypeLayoutEngine::glyphSubstitution(le_int32 count, le_int32 max, le_bool rightToLeft,
359                                               LEGlyphStorage &glyphStorage, LEErrorCode &success)
360{
361    if (LE_FAILURE(success)) {
362        return 0;
363    }
364
365    if ( count < 0 || max < 0 ) {
366        success = LE_ILLEGAL_ARGUMENT_ERROR;
367        return 0;
368    }
369
370    if (fGSUBTable != NULL) {
371        if (fScriptTagV2 != nullScriptTag && fGSUBTable->coversScriptAndLanguage(fScriptTagV2,fLangSysTag)) {
372            count = fGSUBTable->process(glyphStorage, rightToLeft, fScriptTagV2, fLangSysTag, fGDEFTable, fSubstitutionFilter,
373                                    fFeatureMap, fFeatureMapCount, fFeatureOrder, success);
374
375        } else {
376        count = fGSUBTable->process(glyphStorage, rightToLeft, fScriptTag, fLangSysTag, fGDEFTable, fSubstitutionFilter,
377                                    fFeatureMap, fFeatureMapCount, fFeatureOrder, success);
378        }
379    }
380
381    return count;
382}
383le_int32 OpenTypeLayoutEngine::glyphPostProcessing(LEGlyphStorage &tempGlyphStorage, LEGlyphStorage &glyphStorage, LEErrorCode &success)
384{
385    if (LE_FAILURE(success)) {
386        return 0;
387    }
388
389    glyphStorage.adoptGlyphArray(tempGlyphStorage);
390    glyphStorage.adoptCharIndicesArray(tempGlyphStorage);
391    glyphStorage.adoptAuxDataArray(tempGlyphStorage);
392    glyphStorage.adoptGlyphCount(tempGlyphStorage);
393
394    return glyphStorage.getGlyphCount();
395}
396
397le_int32 OpenTypeLayoutEngine::computeGlyphs(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, LEGlyphStorage &glyphStorage, LEErrorCode &success)
398{
399    LEUnicode *outChars = NULL;
400    LEGlyphStorage fakeGlyphStorage;
401    le_int32 outCharCount, outGlyphCount;
402
403    if (LE_FAILURE(success)) {
404        return 0;
405    }
406
407    if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) {
408        success = LE_ILLEGAL_ARGUMENT_ERROR;
409        return 0;
410    }
411
412    outCharCount = characterProcessing(chars, offset, count, max, rightToLeft, outChars, fakeGlyphStorage, success);
413
414    if (LE_FAILURE(success)) {
415        return 0;
416    }
417
418    if (outChars != NULL) {
419        // le_int32 fakeGlyphCount =
420        glyphProcessing(outChars, 0, outCharCount, outCharCount, rightToLeft, fakeGlyphStorage, success);
421        LE_DELETE_ARRAY(outChars); // FIXME: a subclass may have allocated this, in which case this delete might not work...
422        //adjustGlyphs(outChars, 0, outCharCount, rightToLeft, fakeGlyphs, fakeGlyphCount);
423    } else {
424        // le_int32 fakeGlyphCount =
425        glyphProcessing(chars, offset, count, max, rightToLeft, fakeGlyphStorage, success);
426        //adjustGlyphs(chars, offset, count, rightToLeft, fakeGlyphs, fakeGlyphCount);
427    }
428
429    if (LE_FAILURE(success)) {
430        return 0;
431    }
432
433    outGlyphCount = glyphPostProcessing(fakeGlyphStorage, glyphStorage, success);
434
435    return outGlyphCount;
436}
437
438// apply GPOS table, if any
439void OpenTypeLayoutEngine::adjustGlyphPositions(const LEUnicode chars[], le_int32 offset, le_int32 count, le_bool reverse,
440                                                LEGlyphStorage &glyphStorage, LEErrorCode &success)
441{
442    if (LE_FAILURE(success)) {
443        return;
444    }
445
446    if (chars == NULL || offset < 0 || count < 0) {
447        success = LE_ILLEGAL_ARGUMENT_ERROR;
448        return;
449    }
450
451    le_int32 glyphCount = glyphStorage.getGlyphCount();
452    if (glyphCount == 0) {
453        return;
454    }
455
456    if (fGPOSTable != NULL) {
457        GlyphPositionAdjustments *adjustments = new GlyphPositionAdjustments(glyphCount);
458        le_int32 i;
459
460        if (adjustments == NULL) {
461            success = LE_MEMORY_ALLOCATION_ERROR;
462            return;
463        }
464
465#if 0
466        // Don't need to do this if we allocate
467        // the adjustments array w/ new...
468        for (i = 0; i < glyphCount; i += 1) {
469            adjustments->setXPlacement(i, 0);
470            adjustments->setYPlacement(i, 0);
471
472            adjustments->setXAdvance(i, 0);
473            adjustments->setYAdvance(i, 0);
474
475            adjustments->setBaseOffset(i, -1);
476        }
477#endif
478
479        if (fGPOSTable != NULL) {
480            if (fScriptTagV2 != nullScriptTag && fGPOSTable->coversScriptAndLanguage(fScriptTagV2,fLangSysTag)) {
481                fGPOSTable->process(glyphStorage, adjustments, reverse, fScriptTagV2, fLangSysTag, fGDEFTable, success, fFontInstance,
482                                fFeatureMap, fFeatureMapCount, fFeatureOrder);
483
484            } else {
485                fGPOSTable->process(glyphStorage, adjustments, reverse, fScriptTag, fLangSysTag, fGDEFTable, success, fFontInstance,
486                                fFeatureMap, fFeatureMapCount, fFeatureOrder);
487            }
488        } else if ( fTypoFlags & 0x1 ) {
489            static const le_uint32 kernTableTag = LE_KERN_TABLE_TAG;
490            KernTable kt(fFontInstance, getFontTable(kernTableTag));
491            kt.process(glyphStorage);
492        }
493
494        float xAdjust = 0, yAdjust = 0;
495
496        for (i = 0; i < glyphCount; i += 1) {
497            float xAdvance   = adjustments->getXAdvance(i);
498            float yAdvance   = adjustments->getYAdvance(i);
499            float xPlacement = 0;
500            float yPlacement = 0;
501
502
503#if 0
504            // This is where separate kerning adjustments
505            // should get applied.
506            xAdjust += xKerning;
507            yAdjust += yKerning;
508#endif
509
510            for (le_int32 base = i; base >= 0; base = adjustments->getBaseOffset(base)) {
511                xPlacement += adjustments->getXPlacement(base);
512                yPlacement += adjustments->getYPlacement(base);
513            }
514
515            xPlacement = fFontInstance->xUnitsToPoints(xPlacement);
516            yPlacement = fFontInstance->yUnitsToPoints(yPlacement);
517            glyphStorage.adjustPosition(i, xAdjust + xPlacement, -(yAdjust + yPlacement), success);
518
519            xAdjust += fFontInstance->xUnitsToPoints(xAdvance);
520            yAdjust += fFontInstance->yUnitsToPoints(yAdvance);
521        }
522
523        glyphStorage.adjustPosition(glyphCount, xAdjust, -yAdjust, success);
524
525        delete adjustments;
526    } else {
527        // if there was no GPOS table, maybe there's non-OpenType kerning we can use
528        //   Google Patch: disable this.  Causes problems with Tamil.
529        //       Umesh says layout is poor both with and without the change, but
530        //       worse with the change.  See ocean/imageprocessing/layout_test_unittest.cc
531        //   Public ICU ticket for this problem is  #7742
532        // LayoutEngine::adjustGlyphPositions(chars, offset, count, reverse, glyphStorage, success);
533    }
534
535    LEGlyphID zwnj  = fFontInstance->mapCharToGlyph(0x200C);
536
537    if (zwnj != 0x0000) {
538        for (le_int32 g = 0; g < glyphCount; g += 1) {
539            LEGlyphID glyph = glyphStorage[g];
540
541            if (glyph == zwnj) {
542                glyphStorage[g] = LE_SET_GLYPH(glyph, 0xFFFF);
543            }
544        }
545    }
546
547#if 0
548    // Don't know why this is here...
549    LE_DELETE_ARRAY(fFeatureTags);
550    fFeatureTags = NULL;
551#endif
552}
553
554U_NAMESPACE_END
555