1 /* 2 * Copyright 2010 Google Inc. All Rights Reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.typography.font.sfntly.table.core; 18 19 import com.google.typography.font.sfntly.Font.MacintoshEncodingId; 20 import com.google.typography.font.sfntly.Font.PlatformId; 21 import com.google.typography.font.sfntly.Font.UnicodeEncodingId; 22 import com.google.typography.font.sfntly.Font.WindowsEncodingId; 23 import com.google.typography.font.sfntly.data.ReadableFontData; 24 import com.google.typography.font.sfntly.data.WritableFontData; 25 import com.google.typography.font.sfntly.table.Header; 26 import com.google.typography.font.sfntly.table.SubTableContainerTable; 27 28 import com.ibm.icu.charset.CharsetICU; 29 30 import java.nio.ByteBuffer; 31 import java.nio.CharBuffer; 32 import java.nio.charset.Charset; 33 import java.nio.charset.UnsupportedCharsetException; 34 import java.util.Arrays; 35 import java.util.HashSet; 36 import java.util.Iterator; 37 import java.util.Map; 38 import java.util.NoSuchElementException; 39 import java.util.Set; 40 import java.util.TreeMap; 41 42 // TODO(stuartg): support format 1 name tables 43 /** 44 * A Name table. 45 * 46 * @author Stuart Gill 47 */ 48 public final class NameTable extends SubTableContainerTable implements Iterable< 49 NameTable.NameEntry> { 50 51 /** 52 * Offsets to specific elements in the underlying data. These offsets are relative to the 53 * start of the table or the start of sub-blocks within the table. 54 */ 55 public enum Offset { 56 format(0), 57 count(2), 58 stringOffset(4), 59 nameRecordStart(6), 60 61 // format 1 - offset from the end of the name records 62 langTagCount(0), 63 langTagRecord(2), 64 65 nameRecordSize(12), 66 // Name Records 67 nameRecordPlatformId(0), 68 nameRecordEncodingId(2), 69 nameRecordLanguageId(4), 70 nameRecordNameId(6), 71 nameRecordStringLength(8), 72 nameRecordStringOffset(10); 73 74 private final int offset; 75 Offset(int offset)76 private Offset(int offset) { 77 this.offset = offset; 78 } 79 } 80 81 public enum NameId { 82 Unknown(-1), 83 CopyrightNotice(0), 84 FontFamilyName(1), 85 FontSubfamilyName(2), 86 UniqueFontIdentifier(3), 87 FullFontName(4), 88 VersionString(5), 89 PostscriptName(6), 90 Trademark(7), 91 ManufacturerName(8), 92 Designer(9), 93 Description(10), 94 VendorURL(11), 95 DesignerURL(12), 96 LicenseDescription(13), 97 LicenseInfoURL(14), 98 Reserved15(15), 99 PreferredFamily(16), 100 PreferredSubfamily(17), 101 CompatibleFullName(18), 102 SampleText(19), 103 PostscriptCID(20), 104 WWSFamilyName(21), 105 WWSSubfamilyName(22); 106 107 private final int value; 108 NameId(int value)109 private NameId(int value) { 110 this.value = value; 111 } 112 value()113 public int value() { 114 return this.value; 115 } 116 equals(int value)117 public boolean equals(int value) { 118 return value == this.value; 119 } 120 valueOf(int value)121 public static NameId valueOf(int value) { 122 for (NameId name : NameId.values()) { 123 if (name.equals(value)) { 124 return name; 125 } 126 } 127 return Unknown; 128 } 129 } 130 131 public enum UnicodeLanguageId { 132 // Unicode Language IDs (platform ID = 0) 133 Unknown(-1), All(0); 134 135 private final int value; 136 UnicodeLanguageId(int value)137 private UnicodeLanguageId(int value) { 138 this.value = value; 139 } 140 value()141 public int value() { 142 return this.value; 143 } 144 equals(int value)145 public boolean equals(int value) { 146 return value == this.value; 147 } 148 valueOf(int value)149 public static UnicodeLanguageId valueOf(int value) { 150 for (UnicodeLanguageId language : UnicodeLanguageId.values()) { 151 if (language.equals(value)) { 152 return language; 153 } 154 } 155 return Unknown; 156 } 157 } 158 159 /** 160 * Macinstosh Language IDs (platform ID = 1) 161 * 162 */ 163 public enum MacintoshLanguageId { 164 Unknown(-1), 165 English(0), 166 French(1), 167 German(2), 168 Italian(3), 169 Dutch(4), 170 Swedish(5), 171 Spanish(6), 172 Danish(7), 173 Portuguese(8), 174 Norwegian(9), 175 Hebrew(10), 176 Japanese(11), 177 Arabic(12), 178 Finnish(13), 179 Greek(14), 180 Icelandic(15), 181 Maltese(16), 182 Turkish(17), 183 Croatian(18), 184 Chinese_Traditional(19), 185 Urdu(20), 186 Hindi(21), 187 Thai(22), 188 Korean(23), 189 Lithuanian(24), 190 Polish(25), 191 Hungarian(26), 192 Estonian(27), 193 Latvian(28), 194 Sami(29), 195 Faroese(30), 196 FarsiPersian(31), 197 Russian(32), 198 Chinese_Simplified(33), 199 Flemish(34), 200 IrishGaelic(35), 201 Albanian(36), 202 Romanian(37), 203 Czech(38), 204 Slovak(39), 205 Slovenian(40), 206 Yiddish(41), 207 Serbian(42), 208 Macedonian(43), 209 Bulgarian(44), 210 Ukrainian(45), 211 Byelorussian(46), 212 Uzbek(47), 213 Kazakh(48), 214 Azerbaijani_Cyrillic(49), 215 Azerbaijani_Arabic(50), 216 Armenian(51), 217 Georgian(52), 218 Moldavian(53), 219 Kirghiz(54), 220 Tajiki(55), 221 Turkmen(56), 222 Mongolian_Mongolian(57), 223 Mongolian_Cyrillic(58), 224 Pashto(59), 225 Kurdish(60), 226 Kashmiri(61), 227 Sindhi(62), 228 Tibetan(63), 229 Nepali(64), 230 Sanskrit(65), 231 Marathi(66), 232 Bengali(67), 233 Assamese(68), 234 Gujarati(69), 235 Punjabi(70), 236 Oriya(71), 237 Malayalam(72), 238 Kannada(73), 239 Tamil(74), 240 Telugu(75), 241 Sinhalese(76), 242 Burmese(77), 243 Khmer(78), 244 Lao(79), 245 Vietnamese(80), 246 Indonesian(81), 247 Tagalong(82), 248 Malay_Roman(83), 249 Malay_Arabic(84), 250 Amharic(85), 251 Tigrinya(86), 252 Galla(87), 253 Somali(88), 254 Swahili(89), 255 KinyarwandaRuanda(90), 256 Rundi(91), 257 NyanjaChewa(92), 258 Malagasy(93), 259 Esperanto(94), 260 Welsh(128), 261 Basque(129), 262 Catalan(130), 263 Latin(131), 264 Quenchua(132), 265 Guarani(133), 266 Aymara(134), 267 Tatar(135), 268 Uighur(136), 269 Dzongkha(137), 270 Javanese_Roman(138), 271 Sundanese_Roman(139), 272 Galician(140), 273 Afrikaans(141), 274 Breton(142), 275 Inuktitut(143), 276 ScottishGaelic(144), 277 ManxGaelic(145), 278 IrishGaelic_WithDotAbove(146), 279 Tongan(147), 280 Greek_Polytonic(148), 281 Greenlandic(149), 282 Azerbaijani_Roman(150); 283 284 private final int value; 285 MacintoshLanguageId(int value)286 private MacintoshLanguageId(int value) { 287 this.value = value; 288 } 289 value()290 public int value() { 291 return this.value; 292 } 293 equals(int value)294 public boolean equals(int value) { 295 return value == this.value; 296 } 297 valueOf(int value)298 public static MacintoshLanguageId valueOf(int value) { 299 for (MacintoshLanguageId language : MacintoshLanguageId.values()) { 300 if (language.equals(value)) { 301 return language; 302 } 303 } 304 return Unknown; 305 } 306 } 307 308 /** 309 * Windows Language IDs (platform ID = 3) 310 */ 311 public enum WindowsLanguageId { 312 Unknown(-1), 313 Afrikaans_SouthAfrica(0x0436), 314 Albanian_Albania(0x041C), 315 Alsatian_France(0x0484), 316 Amharic_Ethiopia(0x045E), 317 Arabic_Algeria(0x1401), 318 Arabic_Bahrain(0x3C01), 319 Arabic_Egypt(0x0C01), 320 Arabic_Iraq(0x0801), 321 Arabic_Jordan(0x2C01), 322 Arabic_Kuwait(0x3401), 323 Arabic_Lebanon(0x3001), 324 Arabic_Libya(0x1001), 325 Arabic_Morocco(0x1801), 326 Arabic_Oman(0x2001), 327 Arabic_Qatar(0x4001), 328 Arabic_SaudiArabia(0x0401), 329 Arabic_Syria(0x2801), 330 Arabic_Tunisia(0x1C01), 331 Arabic_UAE(0x3801), 332 Arabic_Yemen(0x2401), 333 Armenian_Armenia(0x042B), 334 Assamese_India(0x044D), 335 Azeri_Cyrillic_Azerbaijan(0x082C), 336 Azeri_Latin_Azerbaijan(0x042C), 337 Bashkir_Russia(0x046D), 338 Basque_Basque(0x042D), 339 Belarusian_Belarus(0x0423), 340 Bengali_Bangladesh(0x0845), 341 Bengali_India(0x0445), 342 Bosnian_Cyrillic_BosniaAndHerzegovina(0x201A), 343 Bosnian_Latin_BosniaAndHerzegovina(0x141A), 344 Breton_France(0x047E), 345 Bulgarian_Bulgaria(0x0402), 346 Catalan_Catalan(0x0403), 347 Chinese_HongKongSAR(0x0C04), 348 Chinese_MacaoSAR(0x1404), 349 Chinese_PeoplesRepublicOfChina(0x0804), 350 Chinese_Singapore(0x1004), 351 Chinese_Taiwan(0x0404), 352 Corsican_France(0x0483), 353 Croatian_Croatia(0x041A), 354 Croatian_Latin_BosniaAndHerzegovina(0x101A), 355 Czech_CzechRepublic(0x0405), 356 Danish_Denmark(0x0406), 357 Dari_Afghanistan(0x048C), 358 Divehi_Maldives(0x0465), 359 Dutch_Belgium(0x0813), 360 Dutch_Netherlands(0x0413), 361 English_Australia(0x0C09), 362 English_Belize(0x2809), 363 English_Canada(0x1009), 364 English_Caribbean(0x2409), 365 English_India(0x4009), 366 English_Ireland(0x1809), 367 English_Jamaica(0x2009), 368 English_Malaysia(0x4409), 369 English_NewZealand(0x1409), 370 English_RepublicOfThePhilippines(0x3409), 371 English_Singapore(0x4809), 372 English_SouthAfrica(0x1C09), 373 English_TrinidadAndTobago(0x2C09), 374 English_UnitedKingdom(0x0809), 375 English_UnitedStates(0x0409), 376 English_Zimbabwe(0x3009), 377 Estonian_Estonia(0x0425), 378 Faroese_FaroeIslands(0x0438), 379 Filipino_Philippines(0x0464), 380 Finnish_Finland(0x040B), 381 French_Belgium(0x080C), 382 French_Canada(0x0C0C), 383 French_France(0x040C), 384 French_Luxembourg(0x140c), 385 French_PrincipalityOfMonoco(0x180C), 386 French_Switzerland(0x100C), 387 Frisian_Netherlands(0x0462), 388 Galician_Galician(0x0456), 389 Georgian_Georgia(0x0437), 390 German_Austria(0x0C07), 391 German_Germany(0x0407), 392 German_Liechtenstein(0x1407), 393 German_Luxembourg(0x1007), 394 German_Switzerland(0x0807), 395 Greek_Greece(0x0408), 396 Greenlandic_Greenland(0x046F), 397 Gujarati_India(0x0447), 398 Hausa_Latin_Nigeria(0x0468), 399 Hebrew_Israel(0x040D), 400 Hindi_India(0x0439), 401 Hungarian_Hungary(0x040E), 402 Icelandic_Iceland(0x040F), 403 Igbo_Nigeria(0x0470), 404 Indonesian_Indonesia(0x0421), 405 Inuktitut_Canada(0x045D), 406 Inuktitut_Latin_Canada(0x085D), 407 Irish_Ireland(0x083C), 408 isiXhosa_SouthAfrica(0x0434), 409 isiZulu_SouthAfrica(0x0435), 410 Italian_Italy(0x0410), 411 Italian_Switzerland(0x0810), 412 Japanese_Japan(0x0411), 413 Kannada_India(0x044B), 414 Kazakh_Kazakhstan(0x043F), 415 Khmer_Cambodia(0x0453), 416 Kiche_Guatemala(0x0486), 417 Kinyarwanda_Rwanda(0x0487), 418 Kiswahili_Kenya(0x0441), 419 Konkani_India(0x0457), 420 Korean_Korea(0x0412), 421 Kyrgyz_Kyrgyzstan(0x0440), 422 Lao_LaoPDR(0x0454), 423 Latvian_Latvia(0x0426), 424 Lithuanian_Lithuania(0x0427), 425 LowerSorbian_Germany(0x082E), 426 Luxembourgish_Luxembourg(0x046E), 427 Macedonian_FYROM_FormerYugoslavRepublicOfMacedonia(0x042F), 428 Malay_BruneiDarussalam(0x083E), 429 Malay_Malaysia(0x043E), 430 Malayalam_India(0x044C), 431 Maltese_Malta(0x043A), 432 Maori_NewZealand(0x0481), 433 Mapudungun_Chile(0x047A), 434 Marathi_India(0x044E), 435 Mohawk_Mohawk(0x047C), 436 Mongolian_Cyrillic_Mongolia(0x0450), 437 Mongolian_Traditional_PeoplesRepublicOfChina(0x0850), 438 Nepali_Nepal(0x0461), 439 Norwegian_Bokmal_Norway(0x0414), 440 Norwegian_Nynorsk_Norway(0x0814), 441 Occitan_France(0x0482), 442 Oriya_India(0x0448), 443 Pashto_Afghanistan(0x0463), 444 Polish_Poland(0x0415), 445 Portuguese_Brazil(0x0416), 446 Portuguese_Portugal(0x0816), 447 Punjabi_India(0x0446), 448 Quechua_Bolivia(0x046B), 449 Quechua_Ecuador(0x086B), 450 Quechua_Peru(0x0C6B), 451 Romanian_Romania(0x0418), 452 Romansh_Switzerland(0x0417), 453 Russian_Russia(0x0419), 454 Sami_Inari_Finland(0x243B), 455 Sami_Lule_Norway(0x103B), 456 Sami_Lule_Sweden(0x143B), 457 Sami_Northern_Finland(0x0C3B), 458 Sami_Northern_Norway(0x043B), 459 Sami_Northern_Sweden(0x083B), 460 Sami_Skolt_Finland(0x203B), 461 Sami_Southern_Norway(0x183B), 462 Sami_Southern_Sweden(0x1C3B), 463 Sanskrit_India(0x044F), 464 Serbian_Cyrillic_BosniaAndHerzegovina(0x1C1A), 465 Serbian_Cyrillic_Serbia(0x0C1A), 466 Serbian_Latin_BosniaAndHerzegovina(0x181A), 467 Serbian_Latin_Serbia(0x081A), 468 SesothoSaLeboa_SouthAfrica(0x046C), 469 Setswana_SouthAfrica(0x0432), 470 Sinhala_SriLanka(0x045B), 471 Slovak_Slovakia(0x041B), 472 Slovenian_Slovenia(0x0424), 473 Spanish_Argentina(0x2C0A), 474 Spanish_Bolivia(0x400A), 475 Spanish_Chile(0x340A), 476 Spanish_Colombia(0x240A), 477 Spanish_CostaRica(0x140A), 478 Spanish_DominicanRepublic(0x1C0A), 479 Spanish_Ecuador(0x300A), 480 Spanish_ElSalvador(0x440A), 481 Spanish_Guatemala(0x100A), 482 Spanish_Honduras(0x480A), 483 Spanish_Mexico(0x080A), 484 Spanish_Nicaragua(0x4C0A), 485 Spanish_Panama(0x180A), 486 Spanish_Paraguay(0x3C0A), 487 Spanish_Peru(0x280A), 488 Spanish_PuertoRico(0x500A), 489 Spanish_ModernSort_Spain(0x0C0A), 490 Spanish_TraditionalSort_Spain(0x040A), 491 Spanish_UnitedStates(0x540A), 492 Spanish_Uruguay(0x380A), 493 Spanish_Venezuela(0x200A), 494 Sweden_Finland(0x081D), 495 Swedish_Sweden(0x041D), 496 Syriac_Syria(0x045A), 497 Tajik_Cyrillic_Tajikistan(0x0428), 498 Tamazight_Latin_Algeria(0x085F), 499 Tamil_India(0x0449), 500 Tatar_Russia(0x0444), 501 Telugu_India(0x044A), 502 Thai_Thailand(0x041E), 503 Tibetan_PRC(0x0451), 504 Turkish_Turkey(0x041F), 505 Turkmen_Turkmenistan(0x0442), 506 Uighur_PRC(0x0480), 507 Ukrainian_Ukraine(0x0422), 508 UpperSorbian_Germany(0x042E), 509 Urdu_IslamicRepublicOfPakistan(0x0420), 510 Uzbek_Cyrillic_Uzbekistan(0x0843), 511 Uzbek_Latin_Uzbekistan(0x0443), 512 Vietnamese_Vietnam(0x042A), 513 Welsh_UnitedKingdom(0x0452), 514 Wolof_Senegal(0x0448), 515 Yakut_Russia(0x0485), 516 Yi_PRC(0x0478), 517 Yoruba_Nigeria(0x046A); 518 519 private final int value; 520 WindowsLanguageId(int value)521 private WindowsLanguageId(int value) { 522 this.value = value; 523 } 524 value()525 public int value() { 526 return this.value; 527 } 528 equals(int value)529 public boolean equals(int value) { 530 return value == this.value; 531 } 532 valueOf(int value)533 public static WindowsLanguageId valueOf(int value) { 534 for (WindowsLanguageId language : WindowsLanguageId.values()) { 535 if (language.equals(value)) { 536 return language; 537 } 538 } 539 return Unknown; 540 } 541 } 542 NameTable(Header header, ReadableFontData data)543 private NameTable(Header header, ReadableFontData data) { 544 super(header, data); 545 } 546 format()547 public int format() { 548 return this.data.readUShort(Offset.format.offset); 549 } 550 551 /** 552 * Get the number of names in the name table. 553 * @return the number of names 554 */ nameCount()555 public int nameCount() { 556 return this.data.readUShort(Offset.count.offset); 557 } 558 559 /** 560 * Get the offset to the string data in the name table. 561 * @return the string offset 562 */ stringOffset()563 private int stringOffset() { 564 return this.data.readUShort(Offset.stringOffset.offset); 565 } 566 567 /** 568 * Get the offset for the given name record. 569 * @param index the index of the name record 570 * @return the offset of the name record 571 */ offsetForNameRecord(int index)572 private int offsetForNameRecord(int index) { 573 return Offset.nameRecordStart.offset + index * Offset.nameRecordSize.offset; 574 } 575 576 /** 577 * Get the platform id for the given name record. 578 * 579 * @param index the index of the name record 580 * @return the platform id 581 * @see PlatformId 582 */ platformId(int index)583 public int platformId(int index) { 584 return this.data.readUShort( 585 Offset.nameRecordPlatformId.offset + this.offsetForNameRecord(index)); 586 } 587 588 /** 589 * Get the encoding id for the given name record. 590 * 591 * @param index the index of the name record 592 * @return the encoding id 593 * 594 * @see MacintoshEncodingId 595 * @see WindowsEncodingId 596 * @see UnicodeEncodingId 597 */ encodingId(int index)598 public int encodingId(int index) { 599 return this.data.readUShort( 600 Offset.nameRecordEncodingId.offset + this.offsetForNameRecord(index)); 601 } 602 603 /** 604 * Get the language id for the given name record. 605 * @param index the index of the name record 606 * @return the language id 607 */ languageId(int index)608 public int languageId(int index) { 609 return this.data.readUShort( 610 Offset.nameRecordLanguageId.offset + this.offsetForNameRecord(index)); 611 } 612 613 /** 614 * Get the name id for given name record. 615 * @param index the index of the name record 616 * @return the name id 617 */ nameId(int index)618 public int nameId(int index) { 619 return this.data.readUShort( 620 Offset.nameRecordNameId.offset + this.offsetForNameRecord(index)); 621 } 622 623 /** 624 * Get the length of the string data for the given name record. 625 * @param index the index of the name record 626 * @return the length of the string data in bytes 627 */ nameLength(int index)628 private int nameLength(int index) { 629 return this.data.readUShort( 630 Offset.nameRecordStringLength.offset + this.offsetForNameRecord(index)); 631 } 632 633 /** 634 * Get the offset of the string data for the given name record. 635 * @param index the index of the name record 636 * @return the offset of the string data from the start of the table 637 */ nameOffset(int index)638 private int nameOffset(int index) { 639 return this.data.readUShort( 640 Offset.nameRecordStringOffset.offset + 641 this.offsetForNameRecord(index)) + this.stringOffset(); 642 } 643 644 /** 645 * Get the name as bytes for the given name record. 646 * @param index the index of the name record 647 * @return the bytes for the name 648 */ nameAsBytes(int index)649 public byte[] nameAsBytes(int index) { 650 int length = this.nameLength(index); 651 byte[] b = new byte[length]; 652 this.data.readBytes(this.nameOffset(index), b, 0, length); 653 return b; 654 } 655 656 /** 657 * Get the name as bytes for the specified name. If there is no entry for the requested name 658 * then <code>null</code> is returned. 659 * @param platformId the platform id 660 * @param encodingId the encoding id 661 * @param languageId the language id 662 * @param nameId the name id 663 * @return the bytes for the name 664 */ nameAsBytes(int platformId, int encodingId, int languageId, int nameId)665 public byte[] nameAsBytes(int platformId, int encodingId, int languageId, int nameId) { 666 NameEntry entry = this.nameEntry(platformId, encodingId, languageId, nameId); 667 if (entry != null) { 668 return entry.nameAsBytes(); 669 } 670 return null; 671 } 672 673 /** 674 * Get the name as a String for the given name record. If there is no encoding conversion 675 * available for the name record then a best attempt String will be returned. 676 * @param index the index of the name record 677 * @return the name 678 */ name(int index)679 public String name(int index) { 680 return convertFromNameBytes( 681 this.nameAsBytes(index), this.platformId(index), this.encodingId(index)); 682 } 683 684 /** 685 * Get the name as a String for the specified name. If there is no entry for the requested name 686 * then <code>null</code> is returned. If there is no encoding conversion 687 * available for the name then a best attempt String will be returned. 688 * @param platformId the platform id 689 * @param encodingId the encoding id 690 * @param languageId the language id 691 * @param nameId the name id 692 * @return the name 693 */ name(int platformId, int encodingId, int languageId, int nameId)694 public String name(int platformId, int encodingId, int languageId, int nameId) { 695 NameEntry entry = this.nameEntry(platformId, encodingId, languageId, nameId); 696 if (entry != null) { 697 return entry.name(); 698 } 699 return null; 700 } 701 702 /** 703 * Get the name entry record for the given name entry. 704 * @param index the index of the name record 705 * @return the name entry 706 */ nameEntry(int index)707 public NameEntry nameEntry(int index) { 708 return new NameEntry( 709 this.platformId(index), this.encodingId(index), this.languageId(index), 710 this.nameId(index), this.nameAsBytes(index)); 711 } 712 713 /** 714 * Get the name entry record for the specified name. If there is no entry for the requested name 715 * then <code>null</code> is returned. 716 * @param platformId the platform id 717 * @param encodingId the encoding id 718 * @param languageId the language id 719 * @param nameId the name id 720 * @return the name entry 721 */ nameEntry( final int platformId, final int encodingId, final int languageId, final int nameId)722 public NameEntry nameEntry( 723 final int platformId, final int encodingId, final int languageId, final int nameId) { 724 Iterator<NameEntry> nameEntryIter = this.iterator(new NameEntryFilter() { 725 @Override 726 public boolean accept(int pid, int eid, int lid, int nid) { 727 if (pid == platformId && eid == encodingId && lid == languageId && nid == nameId) { 728 return true; 729 } 730 return false; 731 } 732 }); 733 // can only be one name for each set of ids 734 if (nameEntryIter.hasNext()) { 735 return nameEntryIter.next(); 736 } 737 return null; 738 } 739 740 /** 741 * Get all the name entry records. 742 * @return the set of all name entry records 743 */ names()744 public Set<NameEntry> names() { 745 Set<NameEntry> nameSet = new HashSet<NameEntry>(this.nameCount()); 746 for (NameEntry entry : this) { 747 nameSet.add(entry); 748 } 749 return nameSet; 750 } 751 752 private static class NameEntryId implements Comparable<NameEntryId> { 753 /* @see Font.PlatformId 754 */ 755 protected int platformId; 756 /* @see Font.UnicodeEncodingId 757 * @see Font.MacintoshEncodingId 758 * @see Font.WindowsEncodingId 759 */ 760 protected int encodingId; 761 /* @see NameTable.UnicodeLanguageId 762 * @see NameTable.MacintoshLanguageId 763 * @see NameTable.WindowsLanguageId 764 */ 765 protected int languageId; 766 /* @see NameTable.NameId 767 */ 768 protected int nameId; 769 770 /** 771 * @param platformId 772 * @param encodingId 773 * @param languageId 774 * @param nameId 775 */ NameEntryId(int platformId, int encodingId, int languageId, int nameId)776 protected NameEntryId(int platformId, int encodingId, int languageId, int nameId) { 777 this.platformId = platformId; 778 this.encodingId = encodingId; 779 this.languageId = languageId; 780 this.nameId = nameId; 781 } 782 783 /** 784 * Get the platform id. 785 * 786 * @return the platform id 787 */ getPlatformId()788 protected int getPlatformId() { 789 return this.platformId; 790 } 791 792 /** 793 * Get the encoding id. 794 * 795 * @return the encoding id 796 */ getEncodingId()797 protected int getEncodingId() { 798 return this.encodingId; 799 } 800 801 /** 802 * Get the language id. 803 * 804 * @return the language id 805 */ getLanguageId()806 protected int getLanguageId() { 807 return this.languageId; 808 } 809 810 /** 811 * Get the name id. 812 * 813 * @return the name id 814 */ getNameId()815 protected int getNameId() { 816 return this.nameId; 817 } 818 819 @Override equals(Object obj)820 public boolean equals(Object obj) { 821 if (!(obj instanceof NameEntryId)) { 822 return false; 823 } 824 NameEntryId other = (NameEntryId) obj; 825 return (this.encodingId == other.encodingId) && (this.languageId == other.languageId) 826 && (this.platformId == other.platformId) && (this.nameId == other.nameId); 827 } 828 829 @Override hashCode()830 public int hashCode() { 831 /* 832 * - this takes advantage of the sizes of the various entries and the fact 833 * that the ranges of their values have an almost zero probability of ever 834 * changing - this allows us to generate a unique hash at low cost - if 835 * the ranges do change then we will potentially generate non-unique hash 836 * values which is a common result 837 */ 838 return ((this.encodingId & 0x3f) << 26) | ((this.nameId & 0x3f) << 16) 839 | ((this.platformId & 0x0f) << 12) | (this.languageId & 0xff); 840 } 841 842 /** 843 * Name entries are sorted by platform id, encoding id, language id, and 844 * name id in order of decreasing importance. 845 * 846 * @return less than zero if this entry is less than the other; greater than 847 * zero if this entry is greater than the other; and zero if they 848 * are equal 849 * 850 * @see java.lang.Comparable#compareTo(java.lang.Object) 851 */ 852 @Override compareTo(NameEntryId o)853 public int compareTo(NameEntryId o) { 854 if (this.platformId != o.platformId) { 855 return this.platformId - o.platformId; 856 } 857 if (this.encodingId != o.encodingId) { 858 return this.encodingId - o.encodingId; 859 } 860 if (this.languageId != o.languageId) { 861 return this.languageId - o.languageId; 862 } 863 return this.nameId - o.nameId; 864 } 865 866 @Override toString()867 public String toString() { 868 StringBuilder sb = new StringBuilder(); 869 sb.append("P="); 870 sb.append(PlatformId.valueOf(this.platformId)); 871 sb.append(", E=0x"); 872 sb.append(Integer.toHexString(this.encodingId)); 873 sb.append(", L=0x"); 874 sb.append(Integer.toHexString(this.languageId)); 875 sb.append(", N="); 876 NameId nameId = NameId.valueOf(this.nameId); 877 if (nameId != null) { 878 sb.append(NameId.valueOf(this.nameId)); 879 } else { 880 sb.append("0x"); 881 sb.append(Integer.toHexString(this.nameId)); 882 } 883 return sb.toString(); 884 } 885 } 886 887 /** 888 * Class to represent a name entry in the name table. 889 * 890 */ 891 public static class NameEntry { 892 NameEntryId nameEntryId; 893 protected int length; 894 protected byte[] nameBytes; 895 NameEntry()896 protected NameEntry() { 897 } 898 NameEntry(NameEntryId nameEntryId, byte[] nameBytes)899 protected NameEntry(NameEntryId nameEntryId, byte[] nameBytes) { 900 this.nameEntryId = nameEntryId; 901 this.nameBytes = nameBytes; 902 } 903 NameEntry( int platformId, int encodingId, int languageId, int nameId, byte[] nameBytes)904 protected NameEntry( 905 int platformId, int encodingId, int languageId, int nameId, byte[] nameBytes) { 906 this(new NameEntryId(platformId, encodingId, languageId, nameId), nameBytes); 907 } 908 getNameEntryId()909 protected NameEntryId getNameEntryId() { 910 return this.nameEntryId; 911 } 912 913 /** 914 * Get the platform id. 915 * @return the platform id 916 */ platformId()917 public int platformId() { 918 return this.nameEntryId.getPlatformId(); 919 } 920 921 /** 922 * Get the encoding id. 923 * @return the encoding id 924 */ encodingId()925 public int encodingId() { 926 return this.nameEntryId.getEncodingId(); 927 } 928 929 /** 930 * Get the language id. 931 * @return the language id 932 */ languageId()933 public int languageId() { 934 return this.nameEntryId.getLanguageId(); 935 } 936 937 /** 938 * Get the name id. 939 * @return the name id 940 */ nameId()941 public int nameId() { 942 return this.nameEntryId.getNameId(); 943 } 944 945 /** 946 * Get the bytes for name. 947 * @return the name bytes 948 */ nameAsBytes()949 public byte[] nameAsBytes() { 950 return this.nameBytes; 951 } 952 953 /** 954 * Get the name as a String. If there is no encoding conversion 955 * available for the name bytes then a best attempt String will be returned. 956 * @return the name 957 */ name()958 public String name() { 959 return NameTable.convertFromNameBytes(this.nameBytes, this.platformId(), this.encodingId()); 960 } 961 962 @Override toString()963 public String toString() { 964 StringBuilder sb = new StringBuilder(); 965 sb.append("["); 966 sb.append(this.nameEntryId); 967 sb.append(", \""); 968 String name = this.name(); 969 sb.append(this.name()); 970 sb.append("\"]"); 971 return sb.toString(); 972 } 973 974 @Override equals(Object obj)975 public boolean equals(Object obj) { 976 if (!(obj instanceof NameEntry)) { 977 return false; 978 } 979 NameEntry other = (NameEntry) obj; 980 if (!this.nameEntryId.equals(other.nameEntryId)) { 981 return false; 982 } 983 if (this.nameBytes.length != other.nameBytes.length) { 984 return false; 985 } 986 for (int i = 0; i < this.nameBytes.length; i++) { 987 if (this.nameBytes[i] != other.nameBytes[i]) { 988 return false; 989 } 990 } 991 return true; 992 } 993 994 @Override hashCode()995 public int hashCode() { 996 int hash = this.nameEntryId.hashCode(); 997 for (int i = 0; i < this.nameBytes.length; i+=4) { 998 for (int j = 0; j < 4 && j + i < this.nameBytes.length; j++) { 999 hash |= this.nameBytes[j] << j * 8; 1000 } 1001 } 1002 return hash; 1003 } 1004 } 1005 1006 public static class NameEntryBuilder extends NameEntry { 1007 1008 /** 1009 * Constructor. 1010 */ NameEntryBuilder()1011 protected NameEntryBuilder() { 1012 super(); 1013 } 1014 NameEntryBuilder(NameEntryId nameEntryId, byte[] nameBytes)1015 protected NameEntryBuilder(NameEntryId nameEntryId, byte[] nameBytes) { 1016 super(nameEntryId, nameBytes); 1017 } 1018 NameEntryBuilder(NameEntryId nameEntryId)1019 protected NameEntryBuilder(NameEntryId nameEntryId) { 1020 this(nameEntryId, null); 1021 } 1022 NameEntryBuilder(NameEntry nameEntry)1023 protected NameEntryBuilder(NameEntry nameEntry) { 1024 this(nameEntry.getNameEntryId(), nameEntry.nameAsBytes()); 1025 } 1026 setName(String name)1027 public void setName(String name) { 1028 if (name == null) { 1029 this.nameBytes = new byte[0]; 1030 return; 1031 } 1032 this.nameBytes = NameTable.convertToNameBytes( 1033 name, this.nameEntryId.getPlatformId(), this.nameEntryId.getEncodingId()); 1034 } 1035 setName(byte[] nameBytes)1036 public void setName(byte[] nameBytes) { 1037 this.nameBytes = Arrays.copyOf(nameBytes, nameBytes.length); 1038 } 1039 setName(byte[] nameBytes, int offset, int length)1040 public void setName(byte[] nameBytes, int offset, int length) { 1041 this.nameBytes = Arrays.copyOfRange(nameBytes, offset, offset + length); 1042 } 1043 } 1044 1045 /** 1046 * An interface for a filter to use with the name entry iterator. This allows 1047 * name entries to be iterated and only those acceptable to the filter will be returned. 1048 */ 1049 public interface NameEntryFilter { 1050 /** 1051 * Callback to determine if a name entry is acceptable. 1052 * @param platformId platform id 1053 * @param encodingId encoding id 1054 * @param languageId language id 1055 * @param nameId name id 1056 * @return true if the name entry is acceptable; false otherwise 1057 */ accept(int platformId, int encodingId, int languageId, int nameId)1058 boolean accept(int platformId, int encodingId, int languageId, int nameId); 1059 } 1060 1061 protected class NameEntryIterator implements Iterator<NameEntry> { 1062 private int nameIndex = 0; 1063 private NameEntryFilter filter = null; 1064 NameEntryIterator()1065 private NameEntryIterator() { 1066 // no filter - iterate all name entries 1067 } 1068 NameEntryIterator(NameEntryFilter filter)1069 private NameEntryIterator(NameEntryFilter filter) { 1070 this.filter = filter; 1071 } 1072 1073 @Override hasNext()1074 public boolean hasNext() { 1075 if (this.filter == null) { 1076 if (this.nameIndex < nameCount()) { 1077 return true; 1078 } 1079 return false; 1080 } 1081 for (; this.nameIndex < nameCount(); this.nameIndex++) { 1082 if (filter.accept( 1083 platformId(this.nameIndex), encodingId(this.nameIndex), 1084 languageId(this.nameIndex), nameId(this.nameIndex))) { 1085 return true; 1086 } 1087 } 1088 return false; 1089 } 1090 1091 @Override next()1092 public NameEntry next() { 1093 if (!hasNext()) { 1094 throw new NoSuchElementException(); 1095 } 1096 return nameEntry(this.nameIndex++); 1097 } 1098 1099 @Override remove()1100 public void remove() { 1101 throw new UnsupportedOperationException("Cannot remove a CMap table from an existing font."); 1102 } 1103 } 1104 1105 @Override iterator()1106 public Iterator<NameEntry> iterator() { 1107 return new NameEntryIterator(); 1108 } 1109 1110 /** 1111 * Get an iterator over name entries in the name table. 1112 * @param filter a filter to select acceptable name entries 1113 * @return an iterator over name entries 1114 */ iterator(NameEntryFilter filter)1115 public Iterator<NameEntry> iterator(NameEntryFilter filter) { 1116 return new NameEntryIterator(filter); 1117 } 1118 1119 // TODO(stuartg): do this in the encoding enums getEncodingName(int platformId, int encodingId)1120 private static String getEncodingName(int platformId, int encodingId) { 1121 String encodingName = null; 1122 switch (PlatformId.valueOf(platformId)) { 1123 case Unicode: 1124 encodingName = "UTF-16BE"; 1125 break; 1126 case Macintosh: 1127 switch (MacintoshEncodingId.valueOf(encodingId)) { 1128 case Roman: 1129 encodingName = "MacRoman"; 1130 break; 1131 case Japanese: 1132 encodingName = "Shift_JIS"; 1133 break; 1134 case ChineseTraditional: 1135 encodingName = "Big5"; 1136 break; 1137 case Korean: 1138 encodingName = "EUC-KR"; 1139 break; 1140 case Arabic: 1141 encodingName = "MacArabic"; 1142 break; 1143 case Hebrew: 1144 encodingName = "MacHebrew"; 1145 break; 1146 case Greek: 1147 encodingName = "MacGreek"; 1148 break; 1149 case Russian: 1150 encodingName = "MacCyrillic"; 1151 break; 1152 case RSymbol: 1153 encodingName = "MacSymbol"; 1154 break; 1155 case Devanagari: 1156 break; 1157 case Gurmukhi: 1158 break; 1159 case Gujarati: 1160 break; 1161 case Oriya: 1162 break; 1163 case Bengali: 1164 break; 1165 case Tamil: 1166 break; 1167 case Telugu: 1168 break; 1169 case Kannada: 1170 break; 1171 case Malayalam: 1172 break; 1173 case Sinhalese: 1174 break; 1175 case Burmese: 1176 break; 1177 case Khmer: 1178 break; 1179 case Thai: 1180 encodingName = "MacThai"; 1181 break; 1182 case Laotian: 1183 break; 1184 case Georgian: 1185 // TODO: ??? is it? 1186 encodingName = "MacCyrillic"; 1187 break; 1188 case Armenian: 1189 break; 1190 case ChineseSimplified: 1191 encodingName = "EUC-CN"; 1192 break; 1193 case Tibetan: 1194 break; 1195 case Mongolian: 1196 // TODO: ??? is it? 1197 encodingName = "MacCyrillic"; 1198 break; 1199 case Geez: 1200 break; 1201 case Slavic: 1202 // TODO: ??? is it? 1203 encodingName = "MacCyrillic"; 1204 break; 1205 case Vietnamese: 1206 break; 1207 case Sindhi: 1208 break; 1209 case Uninterpreted: 1210 break; 1211 } 1212 break; 1213 case ISO: 1214 break; 1215 case Windows: 1216 switch (WindowsEncodingId.valueOf(encodingId)) { 1217 case Symbol: 1218 encodingName = "UTF-16BE"; 1219 break; 1220 case UnicodeUCS2: 1221 encodingName = "UTF-16BE"; 1222 break; 1223 case ShiftJIS: 1224 encodingName = "windows-932"; 1225 break; 1226 case PRC: 1227 encodingName = "windows-936"; 1228 break; 1229 case Big5: 1230 encodingName = "windows-950"; 1231 break; 1232 case Wansung: 1233 encodingName = "windows-949"; 1234 break; 1235 case Johab: 1236 encodingName = "ms1361"; 1237 break; 1238 case UnicodeUCS4: 1239 encodingName = "UCS-4"; 1240 break; 1241 } 1242 break; 1243 case Custom: 1244 break; 1245 default: 1246 break; 1247 } 1248 return encodingName; 1249 } 1250 1251 // TODO: caching of charsets? getCharset(int platformId, int encodingId)1252 private static Charset getCharset(int platformId, int encodingId) { 1253 String encodingName = NameTable.getEncodingName(platformId, encodingId); 1254 if (encodingName == null) { 1255 return null; 1256 } 1257 Charset charset = null; 1258 try { 1259 charset = CharsetICU.forNameICU(encodingName); 1260 } catch (UnsupportedCharsetException e) { 1261 return null; 1262 } 1263 return charset; 1264 } 1265 1266 // TODO(stuartg): 1267 // do the conversion by hand to detect conversion failures (i.e. no character in the encoding) convertToNameBytes(String name, int platformId, int encodingId)1268 private static byte[] convertToNameBytes(String name, int platformId, int encodingId) { 1269 Charset cs = NameTable.getCharset(platformId, encodingId); 1270 if (cs == null) { 1271 return null; 1272 } 1273 ByteBuffer bb = cs.encode(name); 1274 return bb.array(); 1275 } 1276 convertFromNameBytes(byte[] nameBytes, int platformId, int encodingId)1277 private static String convertFromNameBytes(byte[] nameBytes, int platformId, int encodingId) { 1278 return NameTable.convertFromNameBytes(ByteBuffer.wrap(nameBytes), platformId, encodingId); 1279 } 1280 convertFromNameBytes(ByteBuffer nameBytes, int platformId, int encodingId)1281 private static String convertFromNameBytes(ByteBuffer nameBytes, int platformId, int encodingId) { 1282 Charset cs = NameTable.getCharset(platformId, encodingId); 1283 if (cs == null) { 1284 return Integer.toHexString(platformId); 1285 } 1286 CharBuffer cb = cs.decode(nameBytes); 1287 return cb.toString(); 1288 } 1289 1290 public static class Builder extends SubTableContainerTable.Builder<NameTable> { 1291 1292 private Map<NameEntryId, NameEntryBuilder> nameEntryMap; 1293 1294 /** 1295 * Create a new builder using the header information and data provided. 1296 * 1297 * @param header the header information 1298 * @param data the data holding the table 1299 * @return a new builder 1300 */ createBuilder(Header header, WritableFontData data)1301 public static Builder createBuilder(Header header, WritableFontData data) { 1302 return new Builder(header, data); 1303 } 1304 Builder(Header header, WritableFontData data)1305 protected Builder(Header header, WritableFontData data) { 1306 super(header, data); 1307 } 1308 Builder(Header header, ReadableFontData data)1309 protected Builder(Header header, ReadableFontData data) { 1310 super(header, data); 1311 } 1312 initialize(ReadableFontData data)1313 private void initialize(ReadableFontData data) { 1314 this.nameEntryMap = new TreeMap<NameEntryId, NameEntryBuilder>(); 1315 1316 if (data != null) { 1317 NameTable table = new NameTable(this.header(), data); 1318 1319 Iterator<NameEntry> nameIter = table.iterator(); 1320 while (nameIter.hasNext()) { 1321 NameEntry nameEntry = nameIter.next(); 1322 NameEntryBuilder nameEntryBuilder = new NameEntryBuilder(nameEntry); 1323 this.nameEntryMap.put(nameEntryBuilder.getNameEntryId(), nameEntryBuilder); 1324 } 1325 } 1326 } 1327 getNameBuilders()1328 private Map<NameEntryId, NameEntryBuilder> getNameBuilders() { 1329 if (this.nameEntryMap == null) { 1330 this.initialize(super.internalReadData()); 1331 } 1332 super.setModelChanged(); 1333 return this.nameEntryMap; 1334 } 1335 1336 /** 1337 * Revert the name builders for the name table to the last version that came 1338 * from data. 1339 */ revertNames()1340 public void revertNames() { 1341 this.nameEntryMap = null; 1342 this.setModelChanged(false); 1343 } 1344 builderCount()1345 public int builderCount() { 1346 return this.getNameBuilders().size(); 1347 } 1348 1349 /** 1350 * Clear the name builders for the name table. 1351 */ clear()1352 public void clear() { 1353 this.getNameBuilders().clear(); 1354 } 1355 has(int platformId, int encodingId, int languageId, int nameId)1356 public boolean has(int platformId, int encodingId, int languageId, int nameId) { 1357 NameEntryId probe = new NameEntryId(platformId, encodingId, languageId, nameId); 1358 return this.getNameBuilders().containsKey(probe); 1359 } 1360 nameBuilder( int platformId, int encodingId, int languageId, int nameId)1361 public NameEntryBuilder nameBuilder( 1362 int platformId, int encodingId, int languageId, int nameId) { 1363 NameEntryId probe = new NameEntryId(platformId, encodingId, languageId, nameId); 1364 NameEntryBuilder builder = this.getNameBuilders().get(probe); 1365 if (builder == null) { 1366 builder = new NameEntryBuilder(probe); 1367 this.getNameBuilders().put(probe, builder); 1368 } 1369 return builder; 1370 } 1371 remove(int platformId, int encodingId, int languageId, int nameId)1372 public boolean remove(int platformId, int encodingId, int languageId, int nameId) { 1373 NameEntryId probe = new NameEntryId(platformId, encodingId, languageId, nameId); 1374 return (this.getNameBuilders().remove(probe) != null); 1375 } 1376 1377 // subclass API implementation 1378 1379 @Override subBuildTable(ReadableFontData data)1380 protected NameTable subBuildTable(ReadableFontData data) { 1381 return new NameTable(this.header(), data); 1382 } 1383 1384 @Override subDataSet()1385 protected void subDataSet() { 1386 this.nameEntryMap = null; 1387 super.setModelChanged(false); 1388 } 1389 1390 @Override subDataSizeToSerialize()1391 protected int subDataSizeToSerialize() { 1392 if (this.nameEntryMap == null || this.nameEntryMap.size() == 0) { 1393 return 0; 1394 } 1395 1396 int size = NameTable.Offset.nameRecordStart.offset + this.nameEntryMap.size() 1397 * NameTable.Offset.nameRecordSize.offset; 1398 for (Map.Entry<NameEntryId, NameEntryBuilder> entry : this.nameEntryMap.entrySet()) { 1399 size += entry.getValue().nameAsBytes().length; 1400 } 1401 return size; 1402 } 1403 1404 @Override subReadyToSerialize()1405 protected boolean subReadyToSerialize() { 1406 if (this.nameEntryMap == null || this.nameEntryMap.size() == 0) { 1407 return false; 1408 } 1409 return true; 1410 } 1411 1412 @Override subSerialize(WritableFontData newData)1413 protected int subSerialize(WritableFontData newData) { 1414 int stringTableStartOffset = 1415 NameTable.Offset.nameRecordStart.offset + this.nameEntryMap.size() 1416 * NameTable.Offset.nameRecordSize.offset; 1417 1418 // header 1419 newData.writeUShort(NameTable.Offset.format.offset, 0); 1420 newData.writeUShort(NameTable.Offset.count.offset, this.nameEntryMap.size()); 1421 newData.writeUShort(NameTable.Offset.stringOffset.offset, stringTableStartOffset); 1422 int nameRecordOffset = NameTable.Offset.nameRecordStart.offset; 1423 int stringOffset = 0; 1424 for (Map.Entry<NameEntryId, NameEntryBuilder> entry : this.nameEntryMap.entrySet()) { 1425 // lookup table 1426 newData.writeUShort(nameRecordOffset + NameTable.Offset.nameRecordPlatformId.offset, 1427 entry.getKey().getPlatformId()); 1428 newData.writeUShort(nameRecordOffset + NameTable.Offset.nameRecordEncodingId.offset, 1429 entry.getKey().getEncodingId()); 1430 newData.writeUShort(nameRecordOffset + NameTable.Offset.nameRecordLanguageId.offset, 1431 entry.getKey().getLanguageId()); 1432 newData.writeUShort(nameRecordOffset + NameTable.Offset.nameRecordNameId.offset, 1433 entry.getKey().getNameId()); 1434 newData.writeUShort(nameRecordOffset + NameTable.Offset.nameRecordStringLength.offset, 1435 entry.getValue().nameAsBytes().length); 1436 newData.writeUShort( 1437 nameRecordOffset + NameTable.Offset.nameRecordStringOffset.offset, stringOffset); 1438 nameRecordOffset += NameTable.Offset.nameRecordSize.offset; 1439 // string table 1440 byte[] nameBytes = entry.getValue().nameAsBytes(); 1441 if (nameBytes.length > 0) { 1442 stringOffset += newData.writeBytes( 1443 stringOffset + stringTableStartOffset, entry.getValue().nameAsBytes()); 1444 } 1445 } 1446 return stringOffset + stringTableStartOffset; 1447 } 1448 } 1449 } 1450