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