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