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