rev 60042 : 8248802: Add log helper methods to FontUtilities.java

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