• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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