1 /*
2  * Copyright (c) 2003, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.font;
27 
28 import java.awt.Font;
29 import java.awt.FontFormatException;
30 import java.awt.GraphicsEnvironment;
31 import java.awt.geom.Point2D;
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.RandomAccessFile;
35 import java.io.UnsupportedEncodingException;
36 import java.nio.ByteBuffer;
37 import java.nio.CharBuffer;
38 import java.nio.IntBuffer;
39 import java.nio.ShortBuffer;
40 import java.nio.channels.ClosedChannelException;
41 import java.nio.channels.FileChannel;
42 import java.util.ArrayList;
43 import java.util.HashMap;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.Map;
48 import java.util.Map.Entry;
49 
50 import sun.java2d.Disposer;
51 import sun.java2d.DisposerRecord;
52 
53 /**
54  * TrueTypeFont is not called SFntFont because it is not expected
55  * to handle all types that may be housed in a such a font file.
56  * If additional types are supported later, it may make sense to
57  * create an SFnt superclass. Eg to handle sfnt-housed postscript fonts.
58  * OpenType fonts are handled by this class, and possibly should be
59  * represented by a subclass.
60  * An instance stores some information from the font file to faciliate
61  * faster access. File size, the table directory and the names of the font
62  * are the most important of these. It amounts to approx 400 bytes
63  * for a typical font. Systems with mutiple locales sometimes have up to 400
64  * font files, and an app which loads all font files would need around
65  * 160Kbytes. So storing any more info than this would be expensive.
66  */
67 public class TrueTypeFont extends FileFont {
68 
69    /* -- Tags for required TrueType tables */
70     public static final int cmapTag = 0x636D6170; // 'cmap'
71     public static final int glyfTag = 0x676C7966; // 'glyf'
72     public static final int headTag = 0x68656164; // 'head'
73     public static final int hheaTag = 0x68686561; // 'hhea'
74     public static final int hmtxTag = 0x686D7478; // 'hmtx'
75     public static final int locaTag = 0x6C6F6361; // 'loca'
76     public static final int maxpTag = 0x6D617870; // 'maxp'
77     public static final int nameTag = 0x6E616D65; // 'name'
78     public static final int postTag = 0x706F7374; // 'post'
79     public static final int os_2Tag = 0x4F532F32; // 'OS/2'
80 
81     /* -- Tags for opentype related tables */
82     public static final int GDEFTag = 0x47444546; // 'GDEF'
83     public static final int GPOSTag = 0x47504F53; // 'GPOS'
84     public static final int GSUBTag = 0x47535542; // 'GSUB'
85     public static final int mortTag = 0x6D6F7274; // 'mort'
86     public static final int morxTag = 0x6D6F7278; // 'morx'
87 
88     /* -- Tags for non-standard tables */
89     public static final int fdscTag = 0x66647363; // 'fdsc' - gxFont descriptor
90     public static final int fvarTag = 0x66766172; // 'fvar' - gxFont variations
91     public static final int featTag = 0x66656174; // 'feat' - layout features
92     public static final int EBLCTag = 0x45424C43; // 'EBLC' - embedded bitmaps
93     public static final int gaspTag = 0x67617370; // 'gasp' - hint/smooth sizes
94 
95     /* --  Other tags */
96     public static final int ttcfTag = 0x74746366; // 'ttcf' - TTC file
97     public static final int v1ttTag = 0x00010000; // 'v1tt' - Version 1 TT font
98     public static final int trueTag = 0x74727565; // 'true' - Version 2 TT font
99     public static final int ottoTag = 0x4f54544f; // 'otto' - OpenType font
100 
101     /* -- ID's used in the 'name' table */
102     public static final int MAC_PLATFORM_ID = 1;
103     public static final int MACROMAN_SPECIFIC_ID = 0;
104     public static final int MACROMAN_ENGLISH_LANG = 0;
105 
106     public static final int MS_PLATFORM_ID = 3;
107     /* MS locale id for US English is the "default" */
108     public static final short ENGLISH_LOCALE_ID = 0x0409; // 1033 decimal
109     public static final int FAMILY_NAME_ID = 1;
110     // public static final int STYLE_WEIGHT_ID = 2; // currently unused.
111     public static final int FULL_NAME_ID = 4;
112     public static final int POSTSCRIPT_NAME_ID = 6;
113 
114     private static final short US_LCID = 0x0409;  // US English - default
115 
116     private static Map<String, Short> lcidMap;
117 
118     static class DirectoryEntry {
119         int tag;
120         int offset;
121         int length;
122     }
123 
124     /* There is a pool which limits the number of fd's that are in
125      * use. Normally fd's are closed as they are replaced in the pool.
126      * But if an instance of this class becomes unreferenced, then there
127      * needs to be a way to close the fd. A finalize() method could do this,
128      * but using the Disposer class will ensure its called in a more timely
129      * manner. This is not something which should be relied upon to free
130      * fd's - its a safeguard.
131      */
132     private static class TTDisposerRecord implements DisposerRecord {
133 
134         FileChannel channel = null;
135 
dispose()136         public synchronized void dispose() {
137             try {
138                 if (channel != null) {
139                     channel.close();
140                 }
141             } catch (IOException e) {
142             } finally {
143                 channel = null;
144             }
145         }
146     }
147 
148     TTDisposerRecord disposerRecord = new TTDisposerRecord();
149 
150     /* > 0 only if this font is a part of a collection */
151     int fontIndex = 0;
152 
153     /* Number of fonts in this collection. ==1 if not a collection */
154     int directoryCount = 1;
155 
156     /* offset in file of table directory for this font */
157     int directoryOffset; // 12 if its not a collection.
158 
159     /* number of table entries in the directory/offsets table */
160     int numTables;
161 
162     /* The contents of the directory/offsets table */
163     DirectoryEntry []tableDirectory;
164 
165 //     protected byte []gposTable = null;
166 //     protected byte []gdefTable = null;
167 //     protected byte []gsubTable = null;
168 //     protected byte []mortTable = null;
169 //     protected boolean hintsTabledChecked = false;
170 //     protected boolean containsHintsTable = false;
171 
172     /* These fields are set from os/2 table info. */
173     private boolean supportsJA;
174     private boolean supportsCJK;
175 
176     /* These are for faster access to the name of the font as
177      * typically exposed via API to applications.
178      */
179     private Locale nameLocale;
180     private String localeFamilyName;
181     private String localeFullName;
182 
TrueTypeFont(String platname, Object nativeNames, int fIndex, boolean javaRasterizer)183     public TrueTypeFont(String platname, Object nativeNames, int fIndex,
184                  boolean javaRasterizer)
185         throws FontFormatException
186     {
187         this(platname, nativeNames, fIndex, javaRasterizer, true);
188     }
189 
190     /**
191      * - does basic verification of the file
192      * - reads the header table for this font (within a collection)
193      * - reads the names (full, family).
194      * - determines the style of the font.
195      * - initializes the CMAP
196      * @throws FontFormatException if the font can't be opened
197      * or fails verification,  or there's no usable cmap
198      */
TrueTypeFont(String platname, Object nativeNames, int fIndex, boolean javaRasterizer, boolean useFilePool)199     public TrueTypeFont(String platname, Object nativeNames, int fIndex,
200                  boolean javaRasterizer, boolean useFilePool)
201         throws FontFormatException {
202         super(platname, nativeNames);
203         useJavaRasterizer = javaRasterizer;
204         fontRank = Font2D.TTF_RANK;
205         try {
206             verify(useFilePool);
207             init(fIndex);
208             if (!useFilePool) {
209                close();
210             }
211         } catch (Throwable t) {
212             close();
213             if (t instanceof FontFormatException) {
214                 throw (FontFormatException)t;
215             } else {
216                 throw new FontFormatException("Unexpected runtime exception.");
217             }
218         }
219         Disposer.addObjectRecord(this, disposerRecord);
220     }
221 
222     /* Enable natives just for fonts picked up from the platform that
223      * may have external bitmaps on Solaris. Could do this just for
224      * the fonts that are specified in font configuration files which
225      * would lighten the burden (think about that).
226      * The EBLCTag is used to skip natives for fonts that contain embedded
227      * bitmaps as there's no need to use X11 for those fonts.
228      * Skip all the latin fonts as they don't need this treatment.
229      * Further refine this to fonts that are natively accessible (ie
230      * as PCF bitmap fonts on the X11 font path).
231      * This method is called when creating the first strike for this font.
232      */
233     @Override
checkUseNatives()234     protected boolean checkUseNatives() {
235         if (checkedNatives) {
236             return useNatives;
237         }
238         if (!FontUtilities.isSolaris || useJavaRasterizer ||
239             FontUtilities.useJDKScaler || nativeNames == null ||
240             getDirectoryEntry(EBLCTag) != null ||
241             GraphicsEnvironment.isHeadless()) {
242             checkedNatives = true;
243             return false; /* useNatives is false */
244         } else if (nativeNames instanceof String) {
245             String name = (String)nativeNames;
246             /* Don't do this for Latin fonts */
247             if (name.indexOf("8859") > 0) {
248                 checkedNatives = true;
249                 return false;
250             } else if (NativeFont.hasExternalBitmaps(name)) {
251                 nativeFonts = new NativeFont[1];
252                 try {
253                     nativeFonts[0] = new NativeFont(name, true);
254                     /* If reach here we have an non-latin font that has
255                      * external bitmaps and we successfully created it.
256                      */
257                     useNatives = true;
258                 } catch (FontFormatException e) {
259                     nativeFonts = null;
260                 }
261             }
262         } else if (nativeNames instanceof String[]) {
263             String[] natNames = (String[])nativeNames;
264             int numNames = natNames.length;
265             boolean externalBitmaps = false;
266             for (int nn = 0; nn < numNames; nn++) {
267                 if (natNames[nn].indexOf("8859") > 0) {
268                     checkedNatives = true;
269                     return false;
270                 } else if (NativeFont.hasExternalBitmaps(natNames[nn])) {
271                     externalBitmaps = true;
272                 }
273             }
274             if (!externalBitmaps) {
275                 checkedNatives = true;
276                 return false;
277             }
278             useNatives = true;
279             nativeFonts = new NativeFont[numNames];
280             for (int nn = 0; nn < numNames; nn++) {
281                 try {
282                     nativeFonts[nn] = new NativeFont(natNames[nn], true);
283                 } catch (FontFormatException e) {
284                     useNatives = false;
285                     nativeFonts = null;
286                 }
287             }
288         }
289         if (useNatives) {
290             glyphToCharMap = new char[getMapper().getNumGlyphs()];
291         }
292         checkedNatives = true;
293         return useNatives;
294     }
295 
296 
open()297     private synchronized FileChannel open() throws FontFormatException {
298         return open(true);
299      }
300 
301     /* This is intended to be called, and the returned value used,
302      * from within a block synchronized on this font object.
303      * ie the channel returned may be nulled out at any time by "close()"
304      * unless the caller holds a lock.
305      * Deadlock warning: FontManager.addToPool(..) acquires a global lock,
306      * which means nested locks may be in effect.
307      */
open(boolean usePool)308     private synchronized FileChannel open(boolean usePool)
309                                      throws FontFormatException {
310         if (disposerRecord.channel == null) {
311             if (FontUtilities.isLogging()) {
312                 FontUtilities.getLogger().info("open TTF: " + platName);
313             }
314             try {
315                 RandomAccessFile raf = (RandomAccessFile)
316                 java.security.AccessController.doPrivileged(
317                     new java.security.PrivilegedAction<Object>() {
318                         public Object run() {
319                             try {
320                                 return new RandomAccessFile(platName, "r");
321                             } catch (FileNotFoundException ffne) {
322                             }
323                             return null;
324                     }
325                 });
326                 disposerRecord.channel = raf.getChannel();
327                 fileSize = (int)disposerRecord.channel.size();
328                 if (usePool) {
329                     FontManager fm = FontManagerFactory.getInstance();
330                     if (fm instanceof SunFontManager) {
331                         ((SunFontManager) fm).addToPool(this);
332                     }
333                 }
334             } catch (NullPointerException e) {
335                 close();
336                 throw new FontFormatException(e.toString());
337             } catch (ClosedChannelException e) {
338                 /* NIO I/O is interruptible, recurse to retry operation.
339                  * The call to channel.size() above can throw this exception.
340                  * Clear interrupts before recursing in case NIO didn't.
341                  * Note that close() sets disposerRecord.channel to null.
342                  */
343                 Thread.interrupted();
344                 close();
345                 open();
346             } catch (IOException e) {
347                 close();
348                 throw new FontFormatException(e.toString());
349             }
350         }
351         return disposerRecord.channel;
352     }
353 
close()354     protected synchronized void close() {
355         disposerRecord.dispose();
356     }
357 
358 
readBlock(ByteBuffer buffer, int offset, int length)359     int readBlock(ByteBuffer buffer, int offset, int length) {
360         int bread = 0;
361         try {
362             synchronized (this) {
363                 if (disposerRecord.channel == null) {
364                     open();
365                 }
366                 if (offset + length > fileSize) {
367                     if (offset >= fileSize) {
368                         /* Since the caller ensures that offset is < fileSize
369                          * this condition suggests that fileSize is now
370                          * different than the value we originally provided
371                          * to native when the scaler was created.
372                          * Also fileSize is updated every time we
373                          * open() the file here, but in native the value
374                          * isn't updated. If the file has changed whilst we
375                          * are executing we want to bail, not spin.
376                          */
377                         if (FontUtilities.isLogging()) {
378                             String msg = "Read offset is " + offset +
379                                 " file size is " + fileSize+
380                                 " file is " + platName;
381                             FontUtilities.getLogger().severe(msg);
382                         }
383                         return -1;
384                     } else {
385                         length = fileSize - offset;
386                     }
387                 }
388                 buffer.clear();
389                 disposerRecord.channel.position(offset);
390                 while (bread < length) {
391                     int cnt = disposerRecord.channel.read(buffer);
392                     if (cnt == -1) {
393                         String msg = "Unexpected EOF " + this;
394                         int currSize = (int)disposerRecord.channel.size();
395                         if (currSize != fileSize) {
396                             msg += " File size was " + fileSize +
397                                 " and now is " + currSize;
398                         }
399                         if (FontUtilities.isLogging()) {
400                             FontUtilities.getLogger().severe(msg);
401                         }
402                         // We could still flip() the buffer here because
403                         // it's possible that we did read some data in
404                         // an earlier loop, and we probably should
405                         // return that to the caller. Although if
406                         // the caller expected 8K of data and we return
407                         // only a few bytes then maybe it's better instead to
408                         // set bread = -1 to indicate failure.
409                         // The following is therefore using arbitrary values
410                         // but is meant to allow cases where enough
411                         // data was read to probably continue.
412                         if (bread > length/2 || bread > 16384) {
413                             buffer.flip();
414                             if (FontUtilities.isLogging()) {
415                                 msg = "Returning " + bread +
416                                     " bytes instead of " + length;
417                                 FontUtilities.getLogger().severe(msg);
418                             }
419                         } else {
420                             bread = -1;
421                         }
422                         throw new IOException(msg);
423                     }
424                     bread += cnt;
425                 }
426                 buffer.flip();
427                 if (bread > length) { // possible if buffer.size() > length
428                     bread = length;
429                 }
430             }
431         } catch (FontFormatException e) {
432             if (FontUtilities.isLogging()) {
433                 FontUtilities.getLogger().severe(
434                                        "While reading " + platName, e);
435             }
436             bread = -1; // signal EOF
437             deregisterFontAndClearStrikeCache();
438         } catch (ClosedChannelException e) {
439             /* NIO I/O is interruptible, recurse to retry operation.
440              * Clear interrupts before recursing in case NIO didn't.
441              */
442             Thread.interrupted();
443             close();
444             return readBlock(buffer, offset, length);
445         } catch (IOException e) {
446             /* If we did not read any bytes at all and the exception is
447              * not a recoverable one (ie is not ClosedChannelException) then
448              * we should indicate that there is no point in re-trying.
449              * Other than an attempt to read past the end of the file it
450              * seems unlikely this would occur as problems opening the
451              * file are handled as a FontFormatException.
452              */
453             if (FontUtilities.isLogging()) {
454                 FontUtilities.getLogger().severe(
455                                        "While reading " + platName, e);
456             }
457             if (bread == 0) {
458                 bread = -1; // signal EOF
459                 deregisterFontAndClearStrikeCache();
460             }
461         }
462         return bread;
463     }
464 
readBlock(int offset, int length)465     ByteBuffer readBlock(int offset, int length) {
466 
467         ByteBuffer buffer = ByteBuffer.allocate(length);
468         try {
469             synchronized (this) {
470                 if (disposerRecord.channel == null) {
471                     open();
472                 }
473                 if (offset + length > fileSize) {
474                     if (offset > fileSize) {
475                         return null; // assert?
476                     } else {
477                         buffer = ByteBuffer.allocate(fileSize-offset);
478                     }
479                 }
480                 disposerRecord.channel.position(offset);
481                 disposerRecord.channel.read(buffer);
482                 buffer.flip();
483             }
484         } catch (FontFormatException e) {
485             return null;
486         } catch (ClosedChannelException e) {
487             /* NIO I/O is interruptible, recurse to retry operation.
488              * Clear interrupts before recursing in case NIO didn't.
489              */
490             Thread.interrupted();
491             close();
492             readBlock(buffer, offset, length);
493         } catch (IOException e) {
494             return null;
495         }
496         return buffer;
497     }
498 
499     /* This is used by native code which can't allocate a direct byte
500      * buffer because of bug 4845371. It, and references to it in native
501      * code in scalerMethods.c can be removed once that bug is fixed.
502      * 4845371 is now fixed but we'll keep this around as it doesn't cost
503      * us anything if its never used/called.
504      */
readBytes(int offset, int length)505     byte[] readBytes(int offset, int length) {
506         ByteBuffer buffer = readBlock(offset, length);
507         if (buffer.hasArray()) {
508             return buffer.array();
509         } else {
510             byte[] bufferBytes = new byte[buffer.limit()];
511             buffer.get(bufferBytes);
512             return bufferBytes;
513         }
514     }
515 
verify(boolean usePool)516     private void verify(boolean usePool) throws FontFormatException {
517         open(usePool);
518     }
519 
520     /* sizes, in bytes, of TT/TTC header records */
521     private static final int TTCHEADERSIZE = 12;
522     private static final int DIRECTORYHEADERSIZE = 12;
523     private static final int DIRECTORYENTRYSIZE = 16;
524 
init(int fIndex)525     protected void init(int fIndex) throws FontFormatException  {
526         int headerOffset = 0;
527         ByteBuffer buffer = readBlock(0, TTCHEADERSIZE);
528         try {
529             switch (buffer.getInt()) {
530 
531             case ttcfTag:
532                 buffer.getInt(); // skip TTC version ID
533                 directoryCount = buffer.getInt();
534                 if (fIndex >= directoryCount) {
535                     throw new FontFormatException("Bad collection index");
536                 }
537                 fontIndex = fIndex;
538                 buffer = readBlock(TTCHEADERSIZE+4*fIndex, 4);
539                 headerOffset = buffer.getInt();
540                 break;
541 
542             case v1ttTag:
543             case trueTag:
544             case ottoTag:
545                 break;
546 
547             default:
548                 throw new FontFormatException("Unsupported sfnt " +
549                                               getPublicFileName());
550             }
551 
552             /* Now have the offset of this TT font (possibly within a TTC)
553              * After the TT version/scaler type field, is the short
554              * representing the number of tables in the table directory.
555              * The table directory begins at 12 bytes after the header.
556              * Each table entry is 16 bytes long (4 32-bit ints)
557              */
558             buffer = readBlock(headerOffset+4, 2);
559             numTables = buffer.getShort();
560             directoryOffset = headerOffset+DIRECTORYHEADERSIZE;
561             ByteBuffer bbuffer = readBlock(directoryOffset,
562                                            numTables*DIRECTORYENTRYSIZE);
563             IntBuffer ibuffer = bbuffer.asIntBuffer();
564             DirectoryEntry table;
565             tableDirectory = new DirectoryEntry[numTables];
566             for (int i=0; i<numTables;i++) {
567                 tableDirectory[i] = table = new DirectoryEntry();
568                 table.tag   =  ibuffer.get();
569                 /* checksum */ ibuffer.get();
570                 table.offset = ibuffer.get();
571                 table.length = ibuffer.get();
572                 if (table.offset + table.length > fileSize) {
573                     throw new FontFormatException("bad table, tag="+table.tag);
574                 }
575             }
576 
577             if (getDirectoryEntry(headTag) == null) {
578                 throw new FontFormatException("missing head table");
579             }
580             if (getDirectoryEntry(maxpTag) == null) {
581                 throw new FontFormatException("missing maxp table");
582             }
583             if (getDirectoryEntry(hmtxTag) != null
584                     && getDirectoryEntry(hheaTag) == null) {
585                 throw new FontFormatException("missing hhea table");
586             }
587             initNames();
588         } catch (Exception e) {
589             if (FontUtilities.isLogging()) {
590                 FontUtilities.getLogger().severe(e.toString());
591             }
592             if (e instanceof FontFormatException) {
593                 throw (FontFormatException)e;
594             } else {
595                 throw new FontFormatException(e.toString());
596             }
597         }
598         if (familyName == null || fullName == null) {
599             throw new FontFormatException("Font name not found");
600         }
601         /* The os2_Table is needed to gather some info, but we don't
602          * want to keep it around (as a field) so obtain it once and
603          * pass it to the code that needs it.
604          */
605         ByteBuffer os2_Table = getTableBuffer(os_2Tag);
606         setStyle(os2_Table);
607         setCJKSupport(os2_Table);
608     }
609 
610     /* The array index corresponds to a bit offset in the TrueType
611      * font's OS/2 compatibility table's code page ranges fields.
612      * These are two 32 bit unsigned int fields at offsets 78 and 82.
613      * We are only interested in determining if the font supports
614      * the windows encodings we expect as the default encoding in
615      * supported locales, so we only map the first of these fields.
616      */
617     static final String[] encoding_mapping = {
618         "cp1252",    /*  0:Latin 1  */
619         "cp1250",    /*  1:Latin 2  */
620         "cp1251",    /*  2:Cyrillic */
621         "cp1253",    /*  3:Greek    */
622         "cp1254",    /*  4:Turkish/Latin 5  */
623         "cp1255",    /*  5:Hebrew   */
624         "cp1256",    /*  6:Arabic   */
625         "cp1257",    /*  7:Windows Baltic   */
626         "",          /*  8:reserved for alternate ANSI */
627         "",          /*  9:reserved for alternate ANSI */
628         "",          /* 10:reserved for alternate ANSI */
629         "",          /* 11:reserved for alternate ANSI */
630         "",          /* 12:reserved for alternate ANSI */
631         "",          /* 13:reserved for alternate ANSI */
632         "",          /* 14:reserved for alternate ANSI */
633         "",          /* 15:reserved for alternate ANSI */
634         "ms874",     /* 16:Thai     */
635         "ms932",     /* 17:JIS/Japanese */
636         "gbk",       /* 18:PRC GBK Cp950  */
637         "ms949",     /* 19:Korean Extended Wansung */
638         "ms950",     /* 20:Chinese (Taiwan, Hongkong, Macau) */
639         "ms1361",    /* 21:Korean Johab */
640         "",          /* 22 */
641         "",          /* 23 */
642         "",          /* 24 */
643         "",          /* 25 */
644         "",          /* 26 */
645         "",          /* 27 */
646         "",          /* 28 */
647         "",          /* 29 */
648         "",          /* 30 */
649         "",          /* 31 */
650     };
651 
652     /* This maps two letter language codes to a Windows code page.
653      * Note that eg Cp1252 (the first subarray) is not exactly the same as
654      * Latin-1 since Windows code pages are do not necessarily correspond.
655      * There are two codepages for zh and ko so if a font supports
656      * only one of these ranges then we need to distinguish based on
657      * country. So far this only seems to matter for zh.
658      * REMIND: Unicode locales such as Hindi do not have a code page so
659      * this whole mechanism needs to be revised to map languages to
660      * the Unicode ranges either when this fails, or as an additional
661      * validating test. Basing it on Unicode ranges should get us away
662      * from needing to map to this small and incomplete set of Windows
663      * code pages which looks odd on non-Windows platforms.
664      */
665     private static final String[][] languages = {
666 
667         /* cp1252/Latin 1 */
668         { "en", "ca", "da", "de", "es", "fi", "fr", "is", "it",
669           "nl", "no", "pt", "sq", "sv", },
670 
671          /* cp1250/Latin2 */
672         { "cs", "cz", "et", "hr", "hu", "nr", "pl", "ro", "sk",
673           "sl", "sq", "sr", },
674 
675         /* cp1251/Cyrillic */
676         { "bg", "mk", "ru", "sh", "uk" },
677 
678         /* cp1253/Greek*/
679         { "el" },
680 
681          /* cp1254/Turkish,Latin 5 */
682         { "tr" },
683 
684          /* cp1255/Hebrew */
685         { "he" },
686 
687         /* cp1256/Arabic */
688         { "ar" },
689 
690          /* cp1257/Windows Baltic */
691         { "et", "lt", "lv" },
692 
693         /* ms874/Thai */
694         { "th" },
695 
696          /* ms932/Japanese */
697         { "ja" },
698 
699         /* gbk/Chinese (PRC GBK Cp950) */
700         { "zh", "zh_CN", },
701 
702         /* ms949/Korean Extended Wansung */
703         { "ko" },
704 
705         /* ms950/Chinese (Taiwan, Hongkong, Macau) */
706         { "zh_HK", "zh_TW", },
707 
708         /* ms1361/Korean Johab */
709         { "ko" },
710     };
711 
712     private static final String[] codePages = {
713         "cp1252",
714         "cp1250",
715         "cp1251",
716         "cp1253",
717         "cp1254",
718         "cp1255",
719         "cp1256",
720         "cp1257",
721         "ms874",
722         "ms932",
723         "gbk",
724         "ms949",
725         "ms950",
726         "ms1361",
727     };
728 
729     private static String defaultCodePage = null;
getCodePage()730     static String getCodePage() {
731 
732         if (defaultCodePage != null) {
733             return defaultCodePage;
734         }
735 
736         if (FontUtilities.isWindows) {
737             defaultCodePage =
738                 java.security.AccessController.doPrivileged(
739                    new sun.security.action.GetPropertyAction("file.encoding"));
740         } else {
741             if (languages.length != codePages.length) {
742                 throw new InternalError("wrong code pages array length");
743             }
744             Locale locale = sun.awt.SunToolkit.getStartupLocale();
745 
746             String language = locale.getLanguage();
747             if (language != null) {
748                 if (language.equals("zh")) {
749                     String country = locale.getCountry();
750                     if (country != null) {
751                         language = language + "_" + country;
752                     }
753                 }
754                 for (int i=0; i<languages.length;i++) {
755                     for (int l=0;l<languages[i].length; l++) {
756                         if (language.equals(languages[i][l])) {
757                             defaultCodePage = codePages[i];
758                             return defaultCodePage;
759                         }
760                     }
761                 }
762             }
763         }
764         if (defaultCodePage == null) {
765             defaultCodePage = "";
766         }
767         return defaultCodePage;
768     }
769 
770     /* Theoretically, reserved bits must not be set, include symbol bits */
771     public static final int reserved_bits1 = 0x80000000;
772     public static final int reserved_bits2 = 0x0000ffff;
773     @Override
supportsEncoding(String encoding)774     boolean supportsEncoding(String encoding) {
775         if (encoding == null) {
776             encoding = getCodePage();
777         }
778         if ("".equals(encoding)) {
779             return false;
780         }
781 
782         encoding = encoding.toLowerCase();
783 
784         /* java_props_md.c has a couple of special cases
785          * if language packs are installed. In these encodings the
786          * fontconfig files pick up different fonts :
787          * SimSun-18030 and MingLiU_HKSCS. Since these fonts will
788          * indicate they support the base encoding, we need to rewrite
789          * these encodings here before checking the map/array.
790          */
791         if (encoding.equals("gb18030")) {
792             encoding = "gbk";
793         } else if (encoding.equals("ms950_hkscs")) {
794             encoding = "ms950";
795         }
796 
797         ByteBuffer buffer = getTableBuffer(os_2Tag);
798         /* required info is at offsets 78 and 82 */
799         if (buffer == null || buffer.capacity() < 86) {
800             return false;
801         }
802 
803         int range1 = buffer.getInt(78); /* ulCodePageRange1 */
804         int range2 = buffer.getInt(82); /* ulCodePageRange2 */
805 
806         /* This test is too stringent for Arial on Solaris (and perhaps
807          * other fonts). Arial has at least one reserved bit set for an
808          * unknown reason.
809          */
810 //         if (((range1 & reserved_bits1) | (range2 & reserved_bits2)) != 0) {
811 //             return false;
812 //         }
813 
814         for (int em=0; em<encoding_mapping.length; em++) {
815             if (encoding_mapping[em].equals(encoding)) {
816                 if (((1 << em) & range1) != 0) {
817                     return true;
818                 }
819             }
820         }
821         return false;
822     }
823 
824 
825     /* Use info in the os_2Table to test CJK support */
setCJKSupport(ByteBuffer os2Table)826     private void setCJKSupport(ByteBuffer os2Table) {
827         /* required info is in ulong at offset 46 */
828         if (os2Table == null || os2Table.capacity() < 50) {
829             return;
830         }
831         int range2 = os2Table.getInt(46); /* ulUnicodeRange2 */
832 
833         /* Any of these bits set in the 32-63 range indicate a font with
834          * support for a CJK range. We aren't looking at some other bits
835          * in the 64-69 range such as half width forms as its unlikely a font
836          * would include those and none of these.
837          */
838         supportsCJK = ((range2 & 0x29bf0000) != 0);
839 
840         /* This should be generalised, but for now just need to know if
841          * Hiragana or Katakana ranges are supported by the font.
842          * In the 4 longs representing unicode ranges supported
843          * bits 49 & 50 indicate hiragana and katakana
844          * This is bits 17 & 18 in the 2nd ulong. If either is supported
845          * we presume this is a JA font.
846          */
847         supportsJA = ((range2 & 0x60000) != 0);
848     }
849 
supportsJA()850     boolean supportsJA() {
851         return supportsJA;
852     }
853 
getTableBuffer(int tag)854      ByteBuffer getTableBuffer(int tag) {
855         DirectoryEntry entry = null;
856 
857         for (int i=0;i<numTables;i++) {
858             if (tableDirectory[i].tag == tag) {
859                 entry = tableDirectory[i];
860                 break;
861             }
862         }
863         if (entry == null || entry.length == 0 ||
864             entry.offset+entry.length > fileSize) {
865             return null;
866         }
867 
868         int bread = 0;
869         ByteBuffer buffer = ByteBuffer.allocate(entry.length);
870         synchronized (this) {
871             try {
872                 if (disposerRecord.channel == null) {
873                     open();
874                 }
875                 disposerRecord.channel.position(entry.offset);
876                 bread = disposerRecord.channel.read(buffer);
877                 buffer.flip();
878             } catch (ClosedChannelException e) {
879                 /* NIO I/O is interruptible, recurse to retry operation.
880                  * Clear interrupts before recursing in case NIO didn't.
881                  */
882                 Thread.interrupted();
883                 close();
884                 return getTableBuffer(tag);
885             } catch (IOException e) {
886                 return null;
887             } catch (FontFormatException e) {
888                 return null;
889             }
890 
891             if (bread < entry.length) {
892                 return null;
893             } else {
894                 return buffer;
895             }
896         }
897     }
898 
899     @Override
getLayoutTableCache()900     protected long getLayoutTableCache() {
901         try {
902           return getScaler().getLayoutTableCache();
903         } catch(FontScalerException fe) {
904             return 0L;
905         }
906     }
907 
908     @Override
getTableBytes(int tag)909     protected byte[] getTableBytes(int tag) {
910         ByteBuffer buffer = getTableBuffer(tag);
911         if (buffer == null) {
912             return null;
913         } else if (buffer.hasArray()) {
914             try {
915                 return buffer.array();
916             } catch (Exception re) {
917             }
918         }
919         byte []data = new byte[getTableSize(tag)];
920         buffer.get(data);
921         return data;
922     }
923 
getTableSize(int tag)924     int getTableSize(int tag) {
925         for (int i=0;i<numTables;i++) {
926             if (tableDirectory[i].tag == tag) {
927                 return tableDirectory[i].length;
928             }
929         }
930         return 0;
931     }
932 
getTableOffset(int tag)933     int getTableOffset(int tag) {
934         for (int i=0;i<numTables;i++) {
935             if (tableDirectory[i].tag == tag) {
936                 return tableDirectory[i].offset;
937             }
938         }
939         return 0;
940     }
941 
getDirectoryEntry(int tag)942     DirectoryEntry getDirectoryEntry(int tag) {
943         for (int i=0;i<numTables;i++) {
944             if (tableDirectory[i].tag == tag) {
945                 return tableDirectory[i];
946             }
947         }
948         return null;
949     }
950 
951     /* Used to determine if this size has embedded bitmaps, which
952      * for CJK fonts should be used in preference to LCD glyphs.
953      */
useEmbeddedBitmapsForSize(int ptSize)954     boolean useEmbeddedBitmapsForSize(int ptSize) {
955         if (!supportsCJK) {
956             return false;
957         }
958         if (getDirectoryEntry(EBLCTag) == null) {
959             return false;
960         }
961         ByteBuffer eblcTable = getTableBuffer(EBLCTag);
962         int numSizes = eblcTable.getInt(4);
963         /* The bitmapSizeTable's start at offset of 8.
964          * Each bitmapSizeTable entry is 48 bytes.
965          * The offset of ppemY in the entry is 45.
966          */
967         for (int i=0;i<numSizes;i++) {
968             int ppemY = eblcTable.get(8+(i*48)+45) &0xff;
969             if (ppemY == ptSize) {
970                 return true;
971             }
972         }
973         return false;
974     }
975 
getFullName()976     public String getFullName() {
977         return fullName;
978     }
979 
980     /* This probably won't get called but is there to support the
981      * contract() of setStyle() defined in the superclass.
982      */
983     @Override
setStyle()984     protected void setStyle() {
985         setStyle(getTableBuffer(os_2Tag));
986     }
987 
988     private int fontWidth = 0;
989     @Override
getWidth()990     public int getWidth() {
991        return (fontWidth > 0) ? fontWidth : super.getWidth();
992     }
993 
994     private int fontWeight = 0;
995     @Override
getWeight()996     public int getWeight() {
997        return (fontWeight > 0) ? fontWeight : super.getWeight();
998     }
999 
1000     /* TrueTypeFont can use the fsSelection fields of OS/2 table
1001      * to determine the style. In the unlikely case that doesn't exist,
1002      * can use macStyle in the 'head' table but simpler to
1003      * fall back to super class algorithm of looking for well known string.
1004      * A very few fonts don't specify this information, but I only
1005      * came across one: Lucida Sans Thai Typewriter Oblique in
1006      * /usr/openwin/lib/locale/th_TH/X11/fonts/TrueType/lucidai.ttf
1007      * that explicitly specified the wrong value. It says its regular.
1008      * I didn't find any fonts that were inconsistent (ie regular plus some
1009      * other value).
1010      */
1011     private static final int fsSelectionItalicBit  = 0x00001;
1012     private static final int fsSelectionBoldBit    = 0x00020;
1013     private static final int fsSelectionRegularBit = 0x00040;
setStyle(ByteBuffer os_2Table)1014     private void setStyle(ByteBuffer os_2Table) {
1015         if (os_2Table == null) {
1016             return;
1017         }
1018         if (os_2Table.capacity() >= 8) {
1019             fontWeight = os_2Table.getChar(4) & 0xffff;
1020             fontWidth  = os_2Table.getChar(6) & 0xffff;
1021         }
1022         /* fsSelection is unsigned short at buffer offset 62 */
1023         if (os_2Table.capacity() < 64) {
1024             super.setStyle();
1025             return;
1026         }
1027         int fsSelection = os_2Table.getChar(62) & 0xffff;
1028         int italic  = fsSelection & fsSelectionItalicBit;
1029         int bold    = fsSelection & fsSelectionBoldBit;
1030         int regular = fsSelection & fsSelectionRegularBit;
1031 //      System.out.println("platname="+platName+" font="+fullName+
1032 //                         " family="+familyName+
1033 //                         " R="+regular+" I="+italic+" B="+bold);
1034         if (regular!=0 && ((italic|bold)!=0)) {
1035             /* This is inconsistent. Try using the font name algorithm */
1036             super.setStyle();
1037             return;
1038         } else if ((regular|italic|bold) == 0) {
1039             /* No style specified. Try using the font name algorithm */
1040             super.setStyle();
1041             return;
1042         }
1043         switch (bold|italic) {
1044         case fsSelectionItalicBit:
1045             style = Font.ITALIC;
1046             break;
1047         case fsSelectionBoldBit:
1048             if (FontUtilities.isSolaris && platName.endsWith("HG-GothicB.ttf")) {
1049                 /* Workaround for Solaris's use of a JA font that's marked as
1050                  * being designed bold, but is used as a PLAIN font.
1051                  */
1052                 style = Font.PLAIN;
1053             } else {
1054                 style = Font.BOLD;
1055             }
1056             break;
1057         case fsSelectionBoldBit|fsSelectionItalicBit:
1058             style = Font.BOLD|Font.ITALIC;
1059         }
1060     }
1061 
1062     private float stSize, stPos, ulSize, ulPos;
1063 
setStrikethroughMetrics(ByteBuffer os_2Table, int upem)1064     private void setStrikethroughMetrics(ByteBuffer os_2Table, int upem) {
1065         if (os_2Table == null || os_2Table.capacity() < 30 || upem < 0) {
1066             stSize = .05f;
1067             stPos = -.4f;
1068             return;
1069         }
1070         ShortBuffer sb = os_2Table.asShortBuffer();
1071         stSize = sb.get(13) / (float)upem;
1072         stPos = -sb.get(14) / (float)upem;
1073     }
1074 
setUnderlineMetrics(ByteBuffer postTable, int upem)1075     private void setUnderlineMetrics(ByteBuffer postTable, int upem) {
1076         if (postTable == null || postTable.capacity() < 12 || upem < 0) {
1077             ulSize = .05f;
1078             ulPos = .1f;
1079             return;
1080         }
1081         ShortBuffer sb = postTable.asShortBuffer();
1082         ulSize = sb.get(5) / (float)upem;
1083         ulPos = -sb.get(4) / (float)upem;
1084     }
1085 
1086     @Override
getStyleMetrics(float pointSize, float[] metrics, int offset)1087     public void getStyleMetrics(float pointSize, float[] metrics, int offset) {
1088 
1089         if (ulSize == 0f && ulPos == 0f) {
1090 
1091             ByteBuffer head_Table = getTableBuffer(headTag);
1092             int upem = -1;
1093             if (head_Table != null && head_Table.capacity() >= 18) {
1094                 ShortBuffer sb = head_Table.asShortBuffer();
1095                 upem = sb.get(9) & 0xffff;
1096                 if (upem < 16 || upem > 16384) {
1097                     upem = 2048;
1098                 }
1099             }
1100 
1101             ByteBuffer os2_Table = getTableBuffer(os_2Tag);
1102             setStrikethroughMetrics(os2_Table, upem);
1103 
1104             ByteBuffer post_Table = getTableBuffer(postTag);
1105             setUnderlineMetrics(post_Table, upem);
1106         }
1107 
1108         metrics[offset] = stPos * pointSize;
1109         metrics[offset+1] = stSize * pointSize;
1110 
1111         metrics[offset+2] = ulPos * pointSize;
1112         metrics[offset+3] = ulSize * pointSize;
1113     }
1114 
makeString(byte[] bytes, int len, short platformID, short encoding)1115     private String makeString(byte[] bytes, int len,
1116                              short platformID, short encoding) {
1117 
1118         if (platformID == MAC_PLATFORM_ID) {
1119             encoding = -1; // hack so we can re-use the code below.
1120         }
1121 
1122         /* Check for fonts using encodings 2->6 is just for
1123          * some old DBCS fonts, apparently mostly on Solaris.
1124          * Some of these fonts encode ascii names as double-byte characters.
1125          * ie with a leading zero byte for what properly should be a
1126          * single byte-char.
1127          */
1128         if (encoding >=2 && encoding <= 6) {
1129              byte[] oldbytes = bytes;
1130              int oldlen = len;
1131              bytes = new byte[oldlen];
1132              len = 0;
1133              for (int i=0; i<oldlen; i++) {
1134                  if (oldbytes[i] != 0) {
1135                      bytes[len++] = oldbytes[i];
1136                  }
1137              }
1138          }
1139 
1140         String charset;
1141         switch (encoding) {
1142             case -1: charset = "US-ASCII";break;
1143             case 1:  charset = "UTF-16";  break; // most common case first.
1144             case 0:  charset = "UTF-16";  break; // symbol uses this
1145             case 2:  charset = "SJIS";    break;
1146             case 3:  charset = "GBK";     break;
1147             case 4:  charset = "MS950";   break;
1148             case 5:  charset = "EUC_KR";  break;
1149             case 6:  charset = "Johab";   break;
1150             default: charset = "UTF-16";  break;
1151         }
1152 
1153         try {
1154             return new String(bytes, 0, len, charset);
1155         } catch (UnsupportedEncodingException e) {
1156             if (FontUtilities.isLogging()) {
1157                 FontUtilities.getLogger().warning(e + " EncodingID=" + encoding);
1158             }
1159             return new String(bytes, 0, len);
1160         } catch (Throwable t) {
1161             return null;
1162         }
1163     }
1164 
initNames()1165     protected void initNames() {
1166 
1167         byte[] name = new byte[256];
1168         ByteBuffer buffer = getTableBuffer(nameTag);
1169 
1170         if (buffer != null) {
1171             ShortBuffer sbuffer = buffer.asShortBuffer();
1172             sbuffer.get(); // format - not needed.
1173             short numRecords = sbuffer.get();
1174             /* The name table uses unsigned shorts. Many of these
1175              * are known small values that fit in a short.
1176              * The values that are sizes or offsets into the table could be
1177              * greater than 32767, so read and store those as ints
1178              */
1179             int stringPtr = sbuffer.get() & 0xffff;
1180 
1181             nameLocale = sun.awt.SunToolkit.getStartupLocale();
1182             short nameLocaleID = getLCIDFromLocale(nameLocale);
1183             languageCompatibleLCIDs =
1184                 getLanguageCompatibleLCIDsFromLocale(nameLocale);
1185 
1186             for (int i=0; i<numRecords; i++) {
1187                 short platformID = sbuffer.get();
1188                 if (platformID != MS_PLATFORM_ID &&
1189                     platformID != MAC_PLATFORM_ID) {
1190                     sbuffer.position(sbuffer.position()+5);
1191                     continue; // skip over this record.
1192                 }
1193                 short encodingID = sbuffer.get();
1194                 short langID     = sbuffer.get();
1195                 short nameID     = sbuffer.get();
1196                 int nameLen    = ((int) sbuffer.get()) & 0xffff;
1197                 int namePtr    = (((int) sbuffer.get()) & 0xffff) + stringPtr;
1198                 String tmpName = null;
1199 
1200                 // only want MacRoman encoding and English name on Mac.
1201                 if ((platformID == MAC_PLATFORM_ID) &&
1202                     (encodingID != MACROMAN_SPECIFIC_ID ||
1203                      langID != MACROMAN_ENGLISH_LANG)) {
1204                     continue;
1205                 }
1206 
1207                 switch (nameID) {
1208 
1209                 case FAMILY_NAME_ID:
1210                     boolean compatible = false;
1211                     if (familyName == null || langID == ENGLISH_LOCALE_ID ||
1212                         langID == nameLocaleID ||
1213                         (localeFamilyName == null &&
1214                          (compatible = isLanguageCompatible(langID))))
1215                     {
1216                         buffer.position(namePtr);
1217                         buffer.get(name, 0, nameLen);
1218                         tmpName = makeString(name, nameLen, platformID, encodingID);
1219                         if (familyName == null || langID == ENGLISH_LOCALE_ID){
1220                             familyName = tmpName;
1221                         }
1222                         if (langID == nameLocaleID ||
1223                             (localeFamilyName == null && compatible))
1224                         {
1225                             localeFamilyName = tmpName;
1226                         }
1227                     }
1228 /*
1229                     for (int ii=0;ii<nameLen;ii++) {
1230                         int val = (int)name[ii]&0xff;
1231                         System.err.print(Integer.toHexString(val)+ " ");
1232                     }
1233                     System.err.println();
1234                     System.err.println("familyName="+familyName +
1235                                        " nameLen="+nameLen+
1236                                        " langID="+langID+ " eid="+encodingID +
1237                                        " str len="+familyName.length());
1238 
1239 */
1240                     break;
1241 
1242                 case FULL_NAME_ID:
1243                     compatible = false;
1244                     if (fullName == null || langID == ENGLISH_LOCALE_ID ||
1245                         langID == nameLocaleID ||
1246                         (localeFullName == null &&
1247                          (compatible = isLanguageCompatible(langID))))
1248                     {
1249                         buffer.position(namePtr);
1250                         buffer.get(name, 0, nameLen);
1251                         tmpName = makeString(name, nameLen, platformID, encodingID);
1252 
1253                         if (fullName == null || langID == ENGLISH_LOCALE_ID) {
1254                             fullName = tmpName;
1255                         }
1256                         if (langID == nameLocaleID ||
1257                             (localeFullName == null && compatible))
1258                         {
1259                             localeFullName = tmpName;
1260                         }
1261                     }
1262                     break;
1263                 }
1264             }
1265             if (localeFamilyName == null) {
1266                 localeFamilyName = familyName;
1267             }
1268             if (localeFullName == null) {
1269                 localeFullName = fullName;
1270             }
1271         }
1272     }
1273 
1274     /* Return the requested name in the requested locale, for the
1275      * MS platform ID. If the requested locale isn't found, return US
1276      * English, if that isn't found, return null and let the caller
1277      * figure out how to handle that.
1278      */
lookupName(short findLocaleID, int findNameID)1279     protected String lookupName(short findLocaleID, int findNameID) {
1280         String foundName = null;
1281         byte[] name = new byte[1024];
1282 
1283         ByteBuffer buffer = getTableBuffer(nameTag);
1284         if (buffer != null) {
1285             ShortBuffer sbuffer = buffer.asShortBuffer();
1286             sbuffer.get(); // format - not needed.
1287             short numRecords = sbuffer.get();
1288 
1289             /* The name table uses unsigned shorts. Many of these
1290              * are known small values that fit in a short.
1291              * The values that are sizes or offsets into the table could be
1292              * greater than 32767, so read and store those as ints
1293              */
1294             int stringPtr = ((int) sbuffer.get()) & 0xffff;
1295 
1296             for (int i=0; i<numRecords; i++) {
1297                 short platformID = sbuffer.get();
1298                 if (platformID != MS_PLATFORM_ID) {
1299                     sbuffer.position(sbuffer.position()+5);
1300                     continue; // skip over this record.
1301                 }
1302                 short encodingID = sbuffer.get();
1303                 short langID     = sbuffer.get();
1304                 short nameID     = sbuffer.get();
1305                 int   nameLen    = ((int) sbuffer.get()) & 0xffff;
1306                 int   namePtr    = (((int) sbuffer.get()) & 0xffff) + stringPtr;
1307                 if (nameID == findNameID &&
1308                     ((foundName == null && langID == ENGLISH_LOCALE_ID)
1309                      || langID == findLocaleID)) {
1310                     buffer.position(namePtr);
1311                     buffer.get(name, 0, nameLen);
1312                     foundName = makeString(name, nameLen, platformID, encodingID);
1313                     if (langID == findLocaleID) {
1314                         return foundName;
1315                     }
1316                 }
1317             }
1318         }
1319         return foundName;
1320     }
1321 
1322     /**
1323      * @return number of logical fonts. Is "1" for all but TTC files
1324      */
getFontCount()1325     public int getFontCount() {
1326         return directoryCount;
1327     }
1328 
getScaler()1329     protected synchronized FontScaler getScaler() {
1330         if (scaler == null) {
1331             scaler = FontScaler.getScaler(this, fontIndex,
1332                 supportsCJK, fileSize);
1333         }
1334         return scaler;
1335     }
1336 
1337 
1338     /* Postscript name is rarely requested. Don't waste cycles locating it
1339      * as part of font creation, nor storage to hold it. Get it only on demand.
1340      */
1341     @Override
getPostscriptName()1342     public String getPostscriptName() {
1343         String name = lookupName(ENGLISH_LOCALE_ID, POSTSCRIPT_NAME_ID);
1344         if (name == null) {
1345             return fullName;
1346         } else {
1347             return name;
1348         }
1349     }
1350 
1351     @Override
getFontName(Locale locale)1352     public String getFontName(Locale locale) {
1353         if (locale == null) {
1354             return fullName;
1355         } else if (locale.equals(nameLocale) && localeFullName != null) {
1356             return localeFullName;
1357         } else {
1358             short localeID = getLCIDFromLocale(locale);
1359             String name = lookupName(localeID, FULL_NAME_ID);
1360             if (name == null) {
1361                 return fullName;
1362             } else {
1363                 return name;
1364             }
1365         }
1366     }
1367 
1368     // Return a Microsoft LCID from the given Locale.
1369     // Used when getting localized font data.
1370 
addLCIDMapEntry(Map<String, Short> map, String key, short value)1371     private static void addLCIDMapEntry(Map<String, Short> map,
1372                                         String key, short value) {
1373         map.put(key, Short.valueOf(value));
1374     }
1375 
createLCIDMap()1376     private static synchronized void createLCIDMap() {
1377         if (lcidMap != null) {
1378             return;
1379         }
1380 
1381         Map<String, Short> map = new HashMap<String, Short>(200);
1382 
1383         // the following statements are derived from the langIDMap
1384         // in src/windows/native/java/lang/java_props_md.c using the following
1385         // awk script:
1386         //    $1~/\/\*/   { next}
1387         //    $3~/\?\?/   { next }
1388         //    $3!~/_/     { next }
1389         //    $1~/0x0409/ { next }
1390         //    $1~/0x0c0a/ { next }
1391         //    $1~/0x042c/ { next }
1392         //    $1~/0x0443/ { next }
1393         //    $1~/0x0812/ { next }
1394         //    $1~/0x04/   { print "        addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next }
1395         //    $3~/,/      { print "        addLCIDMapEntry(map, " $3  " (short) " substr($1, 0, 6) ");" ; next }
1396         //                { print "        addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next }
1397         // The lines of this script:
1398         // - eliminate comments
1399         // - eliminate questionable locales
1400         // - eliminate language-only locales
1401         // - eliminate the default LCID value
1402         // - eliminate a few other unneeded LCID values
1403         // - print language-only locale entries for x04* LCID values
1404         //   (apparently Microsoft doesn't use language-only LCID values -
1405         //   see http://www.microsoft.com/OpenType/otspec/name.htm
1406         // - print complete entries for all other LCID values
1407         // Run
1408         //     awk -f awk-script langIDMap > statements
1409         addLCIDMapEntry(map, "ar", (short) 0x0401);
1410         addLCIDMapEntry(map, "bg", (short) 0x0402);
1411         addLCIDMapEntry(map, "ca", (short) 0x0403);
1412         addLCIDMapEntry(map, "zh", (short) 0x0404);
1413         addLCIDMapEntry(map, "cs", (short) 0x0405);
1414         addLCIDMapEntry(map, "da", (short) 0x0406);
1415         addLCIDMapEntry(map, "de", (short) 0x0407);
1416         addLCIDMapEntry(map, "el", (short) 0x0408);
1417         addLCIDMapEntry(map, "es", (short) 0x040a);
1418         addLCIDMapEntry(map, "fi", (short) 0x040b);
1419         addLCIDMapEntry(map, "fr", (short) 0x040c);
1420         addLCIDMapEntry(map, "iw", (short) 0x040d);
1421         addLCIDMapEntry(map, "hu", (short) 0x040e);
1422         addLCIDMapEntry(map, "is", (short) 0x040f);
1423         addLCIDMapEntry(map, "it", (short) 0x0410);
1424         addLCIDMapEntry(map, "ja", (short) 0x0411);
1425         addLCIDMapEntry(map, "ko", (short) 0x0412);
1426         addLCIDMapEntry(map, "nl", (short) 0x0413);
1427         addLCIDMapEntry(map, "no", (short) 0x0414);
1428         addLCIDMapEntry(map, "pl", (short) 0x0415);
1429         addLCIDMapEntry(map, "pt", (short) 0x0416);
1430         addLCIDMapEntry(map, "rm", (short) 0x0417);
1431         addLCIDMapEntry(map, "ro", (short) 0x0418);
1432         addLCIDMapEntry(map, "ru", (short) 0x0419);
1433         addLCIDMapEntry(map, "hr", (short) 0x041a);
1434         addLCIDMapEntry(map, "sk", (short) 0x041b);
1435         addLCIDMapEntry(map, "sq", (short) 0x041c);
1436         addLCIDMapEntry(map, "sv", (short) 0x041d);
1437         addLCIDMapEntry(map, "th", (short) 0x041e);
1438         addLCIDMapEntry(map, "tr", (short) 0x041f);
1439         addLCIDMapEntry(map, "ur", (short) 0x0420);
1440         addLCIDMapEntry(map, "in", (short) 0x0421);
1441         addLCIDMapEntry(map, "uk", (short) 0x0422);
1442         addLCIDMapEntry(map, "be", (short) 0x0423);
1443         addLCIDMapEntry(map, "sl", (short) 0x0424);
1444         addLCIDMapEntry(map, "et", (short) 0x0425);
1445         addLCIDMapEntry(map, "lv", (short) 0x0426);
1446         addLCIDMapEntry(map, "lt", (short) 0x0427);
1447         addLCIDMapEntry(map, "fa", (short) 0x0429);
1448         addLCIDMapEntry(map, "vi", (short) 0x042a);
1449         addLCIDMapEntry(map, "hy", (short) 0x042b);
1450         addLCIDMapEntry(map, "eu", (short) 0x042d);
1451         addLCIDMapEntry(map, "mk", (short) 0x042f);
1452         addLCIDMapEntry(map, "tn", (short) 0x0432);
1453         addLCIDMapEntry(map, "xh", (short) 0x0434);
1454         addLCIDMapEntry(map, "zu", (short) 0x0435);
1455         addLCIDMapEntry(map, "af", (short) 0x0436);
1456         addLCIDMapEntry(map, "ka", (short) 0x0437);
1457         addLCIDMapEntry(map, "fo", (short) 0x0438);
1458         addLCIDMapEntry(map, "hi", (short) 0x0439);
1459         addLCIDMapEntry(map, "mt", (short) 0x043a);
1460         addLCIDMapEntry(map, "se", (short) 0x043b);
1461         addLCIDMapEntry(map, "gd", (short) 0x043c);
1462         addLCIDMapEntry(map, "ms", (short) 0x043e);
1463         addLCIDMapEntry(map, "kk", (short) 0x043f);
1464         addLCIDMapEntry(map, "ky", (short) 0x0440);
1465         addLCIDMapEntry(map, "sw", (short) 0x0441);
1466         addLCIDMapEntry(map, "tt", (short) 0x0444);
1467         addLCIDMapEntry(map, "bn", (short) 0x0445);
1468         addLCIDMapEntry(map, "pa", (short) 0x0446);
1469         addLCIDMapEntry(map, "gu", (short) 0x0447);
1470         addLCIDMapEntry(map, "ta", (short) 0x0449);
1471         addLCIDMapEntry(map, "te", (short) 0x044a);
1472         addLCIDMapEntry(map, "kn", (short) 0x044b);
1473         addLCIDMapEntry(map, "ml", (short) 0x044c);
1474         addLCIDMapEntry(map, "mr", (short) 0x044e);
1475         addLCIDMapEntry(map, "sa", (short) 0x044f);
1476         addLCIDMapEntry(map, "mn", (short) 0x0450);
1477         addLCIDMapEntry(map, "cy", (short) 0x0452);
1478         addLCIDMapEntry(map, "gl", (short) 0x0456);
1479         addLCIDMapEntry(map, "dv", (short) 0x0465);
1480         addLCIDMapEntry(map, "qu", (short) 0x046b);
1481         addLCIDMapEntry(map, "mi", (short) 0x0481);
1482         addLCIDMapEntry(map, "ar_IQ", (short) 0x0801);
1483         addLCIDMapEntry(map, "zh_CN", (short) 0x0804);
1484         addLCIDMapEntry(map, "de_CH", (short) 0x0807);
1485         addLCIDMapEntry(map, "en_GB", (short) 0x0809);
1486         addLCIDMapEntry(map, "es_MX", (short) 0x080a);
1487         addLCIDMapEntry(map, "fr_BE", (short) 0x080c);
1488         addLCIDMapEntry(map, "it_CH", (short) 0x0810);
1489         addLCIDMapEntry(map, "nl_BE", (short) 0x0813);
1490         addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814);
1491         addLCIDMapEntry(map, "pt_PT", (short) 0x0816);
1492         addLCIDMapEntry(map, "ro_MD", (short) 0x0818);
1493         addLCIDMapEntry(map, "ru_MD", (short) 0x0819);
1494         addLCIDMapEntry(map, "sr_CS", (short) 0x081a);
1495         addLCIDMapEntry(map, "sv_FI", (short) 0x081d);
1496         addLCIDMapEntry(map, "az_AZ", (short) 0x082c);
1497         addLCIDMapEntry(map, "se_SE", (short) 0x083b);
1498         addLCIDMapEntry(map, "ga_IE", (short) 0x083c);
1499         addLCIDMapEntry(map, "ms_BN", (short) 0x083e);
1500         addLCIDMapEntry(map, "uz_UZ", (short) 0x0843);
1501         addLCIDMapEntry(map, "qu_EC", (short) 0x086b);
1502         addLCIDMapEntry(map, "ar_EG", (short) 0x0c01);
1503         addLCIDMapEntry(map, "zh_HK", (short) 0x0c04);
1504         addLCIDMapEntry(map, "de_AT", (short) 0x0c07);
1505         addLCIDMapEntry(map, "en_AU", (short) 0x0c09);
1506         addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c);
1507         addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a);
1508         addLCIDMapEntry(map, "se_FI", (short) 0x0c3b);
1509         addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b);
1510         addLCIDMapEntry(map, "ar_LY", (short) 0x1001);
1511         addLCIDMapEntry(map, "zh_SG", (short) 0x1004);
1512         addLCIDMapEntry(map, "de_LU", (short) 0x1007);
1513         addLCIDMapEntry(map, "en_CA", (short) 0x1009);
1514         addLCIDMapEntry(map, "es_GT", (short) 0x100a);
1515         addLCIDMapEntry(map, "fr_CH", (short) 0x100c);
1516         addLCIDMapEntry(map, "hr_BA", (short) 0x101a);
1517         addLCIDMapEntry(map, "ar_DZ", (short) 0x1401);
1518         addLCIDMapEntry(map, "zh_MO", (short) 0x1404);
1519         addLCIDMapEntry(map, "de_LI", (short) 0x1407);
1520         addLCIDMapEntry(map, "en_NZ", (short) 0x1409);
1521         addLCIDMapEntry(map, "es_CR", (short) 0x140a);
1522         addLCIDMapEntry(map, "fr_LU", (short) 0x140c);
1523         addLCIDMapEntry(map, "bs_BA", (short) 0x141a);
1524         addLCIDMapEntry(map, "ar_MA", (short) 0x1801);
1525         addLCIDMapEntry(map, "en_IE", (short) 0x1809);
1526         addLCIDMapEntry(map, "es_PA", (short) 0x180a);
1527         addLCIDMapEntry(map, "fr_MC", (short) 0x180c);
1528         addLCIDMapEntry(map, "sr_BA", (short) 0x181a);
1529         addLCIDMapEntry(map, "ar_TN", (short) 0x1c01);
1530         addLCIDMapEntry(map, "en_ZA", (short) 0x1c09);
1531         addLCIDMapEntry(map, "es_DO", (short) 0x1c0a);
1532         addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a);
1533         addLCIDMapEntry(map, "ar_OM", (short) 0x2001);
1534         addLCIDMapEntry(map, "en_JM", (short) 0x2009);
1535         addLCIDMapEntry(map, "es_VE", (short) 0x200a);
1536         addLCIDMapEntry(map, "ar_YE", (short) 0x2401);
1537         addLCIDMapEntry(map, "es_CO", (short) 0x240a);
1538         addLCIDMapEntry(map, "ar_SY", (short) 0x2801);
1539         addLCIDMapEntry(map, "en_BZ", (short) 0x2809);
1540         addLCIDMapEntry(map, "es_PE", (short) 0x280a);
1541         addLCIDMapEntry(map, "ar_JO", (short) 0x2c01);
1542         addLCIDMapEntry(map, "en_TT", (short) 0x2c09);
1543         addLCIDMapEntry(map, "es_AR", (short) 0x2c0a);
1544         addLCIDMapEntry(map, "ar_LB", (short) 0x3001);
1545         addLCIDMapEntry(map, "en_ZW", (short) 0x3009);
1546         addLCIDMapEntry(map, "es_EC", (short) 0x300a);
1547         addLCIDMapEntry(map, "ar_KW", (short) 0x3401);
1548         addLCIDMapEntry(map, "en_PH", (short) 0x3409);
1549         addLCIDMapEntry(map, "es_CL", (short) 0x340a);
1550         addLCIDMapEntry(map, "ar_AE", (short) 0x3801);
1551         addLCIDMapEntry(map, "es_UY", (short) 0x380a);
1552         addLCIDMapEntry(map, "ar_BH", (short) 0x3c01);
1553         addLCIDMapEntry(map, "es_PY", (short) 0x3c0a);
1554         addLCIDMapEntry(map, "ar_QA", (short) 0x4001);
1555         addLCIDMapEntry(map, "es_BO", (short) 0x400a);
1556         addLCIDMapEntry(map, "es_SV", (short) 0x440a);
1557         addLCIDMapEntry(map, "es_HN", (short) 0x480a);
1558         addLCIDMapEntry(map, "es_NI", (short) 0x4c0a);
1559         addLCIDMapEntry(map, "es_PR", (short) 0x500a);
1560 
1561         lcidMap = map;
1562     }
1563 
getLCIDFromLocale(Locale locale)1564     private static short getLCIDFromLocale(Locale locale) {
1565         // optimize for common case
1566         if (locale.equals(Locale.US)) {
1567             return US_LCID;
1568         }
1569 
1570         if (lcidMap == null) {
1571             createLCIDMap();
1572         }
1573 
1574         String key = locale.toString();
1575         while (!"".equals(key)) {
1576             Short lcidObject = lcidMap.get(key);
1577             if (lcidObject != null) {
1578                 return lcidObject.shortValue();
1579             }
1580             int pos = key.lastIndexOf('_');
1581             if (pos < 1) {
1582                 return US_LCID;
1583             }
1584             key = key.substring(0, pos);
1585         }
1586 
1587         return US_LCID;
1588     }
1589 
1590     @Override
getFamilyName(Locale locale)1591     public String getFamilyName(Locale locale) {
1592         if (locale == null) {
1593             return familyName;
1594         } else if (locale.equals(nameLocale) && localeFamilyName != null) {
1595             return localeFamilyName;
1596         } else {
1597             short localeID = getLCIDFromLocale(locale);
1598             String name = lookupName(localeID, FAMILY_NAME_ID);
1599             if (name == null) {
1600                 return familyName;
1601             } else {
1602                 return name;
1603             }
1604         }
1605     }
1606 
getMapper()1607     public CharToGlyphMapper getMapper() {
1608         if (mapper == null) {
1609             mapper = new TrueTypeGlyphMapper(this);
1610         }
1611         return mapper;
1612     }
1613 
1614     /* This duplicates initNames() but that has to run fast as its used
1615      * during typical start-up and the information here is likely never
1616      * needed.
1617      */
initAllNames(int requestedID, HashSet<String> names)1618     protected void initAllNames(int requestedID, HashSet<String> names) {
1619 
1620         byte[] name = new byte[256];
1621         ByteBuffer buffer = getTableBuffer(nameTag);
1622 
1623         if (buffer != null) {
1624             ShortBuffer sbuffer = buffer.asShortBuffer();
1625             sbuffer.get(); // format - not needed.
1626             short numRecords = sbuffer.get();
1627 
1628             /* The name table uses unsigned shorts. Many of these
1629              * are known small values that fit in a short.
1630              * The values that are sizes or offsets into the table could be
1631              * greater than 32767, so read and store those as ints
1632              */
1633             int stringPtr = ((int) sbuffer.get()) & 0xffff;
1634             for (int i=0; i<numRecords; i++) {
1635                 short platformID = sbuffer.get();
1636                 if (platformID != MS_PLATFORM_ID) {
1637                     sbuffer.position(sbuffer.position()+5);
1638                     continue; // skip over this record.
1639                 }
1640                 short encodingID = sbuffer.get();
1641                 short langID     = sbuffer.get();
1642                 short nameID     = sbuffer.get();
1643                 int   nameLen    = ((int) sbuffer.get()) & 0xffff;
1644                 int   namePtr    = (((int) sbuffer.get()) & 0xffff) + stringPtr;
1645 
1646                 if (nameID == requestedID) {
1647                     buffer.position(namePtr);
1648                     buffer.get(name, 0, nameLen);
1649                     names.add(makeString(name, nameLen, platformID, encodingID));
1650                 }
1651             }
1652         }
1653     }
1654 
getAllFamilyNames()1655     String[] getAllFamilyNames() {
1656         HashSet<String> aSet = new HashSet<>();
1657         try {
1658             initAllNames(FAMILY_NAME_ID, aSet);
1659         } catch (Exception e) {
1660             /* In case of malformed font */
1661         }
1662         return aSet.toArray(new String[0]);
1663     }
1664 
getAllFullNames()1665     String[] getAllFullNames() {
1666         HashSet<String> aSet = new HashSet<>();
1667         try {
1668             initAllNames(FULL_NAME_ID, aSet);
1669         } catch (Exception e) {
1670             /* In case of malformed font */
1671         }
1672         return aSet.toArray(new String[0]);
1673     }
1674 
1675     /*  Used by the OpenType engine for mark positioning.
1676      */
1677     @Override
getGlyphPoint(long pScalerContext, int glyphCode, int ptNumber)1678     Point2D.Float getGlyphPoint(long pScalerContext,
1679                                 int glyphCode, int ptNumber) {
1680         try {
1681             return getScaler().getGlyphPoint(pScalerContext,
1682                                              glyphCode, ptNumber);
1683         } catch(FontScalerException fe) {
1684             return null;
1685         }
1686     }
1687 
1688     private char[] gaspTable;
1689 
getGaspTable()1690     private char[] getGaspTable() {
1691 
1692         if (gaspTable != null) {
1693             return gaspTable;
1694         }
1695 
1696         ByteBuffer buffer = getTableBuffer(gaspTag);
1697         if (buffer == null) {
1698             return gaspTable = new char[0];
1699         }
1700 
1701         CharBuffer cbuffer = buffer.asCharBuffer();
1702         char format = cbuffer.get();
1703         /* format "1" has appeared for some Windows Vista fonts.
1704          * Its presently undocumented but the existing values
1705          * seem to be still valid so we can use it.
1706          */
1707         if (format > 1) { // unrecognised format
1708             return gaspTable = new char[0];
1709         }
1710 
1711         char numRanges = cbuffer.get();
1712         if (4+numRanges*4 > getTableSize(gaspTag)) { // sanity check
1713             return gaspTable = new char[0];
1714         }
1715         gaspTable = new char[2*numRanges];
1716         cbuffer.get(gaspTable);
1717         return gaspTable;
1718     }
1719 
1720     /* This is to obtain info from the TT 'gasp' (grid-fitting and
1721      * scan-conversion procedure) table which specifies three combinations:
1722      * Hint, Smooth (greyscale), Hint and Smooth.
1723      * In this simplified scheme we don't distinguish the latter two. We
1724      * hint even at small sizes, so as to preserve metrics consistency.
1725      * If the information isn't available default values are substituted.
1726      * The more precise defaults we'd do if we distinguished the cases are:
1727      * Bold (no other style) fonts :
1728      * 0-8 : Smooth ( do grey)
1729      * 9+  : Hint + smooth (gridfit + grey)
1730      * Plain, Italic and Bold-Italic fonts :
1731      * 0-8 : Smooth ( do grey)
1732      * 9-17 : Hint (gridfit)
1733      * 18+  : Hint + smooth (gridfit + grey)
1734      * The defaults should rarely come into play as most TT fonts provide
1735      * better defaults.
1736      * REMIND: consider unpacking the table into an array of booleans
1737      * for faster use.
1738      */
1739     @Override
useAAForPtSize(int ptsize)1740     public boolean useAAForPtSize(int ptsize) {
1741 
1742         char[] gasp = getGaspTable();
1743         if (gasp.length > 0) {
1744             for (int i=0;i<gasp.length;i+=2) {
1745                 if (ptsize <= gasp[i]) {
1746                     return ((gasp[i+1] & 0x2) != 0); // bit 2 means DO_GRAY;
1747                 }
1748             }
1749             return true;
1750         }
1751 
1752         if (style == Font.BOLD) {
1753             return true;
1754         } else {
1755             return ptsize <= 8 || ptsize >= 18;
1756         }
1757     }
1758 
1759     @Override
hasSupplementaryChars()1760     public boolean hasSupplementaryChars() {
1761         return ((TrueTypeGlyphMapper)getMapper()).hasSupplementaryChars();
1762     }
1763 
1764     @Override
toString()1765     public String toString() {
1766         return "** TrueType Font: Family="+familyName+ " Name="+fullName+
1767             " style="+style+" fileName="+getPublicFileName();
1768     }
1769 
1770 
1771     private static Map<String, short[]> lcidLanguageCompatibilityMap;
1772     private static final short[] EMPTY_COMPATIBLE_LCIDS = new short[0];
1773 
1774     // the language compatible LCIDs for this font's nameLocale
1775     private short[] languageCompatibleLCIDs;
1776 
1777     /*
1778      * Returns true if the given lcid's language is compatible
1779      * to the language of the startup Locale. I.e. if
1780      * startupLocale.getLanguage().equals(lcidLocale.getLanguage()) would
1781      * return true.
1782      */
isLanguageCompatible(short lcid)1783     private boolean isLanguageCompatible(short lcid){
1784         for (short s : languageCompatibleLCIDs) {
1785             if (s == lcid) {
1786                 return true;
1787             }
1788         }
1789         return false;
1790     }
1791 
1792     /*
1793      * Returns an array of all the language compatible LCIDs for the
1794      * given Locale. This array is later used to find compatible
1795      * locales.
1796      */
getLanguageCompatibleLCIDsFromLocale(Locale locale)1797     private static short[] getLanguageCompatibleLCIDsFromLocale(Locale locale) {
1798         if (lcidLanguageCompatibilityMap == null) {
1799             createLCIDMap();
1800             createLCIDLanguageCompatibilityMap();
1801         }
1802         String language = locale.getLanguage();
1803         short[] result = lcidLanguageCompatibilityMap.get(language);
1804         return result == null ? EMPTY_COMPATIBLE_LCIDS : result;
1805     }
1806 
1807 //     private static void prtLine(String s) {
1808 //        System.out.println(s);
1809 //     }
1810 
1811 //     /*
1812 //      * Initializes the map from Locale keys (e.g. "en_BZ" or "de")
1813 //      * to language compatible LCIDs.
1814 //      * This map could be statically created based on the fixed known set
1815 //      * added to lcidMap.
1816 //      */
1817 //     private static void createLCIDLanguageCompatibilityMap() {
1818 //         if (lcidLanguageCompatibilityMap != null) {
1819 //             return;
1820 //         }
1821 //         HashMap<String, List<Short>> result = new HashMap<>();
1822 //         for (Entry<String, Short> e : lcidMap.entrySet()) {
1823 //             String language = e.getKey();
1824 //             int index = language.indexOf('_');
1825 //             if (index != -1) {
1826 //                 language = language.substring(0, index);
1827 //             }
1828 //             List<Short> list = result.get(language);
1829 //             if (list == null) {
1830 //                 list = new ArrayList<>();
1831 //                 result.put(language, list);
1832 //             }
1833 //             if (index == -1) {
1834 //                 list.add(0, e.getValue());
1835 //             } else{
1836 //                 list.add(e.getValue());
1837 //             }
1838 //         }
1839 //         Map<String, short[]> compMap = new HashMap<>();
1840 //         for (Entry<String, List<Short>> e : result.entrySet()) {
1841 //             if (e.getValue().size() > 1) {
1842 //                 List<Short> list = e.getValue();
1843 //                 short[] shorts = new short[list.size()];
1844 //                 for (int i = 0; i < shorts.length; i++) {
1845 //                     shorts[i] = list.get(i);
1846 //                 }
1847 //                 compMap.put(e.getKey(), shorts);
1848 //             }
1849 //         }
1850 
1851 //         /* Now dump code to init the map to System.out */
1852 //         prtLine("    private static void createLCIDLanguageCompatibilityMap() {");
1853 //         prtLine("");
1854 
1855 //         prtLine("        Map<String, short[]> map = new HashMap<>();");
1856 //         prtLine("");
1857 //         prtLine("        short[] sarr;");
1858 //         for (Entry<String, short[]> e : compMap.entrySet()) {
1859 //             String lang = e.getKey();
1860 //             short[] ids = e.getValue();
1861 //             StringBuilder sb = new StringBuilder("sarr = new short[] { ");
1862 //             for (int i = 0; i < ids.length; i++) {
1863 //                 sb.append(ids[i]+", ");
1864 //             }
1865 //             sb.append("}");
1866 //             prtLine("        " + sb + ";");
1867 //             prtLine("        map.put(\"" + lang + "\", sarr);");
1868 //         }
1869 //         prtLine("");
1870 //         prtLine("        lcidLanguageCompatibilityMap = map;");
1871 //         prtLine("    }");
1872 //         /* done dumping map */
1873 
1874 //         lcidLanguageCompatibilityMap = compMap;
1875 //     }
1876 
createLCIDLanguageCompatibilityMap()1877     private static void createLCIDLanguageCompatibilityMap() {
1878 
1879         Map<String, short[]> map = new HashMap<>();
1880 
1881         short[] sarr;
1882         sarr = new short[] { 1031, 3079, 5127, 2055, 4103, };
1883         map.put("de", sarr);
1884         sarr = new short[] { 1044, 2068, };
1885         map.put("no", sarr);
1886         sarr = new short[] { 1049, 2073, };
1887         map.put("ru", sarr);
1888         sarr = new short[] { 1053, 2077, };
1889         map.put("sv", sarr);
1890         sarr = new short[] { 1046, 2070, };
1891         map.put("pt", sarr);
1892         sarr = new short[] { 1131, 3179, 2155, };
1893         map.put("qu", sarr);
1894         sarr = new short[] { 1086, 2110, };
1895         map.put("ms", sarr);
1896         sarr = new short[] { 11273, 3081, 12297, 8201, 10249, 4105, 13321, 6153, 7177, 5129, 2057, };
1897         map.put("en", sarr);
1898         sarr = new short[] { 1050, 4122, };
1899         map.put("hr", sarr);
1900         sarr = new short[] { 1040, 2064, };
1901         map.put("it", sarr);
1902         sarr = new short[] { 1036, 5132, 6156, 2060, 3084, 4108, };
1903         map.put("fr", sarr);
1904         sarr = new short[] { 1034, 12298, 14346, 2058, 8202, 19466, 17418, 9226, 13322, 5130, 7178, 11274, 16394, 4106, 10250, 6154, 18442, 20490, 15370, };
1905         map.put("es", sarr);
1906         sarr = new short[] { 1028, 3076, 5124, 4100, 2052, };
1907         map.put("zh", sarr);
1908         sarr = new short[] { 1025, 8193, 16385, 9217, 2049, 14337, 15361, 11265, 13313, 10241, 7169, 12289, 4097, 5121, 6145, 3073, };
1909         map.put("ar", sarr);
1910         sarr = new short[] { 1083, 3131, 2107, };
1911         map.put("se", sarr);
1912         sarr = new short[] { 1048, 2072, };
1913         map.put("ro", sarr);
1914         sarr = new short[] { 1043, 2067, };
1915         map.put("nl", sarr);
1916         sarr = new short[] { 7194, 3098, };
1917         map.put("sr", sarr);
1918 
1919         lcidLanguageCompatibilityMap = map;
1920     }
1921 }
1922