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