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