1 /* 2 * (C) Copyright IBM Corp. 1998-2011 - All Rights Reserved 3 */ 4 5 #include "LETypes.h" 6 #include "LEScripts.h" 7 #include "LELanguages.h" 8 9 #include "LayoutEngine.h" 10 #include "ArabicLayoutEngine.h" 11 #include "CanonShaping.h" 12 #include "HanLayoutEngine.h" 13 #include "HangulLayoutEngine.h" 14 #include "IndicLayoutEngine.h" 15 #include "KhmerLayoutEngine.h" 16 #include "ThaiLayoutEngine.h" 17 #include "TibetanLayoutEngine.h" 18 #include "GXLayoutEngine.h" 19 #include "ScriptAndLanguageTags.h" 20 #include "CharSubstitutionFilter.h" 21 22 #include "LEGlyphStorage.h" 23 24 #include "OpenTypeUtilities.h" 25 #include "GlyphSubstitutionTables.h" 26 #include "GlyphDefinitionTables.h" 27 #include "MorphTables.h" 28 29 #include "DefaultCharMapper.h" 30 31 #include "KernTable.h" 32 33 U_NAMESPACE_BEGIN 34 35 /* Leave this copyright notice here! It needs to go somewhere in this library. */ 36 static const char copyright[] = U_COPYRIGHT_STRING; 37 38 const le_int32 LayoutEngine::kTypoFlagKern = 0x1; 39 const le_int32 LayoutEngine::kTypoFlagLiga = 0x2; 40 41 const LEUnicode32 DefaultCharMapper::controlChars[] = { 42 0x0009, 0x000A, 0x000D, 43 /*0x200C, 0x200D,*/ 0x200E, 0x200F, 44 0x2028, 0x2029, 0x202A, 0x202B, 0x202C, 0x202D, 0x202E, 45 0x206A, 0x206B, 0x206C, 0x206D, 0x206E, 0x206F 46 }; 47 48 const le_int32 DefaultCharMapper::controlCharsCount = LE_ARRAY_SIZE(controlChars); 49 50 LEUnicode32 DefaultCharMapper::mapChar(LEUnicode32 ch) const 51 { 52 if (fFilterControls) { 53 le_int32 index = OpenTypeUtilities::search((le_uint32)ch, (le_uint32 *)controlChars, controlCharsCount); 54 55 if (controlChars[index] == ch) { 56 return 0xFFFF; 57 } 58 } 59 60 if (fMirror) { 61 le_int32 index = OpenTypeUtilities::search((le_uint32) ch, (le_uint32 *)DefaultCharMapper::mirroredChars, DefaultCharMapper::mirroredCharsCount); 62 63 if (mirroredChars[index] == ch) { 64 return DefaultCharMapper::srahCderorrim[index]; 65 } 66 } 67 68 return ch; 69 } 70 71 // This is here to get it out of LEGlyphFilter.h. 72 // No particular reason to put it here, other than 73 // this is a good central location... 74 LEGlyphFilter::~LEGlyphFilter() 75 { 76 // nothing to do 77 } 78 79 CharSubstitutionFilter::CharSubstitutionFilter(const LEFontInstance *fontInstance) 80 : fFontInstance(fontInstance) 81 { 82 // nothing to do 83 } 84 85 CharSubstitutionFilter::~CharSubstitutionFilter() 86 { 87 // nothing to do 88 } 89 90 class CanonMarkFilter : public UMemory, public LEGlyphFilter 91 { 92 private: 93 const GlyphClassDefinitionTable *classDefTable; 94 95 CanonMarkFilter(const CanonMarkFilter &other); // forbid copying of this class 96 CanonMarkFilter &operator=(const CanonMarkFilter &other); // forbid copying of this class 97 98 public: 99 CanonMarkFilter(const GlyphDefinitionTableHeader *gdefTable); 100 virtual ~CanonMarkFilter(); 101 102 virtual le_bool accept(LEGlyphID glyph) const; 103 }; 104 105 CanonMarkFilter::CanonMarkFilter(const GlyphDefinitionTableHeader *gdefTable) 106 { 107 classDefTable = gdefTable->getMarkAttachClassDefinitionTable(); 108 } 109 110 CanonMarkFilter::~CanonMarkFilter() 111 { 112 // nothing to do? 113 } 114 115 le_bool CanonMarkFilter::accept(LEGlyphID glyph) const 116 { 117 le_int32 glyphClass = classDefTable->getGlyphClass(glyph); 118 119 return glyphClass != 0; 120 } 121 122 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(LayoutEngine) 123 124 #define ccmpFeatureTag LE_CCMP_FEATURE_TAG 125 126 #define ccmpFeatureMask 0x80000000UL 127 128 #define canonFeatures (ccmpFeatureMask) 129 130 static const FeatureMap canonFeatureMap[] = 131 { 132 {ccmpFeatureTag, ccmpFeatureMask} 133 }; 134 135 static const le_int32 canonFeatureMapCount = LE_ARRAY_SIZE(canonFeatureMap); 136 137 LayoutEngine::LayoutEngine(const LEFontInstance *fontInstance, 138 le_int32 scriptCode, 139 le_int32 languageCode, 140 le_int32 typoFlags, 141 LEErrorCode &success) 142 : fGlyphStorage(NULL), fFontInstance(fontInstance), fScriptCode(scriptCode), fLanguageCode(languageCode), 143 fTypoFlags(typoFlags), fFilterZeroWidth(TRUE) 144 { 145 if (LE_FAILURE(success)) { 146 return; 147 } 148 149 fGlyphStorage = new LEGlyphStorage(); 150 if (fGlyphStorage == NULL) { 151 success = LE_MEMORY_ALLOCATION_ERROR; 152 } 153 } 154 155 le_int32 LayoutEngine::getGlyphCount() const 156 { 157 return fGlyphStorage->getGlyphCount(); 158 } 159 160 void LayoutEngine::getCharIndices(le_int32 charIndices[], le_int32 indexBase, LEErrorCode &success) const 161 { 162 fGlyphStorage->getCharIndices(charIndices, indexBase, success); 163 } 164 165 void LayoutEngine::getCharIndices(le_int32 charIndices[], LEErrorCode &success) const 166 { 167 fGlyphStorage->getCharIndices(charIndices, success); 168 } 169 170 // Copy the glyphs into caller's (32-bit) glyph array, OR in extraBits 171 void LayoutEngine::getGlyphs(le_uint32 glyphs[], le_uint32 extraBits, LEErrorCode &success) const 172 { 173 fGlyphStorage->getGlyphs(glyphs, extraBits, success); 174 } 175 176 void LayoutEngine::getGlyphs(LEGlyphID glyphs[], LEErrorCode &success) const 177 { 178 fGlyphStorage->getGlyphs(glyphs, success); 179 } 180 181 182 void LayoutEngine::getGlyphPositions(float positions[], LEErrorCode &success) const 183 { 184 fGlyphStorage->getGlyphPositions(positions, success); 185 } 186 187 void LayoutEngine::getGlyphPosition(le_int32 glyphIndex, float &x, float &y, LEErrorCode &success) const 188 { 189 fGlyphStorage->getGlyphPosition(glyphIndex, x, y, success); 190 } 191 192 le_int32 LayoutEngine::characterProcessing(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, 193 LEUnicode *&outChars, LEGlyphStorage &glyphStorage, LEErrorCode &success) 194 { 195 if (LE_FAILURE(success)) { 196 return 0; 197 } 198 199 if (offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { 200 success = LE_ILLEGAL_ARGUMENT_ERROR; 201 return 0; 202 } 203 204 const GlyphSubstitutionTableHeader *canonGSUBTable = (GlyphSubstitutionTableHeader *) CanonShaping::glyphSubstitutionTable; 205 LETag scriptTag = OpenTypeLayoutEngine::getScriptTag(fScriptCode); 206 LETag langSysTag = OpenTypeLayoutEngine::getLangSysTag(fLanguageCode); 207 le_int32 i, dir = 1, out = 0, outCharCount = count; 208 209 if (canonGSUBTable->coversScript(scriptTag)) { 210 CharSubstitutionFilter *substitutionFilter = new CharSubstitutionFilter(fFontInstance); 211 if (substitutionFilter == NULL) { 212 success = LE_MEMORY_ALLOCATION_ERROR; 213 return 0; 214 } 215 216 const LEUnicode *inChars = &chars[offset]; 217 LEUnicode *reordered = NULL; 218 LEGlyphStorage fakeGlyphStorage; 219 220 fakeGlyphStorage.allocateGlyphArray(count, rightToLeft, success); 221 222 if (LE_FAILURE(success)) { 223 delete substitutionFilter; 224 return 0; 225 } 226 227 // This is the cheapest way to get mark reordering only for Hebrew. 228 // We could just do the mark reordering for all scripts, but most 229 // of them probably don't need it... 230 if (fScriptCode == hebrScriptCode) { 231 reordered = LE_NEW_ARRAY(LEUnicode, count); 232 233 if (reordered == NULL) { 234 delete substitutionFilter; 235 success = LE_MEMORY_ALLOCATION_ERROR; 236 return 0; 237 } 238 239 CanonShaping::reorderMarks(&chars[offset], count, rightToLeft, reordered, fakeGlyphStorage); 240 inChars = reordered; 241 } 242 243 fakeGlyphStorage.allocateAuxData(success); 244 245 if (LE_FAILURE(success)) { 246 delete substitutionFilter; 247 return 0; 248 } 249 250 if (rightToLeft) { 251 out = count - 1; 252 dir = -1; 253 } 254 255 for (i = 0; i < count; i += 1, out += dir) { 256 fakeGlyphStorage[out] = (LEGlyphID) inChars[i]; 257 fakeGlyphStorage.setAuxData(out, canonFeatures, success); 258 } 259 260 if (reordered != NULL) { 261 LE_DELETE_ARRAY(reordered); 262 } 263 264 outCharCount = canonGSUBTable->process(fakeGlyphStorage, rightToLeft, scriptTag, langSysTag, NULL, substitutionFilter, canonFeatureMap, canonFeatureMapCount, FALSE, success); 265 266 if (LE_FAILURE(success)) { 267 delete substitutionFilter; 268 return 0; 269 } 270 271 out = (rightToLeft? outCharCount - 1 : 0); 272 273 /* 274 * The char indices array in fakeGlyphStorage has the correct mapping 275 * back to the original input characters. Save it in glyphStorage. The 276 * subsequent call to glyphStoratge.allocateGlyphArray will keep this 277 * array rather than allocating and initializing a new one. 278 */ 279 glyphStorage.adoptCharIndicesArray(fakeGlyphStorage); 280 281 outChars = LE_NEW_ARRAY(LEUnicode, outCharCount); 282 283 if (outChars == NULL) { 284 delete substitutionFilter; 285 success = LE_MEMORY_ALLOCATION_ERROR; 286 return 0; 287 } 288 289 for (i = 0; i < outCharCount; i += 1, out += dir) { 290 outChars[out] = (LEUnicode) LE_GET_GLYPH(fakeGlyphStorage[i]); 291 } 292 293 delete substitutionFilter; 294 } 295 296 return outCharCount; 297 } 298 299 le_int32 LayoutEngine::computeGlyphs(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, 300 LEGlyphStorage &glyphStorage, LEErrorCode &success) 301 { 302 if (LE_FAILURE(success)) { 303 return 0; 304 } 305 306 if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { 307 success = LE_ILLEGAL_ARGUMENT_ERROR; 308 return 0; 309 } 310 311 LEUnicode *outChars = NULL; 312 le_int32 outCharCount = characterProcessing(chars, offset, count, max, rightToLeft, outChars, glyphStorage, success); 313 314 if (outChars != NULL) { 315 mapCharsToGlyphs(outChars, 0, outCharCount, rightToLeft, rightToLeft, glyphStorage, success); 316 LE_DELETE_ARRAY(outChars); // FIXME: a subclass may have allocated this, in which case this delete might not work... 317 } else { 318 mapCharsToGlyphs(chars, offset, count, rightToLeft, rightToLeft, glyphStorage, success); 319 } 320 321 return glyphStorage.getGlyphCount(); 322 } 323 324 // Input: glyphs 325 // Output: positions 326 void LayoutEngine::positionGlyphs(LEGlyphStorage &glyphStorage, float x, float y, LEErrorCode &success) 327 { 328 if (LE_FAILURE(success)) { 329 return; 330 } 331 332 glyphStorage.allocatePositions(success); 333 334 if (LE_FAILURE(success)) { 335 return; 336 } 337 338 le_int32 i, glyphCount = glyphStorage.getGlyphCount(); 339 340 for (i = 0; i < glyphCount; i += 1) { 341 LEPoint advance; 342 343 glyphStorage.setPosition(i, x, y, success); 344 345 fFontInstance->getGlyphAdvance(glyphStorage[i], advance); 346 x += advance.fX; 347 y += advance.fY; 348 } 349 350 glyphStorage.setPosition(glyphCount, x, y, success); 351 } 352 353 void LayoutEngine::adjustGlyphPositions(const LEUnicode chars[], le_int32 offset, le_int32 count, le_bool reverse, 354 LEGlyphStorage &glyphStorage, LEErrorCode &success) 355 { 356 if (LE_FAILURE(success)) { 357 return; 358 } 359 360 if (chars == NULL || offset < 0 || count < 0) { 361 success = LE_ILLEGAL_ARGUMENT_ERROR; 362 return; 363 } 364 365 GlyphDefinitionTableHeader *gdefTable = (GlyphDefinitionTableHeader *) CanonShaping::glyphDefinitionTable; 366 CanonMarkFilter filter(gdefTable); 367 368 adjustMarkGlyphs(&chars[offset], count, reverse, glyphStorage, &filter, success); 369 370 if (fTypoFlags & 0x1) { /* kerning enabled */ 371 static const le_uint32 kernTableTag = LE_KERN_TABLE_TAG; 372 373 KernTable kt(fFontInstance, getFontTable(kernTableTag)); 374 kt.process(glyphStorage); 375 } 376 377 // default is no adjustments 378 return; 379 } 380 381 void LayoutEngine::adjustMarkGlyphs(LEGlyphStorage &glyphStorage, LEGlyphFilter *markFilter, LEErrorCode &success) 382 { 383 float xAdjust = 0; 384 le_int32 p, glyphCount = glyphStorage.getGlyphCount(); 385 386 if (LE_FAILURE(success)) { 387 return; 388 } 389 390 if (markFilter == NULL) { 391 success = LE_ILLEGAL_ARGUMENT_ERROR; 392 return; 393 } 394 395 float ignore, prev; 396 397 glyphStorage.getGlyphPosition(0, prev, ignore, success); 398 399 for (p = 0; p < glyphCount; p += 1) { 400 float next, xAdvance; 401 402 glyphStorage.getGlyphPosition(p + 1, next, ignore, success); 403 404 xAdvance = next - prev; 405 glyphStorage.adjustPosition(p, xAdjust, 0, success); 406 407 if (markFilter->accept(glyphStorage[p])) { 408 xAdjust -= xAdvance; 409 } 410 411 prev = next; 412 } 413 414 glyphStorage.adjustPosition(glyphCount, xAdjust, 0, success); 415 } 416 417 void LayoutEngine::adjustMarkGlyphs(const LEUnicode chars[], le_int32 charCount, le_bool reverse, LEGlyphStorage &glyphStorage, LEGlyphFilter *markFilter, LEErrorCode &success) 418 { 419 float xAdjust = 0; 420 le_int32 c = 0, direction = 1, p; 421 le_int32 glyphCount = glyphStorage.getGlyphCount(); 422 423 if (LE_FAILURE(success)) { 424 return; 425 } 426 427 if (markFilter == NULL) { 428 success = LE_ILLEGAL_ARGUMENT_ERROR; 429 return; 430 } 431 432 if (reverse) { 433 c = glyphCount - 1; 434 direction = -1; 435 } 436 437 float ignore, prev; 438 439 glyphStorage.getGlyphPosition(0, prev, ignore, success); 440 441 for (p = 0; p < charCount; p += 1, c += direction) { 442 float next, xAdvance; 443 444 glyphStorage.getGlyphPosition(p + 1, next, ignore, success); 445 446 xAdvance = next - prev; 447 glyphStorage.adjustPosition(p, xAdjust, 0, success); 448 449 if (markFilter->accept(chars[c])) { 450 xAdjust -= xAdvance; 451 } 452 453 prev = next; 454 } 455 456 glyphStorage.adjustPosition(glyphCount, xAdjust, 0, success); 457 } 458 459 const void *LayoutEngine::getFontTable(LETag tableTag) const 460 { 461 return fFontInstance->getFontTable(tableTag); 462 } 463 464 void LayoutEngine::mapCharsToGlyphs(const LEUnicode chars[], le_int32 offset, le_int32 count, le_bool reverse, le_bool mirror, 465 LEGlyphStorage &glyphStorage, LEErrorCode &success) 466 { 467 if (LE_FAILURE(success)) { 468 return; 469 } 470 471 glyphStorage.allocateGlyphArray(count, reverse, success); 472 473 DefaultCharMapper charMapper(TRUE, mirror); 474 475 fFontInstance->mapCharsToGlyphs(chars, offset, count, reverse, &charMapper, fFilterZeroWidth, glyphStorage); 476 } 477 478 // Input: characters, font? 479 // Output: glyphs, positions, char indices 480 // Returns: number of glyphs 481 le_int32 LayoutEngine::layoutChars(const LEUnicode chars[], le_int32 offset, le_int32 count, le_int32 max, le_bool rightToLeft, 482 float x, float y, LEErrorCode &success) 483 { 484 if (LE_FAILURE(success)) { 485 return 0; 486 } 487 488 if (chars == NULL || offset < 0 || count < 0 || max < 0 || offset >= max || offset + count > max) { 489 success = LE_ILLEGAL_ARGUMENT_ERROR; 490 return 0; 491 } 492 493 le_int32 glyphCount; 494 495 if (fGlyphStorage->getGlyphCount() > 0) { 496 fGlyphStorage->reset(); 497 } 498 499 glyphCount = computeGlyphs(chars, offset, count, max, rightToLeft, *fGlyphStorage, success); 500 positionGlyphs(*fGlyphStorage, x, y, success); 501 adjustGlyphPositions(chars, offset, count, rightToLeft, *fGlyphStorage, success); 502 503 return glyphCount; 504 } 505 506 void LayoutEngine::reset() 507 { 508 fGlyphStorage->reset(); 509 } 510 511 LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, LEErrorCode &success) 512 { 513 // 3 -> kerning and ligatures 514 return LayoutEngine::layoutEngineFactory(fontInstance, scriptCode, languageCode, 3, success); 515 } 516 517 LayoutEngine *LayoutEngine::layoutEngineFactory(const LEFontInstance *fontInstance, le_int32 scriptCode, le_int32 languageCode, le_int32 typoFlags, LEErrorCode &success) 518 { 519 static const le_uint32 gsubTableTag = LE_GSUB_TABLE_TAG; 520 static const le_uint32 mortTableTag = LE_MORT_TABLE_TAG; 521 522 if (LE_FAILURE(success)) { 523 return NULL; 524 } 525 526 const GlyphSubstitutionTableHeader *gsubTable = (const GlyphSubstitutionTableHeader *) fontInstance->getFontTable(gsubTableTag); 527 LayoutEngine *result = NULL; 528 LETag scriptTag = 0x00000000; 529 LETag languageTag = 0x00000000; 530 LETag v2ScriptTag = OpenTypeLayoutEngine::getV2ScriptTag(scriptCode); 531 532 // Right now, only invoke V2 processing for Devanagari. TODO: Allow more V2 scripts as they are 533 // properly tested. 534 535 if ( v2ScriptTag == dev2ScriptTag && gsubTable != NULL && gsubTable->coversScript( v2ScriptTag )) { 536 result = new IndicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, TRUE, gsubTable, success); 537 } 538 else if (gsubTable != NULL && gsubTable->coversScript(scriptTag = OpenTypeLayoutEngine::getScriptTag(scriptCode))) { 539 switch (scriptCode) { 540 case bengScriptCode: 541 case devaScriptCode: 542 case gujrScriptCode: 543 case kndaScriptCode: 544 case mlymScriptCode: 545 case oryaScriptCode: 546 case guruScriptCode: 547 case tamlScriptCode: 548 case teluScriptCode: 549 case sinhScriptCode: 550 result = new IndicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, FALSE, gsubTable, success); 551 break; 552 553 case arabScriptCode: 554 result = new ArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 555 break; 556 557 case hebrScriptCode: 558 // Disable hebrew ligatures since they have only archaic uses, see ticket #8318 559 result = new OpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags & ~kTypoFlagLiga, gsubTable, success); 560 break; 561 562 case hangScriptCode: 563 result = new HangulOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 564 break; 565 566 case haniScriptCode: 567 languageTag = OpenTypeLayoutEngine::getLangSysTag(languageCode); 568 569 switch (languageCode) { 570 case korLanguageCode: 571 case janLanguageCode: 572 case zhtLanguageCode: 573 case zhsLanguageCode: 574 if (gsubTable->coversScriptAndLanguage(scriptTag, languageTag, TRUE)) { 575 result = new HanOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 576 break; 577 } 578 579 // note: falling through to default case. 580 default: 581 result = new OpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 582 break; 583 } 584 585 break; 586 587 case tibtScriptCode: 588 result = new TibetanOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 589 break; 590 591 case khmrScriptCode: 592 result = new KhmerOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 593 break; 594 595 default: 596 result = new OpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, gsubTable, success); 597 break; 598 } 599 } else { 600 const MorphTableHeader *morphTable = (MorphTableHeader *) fontInstance->getFontTable(mortTableTag); 601 602 if (morphTable != NULL) { 603 result = new GXLayoutEngine(fontInstance, scriptCode, languageCode, morphTable, success); 604 } else { 605 switch (scriptCode) { 606 case bengScriptCode: 607 case devaScriptCode: 608 case gujrScriptCode: 609 case kndaScriptCode: 610 case mlymScriptCode: 611 case oryaScriptCode: 612 case guruScriptCode: 613 case tamlScriptCode: 614 case teluScriptCode: 615 case sinhScriptCode: 616 { 617 result = new IndicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success); 618 break; 619 } 620 621 case arabScriptCode: 622 //case hebrScriptCode: 623 result = new UnicodeArabicOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success); 624 break; 625 626 //case hebrScriptCode: 627 // return new HebrewOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags); 628 629 case thaiScriptCode: 630 result = new ThaiLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success); 631 break; 632 633 case hangScriptCode: 634 result = new HangulOpenTypeLayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success); 635 break; 636 637 default: 638 result = new LayoutEngine(fontInstance, scriptCode, languageCode, typoFlags, success); 639 break; 640 } 641 } 642 } 643 644 if (result && LE_FAILURE(success)) { 645 delete result; 646 result = NULL; 647 } 648 649 if (result == NULL) { 650 success = LE_MEMORY_ALLOCATION_ERROR; 651 } 652 653 return result; 654 } 655 656 LayoutEngine::~LayoutEngine() { 657 delete fGlyphStorage; 658 } 659 660 U_NAMESPACE_END 661