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