1 /* 2 * Copyright (c) 2011, 2013, 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 com.sun.javafx.font; 27 28 29 import java.lang.ref.WeakReference; 30 import java.io.File; 31 import java.io.FileNotFoundException; 32 import java.security.AccessController; 33 import java.security.PrivilegedAction; 34 import java.util.HashMap; 35 import java.util.Locale; 36 import java.util.Map; 37 import java.util.concurrent.ConcurrentHashMap; 38 import com.sun.javafx.geom.RectBounds; 39 import com.sun.javafx.geom.transform.BaseTransform; 40 import com.sun.javafx.font.FontFileReader.Buffer; 41 import static com.sun.javafx.font.PrismMetrics.*; 42 43 public abstract class PrismFontFile implements FontResource, FontConstants { 44 45 private int fontInstallationType = -1; // unknown, 0=embedded, 1=system 46 47 // TrueType fonts can have multiple names, most notably split up by 48 // platform and locale. Whilst fonts that have different names for 49 // different platforms are arguable buggy, those with localised names 50 // are not. This can cause problems. Suppose that a font has English, 51 // French and German names, and the platform enumerates the name that 52 // is most appropriate for the user locale. Then suppose a French 53 // developer uses the French name, but for his German user this font 54 // is not located by the platform because it reports the German name 55 // for that font. At runtime we no longer have any connection to the 56 // locale of the developer so we can't look for the name for that 57 // locale, even if the platform have us a performant option for that. 58 // 59 // The English name which some might think is supposed 60 // to be the interoperable name is not treated at all specially in 61 // the font format and doesn't even come up for either the user or 62 // the developer, and in fact doesn't even have to be present. 63 // Having said that we'll probably have the best luck for most users 64 // and fonts by assuming the English name if the locale name doesn't 65 // work. But either way, without platform API support for this 66 // then its really expensive as all font files need to be opened. 67 // 68 String familyName; /* Family font name (English) */ 69 String fullName; /* Full font name (English) */ 70 String psName; /* PostScript font name */ 71 String localeFamilyName; 72 String localeFullName; 73 String styleName; 74 String localeStyleName; 75 String filename; 76 int filesize; 77 FontFileReader filereader; 78 int numGlyphs = -1; 79 short indexToLocFormat; 80 int fontIndex; // into a TTC. 81 boolean isCFF; 82 boolean isEmbedded = false; 83 boolean isCopy = false; 84 boolean isTracked = false; 85 boolean isDecoded = false; 86 boolean isRegistered = true; 87 88 /* The glyph image data is stored only in a texture, and we 89 * manage how much of that is kept around. We clearly want 90 * to keep a reference to the strike that created that data. 91 */ 92 Map<FontStrikeDesc, WeakReference<PrismFontStrike>> strikeMap = 93 new ConcurrentHashMap<FontStrikeDesc, WeakReference<PrismFontStrike>>(); 94 95 protected PrismFontFile(String name, String filename, int fIndex, 96 boolean register, boolean embedded, 97 boolean copy, boolean tracked) throws Exception { 98 this.filename = filename; 99 this.isRegistered = register; 100 this.isEmbedded = embedded; 101 this.isCopy = copy; 102 this.isTracked = tracked; 103 init(name, fIndex); 104 } 105 106 WeakReference<PrismFontFile> createFileDisposer(PrismFontFactory factory) { 107 FileDisposer disposer = new FileDisposer(filename, isTracked); 108 WeakReference<PrismFontFile> ref = Disposer.addRecord(this, disposer); 109 disposer.setFactory(factory, ref); 110 return ref; 111 } 112 113 void setIsDecoded(boolean decoded) { 114 isDecoded = decoded; 115 } 116 117 /* This is called only for fonts where a temp file was created 118 */ 119 protected synchronized void disposeOnShutdown() { 120 if (isCopy || isDecoded) { 121 AccessController.doPrivileged( 122 (PrivilegedAction<Void>) () -> { 123 try { 124 (new File(filename)).delete(); 125 /* Embedded fonts (copy) can also be decoded. 126 * Set both flags to false to avoid double deletes. 127 */ 128 isCopy = isDecoded = false; 129 } catch (Exception e) { 130 } 131 return null; 132 } 133 ); 134 if (PrismFontFactory.debugFonts) { 135 System.err.println("Temp file deleted: " + filename); 136 } 137 } 138 } 139 140 public int getDefaultAAMode() { 141 return AA_GREYSCALE; 142 } 143 144 public boolean isInstalledFont() { 145 if (fontInstallationType == -1) { 146 PrismFontFactory factory = PrismFontFactory.getFontFactory(); 147 fontInstallationType = factory.isInstalledFont(filename) ? 1 : 0; 148 } 149 return fontInstallationType > 0; 150 } 151 152 static class FileDisposer implements DisposerRecord { 153 String fileName; 154 boolean isTracked; 155 PrismFontFactory factory; 156 WeakReference<PrismFontFile> refKey; 157 158 public FileDisposer(String fileName, boolean isTracked) { 159 this.fileName = fileName; 160 this.isTracked = isTracked; 161 } 162 163 public void setFactory(PrismFontFactory factory, 164 WeakReference<PrismFontFile> refKey) { 165 this.factory = factory; 166 this.refKey = refKey; 167 } 168 169 public synchronized void dispose() { 170 if (fileName != null) { 171 AccessController.doPrivileged( 172 (PrivilegedAction<Void>) () -> { 173 try { 174 File file = new File(fileName); 175 int size = (int)file.length(); 176 file.delete(); 177 // decrement tracker only after 178 // successful deletion. 179 if (isTracked) { 180 FontFileWriter.FontTracker. 181 getTracker().subBytes(size); 182 } 183 if (factory != null && refKey != null) { 184 Object o = refKey.get(); 185 if (o == null) { 186 factory.removeTmpFont(refKey); 187 factory = null; 188 refKey = null; 189 } 190 } 191 if (PrismFontFactory.debugFonts) { 192 System.err.println("FileDisposer=" + fileName); 193 } 194 } catch (Exception e) { 195 if (PrismFontFactory.debugFonts) { 196 e.printStackTrace(); 197 } 198 } 199 return null; 200 } 201 ); 202 fileName = null; 203 } 204 } 205 } 206 207 public String getFileName() { 208 return filename; 209 } 210 211 protected int getFileSize() { 212 return filesize; 213 } 214 215 protected int getFontIndex() { 216 return fontIndex; 217 } 218 219 public String getFullName() { 220 return fullName; 221 } 222 223 public String getPSName() { 224 if (psName == null) { 225 psName = fullName; 226 } 227 return psName; 228 } 229 230 public String getFamilyName() { 231 return familyName; 232 } 233 234 public String getStyleName() { 235 return styleName; 236 } 237 238 public String getLocaleFullName() { 239 return localeFullName; 240 } 241 242 public String getLocaleFamilyName() { 243 return localeFamilyName; 244 } 245 246 public String getLocaleStyleName() { 247 return localeStyleName; 248 } 249 250 /* 251 * Returns the features the font supports. 252 */ 253 public int getFeatures() { 254 //TODO check font file for features 255 return -1; 256 } 257 258 public Map getStrikeMap() { 259 return strikeMap; 260 } 261 262 protected abstract PrismFontStrike createStrike(float size, 263 BaseTransform transform, 264 int aaMode, 265 FontStrikeDesc desc); 266 267 public FontStrike getStrike(float size, BaseTransform transform, 268 int aaMode) { 269 FontStrikeDesc desc = new FontStrikeDesc(size, transform, aaMode); 270 WeakReference<PrismFontStrike> ref = strikeMap.get(desc); 271 PrismFontStrike strike = null; 272 if (ref != null) { 273 strike = ref.get(); 274 } 275 if (strike == null) { 276 strike = createStrike(size, transform, aaMode, desc); 277 DisposerRecord disposer = strike.getDisposer(); 278 if (disposer != null) { 279 ref = Disposer.addRecord(strike, disposer); 280 } else { 281 ref = new WeakReference<PrismFontStrike>(strike); 282 } 283 strikeMap.put(desc, ref); 284 } 285 return strike; 286 } 287 288 HashMap<Integer, int[]> bbCache = null; 289 static final int[] EMPTY_BOUNDS = new int[4]; 290 291 protected abstract int[] createGlyphBoundingBox(int gc); 292 293 @Override 294 public float[] getGlyphBoundingBox(int gc, float size, float[] retArr) { 295 if (retArr == null || retArr.length < 4) { 296 retArr = new float[4]; 297 } 298 if (gc >= getNumGlyphs()) { 299 retArr[0] = retArr[1] = retArr[2] = retArr[3] = 0; 300 return retArr; 301 } 302 if (bbCache == null) { 303 bbCache = new HashMap<Integer, int[]>(); 304 } 305 int[] bb = bbCache.get(gc); 306 if (bb == null) { 307 bb = createGlyphBoundingBox(gc); 308 if (bb == null) bb = EMPTY_BOUNDS; 309 bbCache.put(gc, bb); 310 } 311 float scale = size / getUnitsPerEm(); 312 retArr[0] = bb[0] * scale; 313 retArr[1] = bb[1] * scale; 314 retArr[2] = bb[2] * scale; 315 retArr[3] = bb[3] * scale; 316 return retArr; 317 } 318 319 int getNumGlyphs() { 320 if (numGlyphs == -1) { 321 Buffer buffer = readTable(maxpTag); 322 numGlyphs = buffer.getChar(4); // offset 4 bytes in MAXP table. 323 } 324 return numGlyphs; 325 } 326 327 protected boolean isCFF() { 328 return isCFF; 329 } 330 331 private Object peer; 332 public Object getPeer() { 333 return peer; 334 } 335 336 public void setPeer(Object peer) { 337 this.peer = peer; 338 } 339 340 synchronized Buffer readTable(int tag) { 341 Buffer buffer = null; 342 boolean openedFile = false; 343 try { 344 openedFile = filereader.openFile(); 345 DirectoryEntry tagDE = getDirectoryEntry(tag); 346 if (tagDE != null) { 347 buffer = filereader.readBlock(tagDE.offset, tagDE.length); 348 } 349 } catch (Exception e) { 350 if (PrismFontFactory.debugFonts) { 351 e.printStackTrace(); 352 } 353 } finally { 354 if (openedFile) { 355 try { 356 filereader.closeFile(); 357 } catch (Exception e2) { 358 } 359 } 360 } 361 return buffer; 362 } 363 364 int directoryCount = 1; 365 366 /** 367 * @return number of logical fonts. Is "1" for all but TTC files 368 */ 369 public int getFontCount() { 370 return directoryCount; 371 } 372 373 int numTables; 374 DirectoryEntry[] tableDirectory; 375 static class DirectoryEntry { 376 int tag; 377 int offset; 378 int length; 379 } 380 381 DirectoryEntry getDirectoryEntry(int tag) { 382 for (int i=0;i<numTables;i++) { 383 if (tableDirectory[i].tag == tag) { 384 return tableDirectory[i]; 385 } 386 } 387 return null; 388 } 389 390 /* Called from the constructor. Does the basic work of finding 391 * the right font in a TTC, the font names and enough info 392 * (the table offset directory) to be able to locate tables later. 393 * Throws an exception if it doesn't like what it finds. 394 */ 395 private void init(String name, int fIndex) throws Exception { 396 filereader = new FontFileReader(filename); 397 WoffDecoder decoder = null; 398 try { 399 if (!filereader.openFile()) { 400 throw new FileNotFoundException("Unable to create FontResource" 401 + " for file " + filename); 402 } 403 Buffer buffer = filereader.readBlock(0, TTCHEADERSIZE); 404 int sfntTag = buffer.getInt(); 405 406 /* Handle wOFF files */ 407 if (sfntTag == woffTag) { 408 decoder = new WoffDecoder(); 409 File file = decoder.openFile(); 410 decoder.decode(filereader); 411 decoder.closeFile(); 412 413 /* Create a new reader with the decoded file */ 414 filereader.closeFile(); 415 filereader = new FontFileReader(file.getPath()); 416 if (!filereader.openFile()) { 417 throw new FileNotFoundException("Unable to create " 418 + "FontResource for file " + filename); 419 } 420 buffer = filereader.readBlock(0, TTCHEADERSIZE); 421 sfntTag = buffer.getInt(); 422 } 423 424 filesize = (int)filereader.getLength(); 425 int headerOffset = 0; 426 if (sfntTag == ttcfTag) { 427 buffer.getInt(); // skip TTC version ID 428 directoryCount = buffer.getInt(); 429 if (fIndex >= directoryCount) { 430 throw new Exception("Bad collection index"); 431 } 432 fontIndex = fIndex; 433 buffer = filereader.readBlock(TTCHEADERSIZE+4*fIndex, 4); 434 headerOffset = buffer.getInt(); 435 buffer = filereader.readBlock(headerOffset, 4); 436 sfntTag = buffer.getInt(); 437 } 438 439 switch (sfntTag) { 440 case v1ttTag: 441 case trueTag: 442 break; 443 444 case ottoTag: 445 isCFF = true; 446 break; 447 448 default: 449 throw new Exception("Unsupported sfnt " + filename); 450 } 451 452 /* Now have the offset of this TT font (possibly within a TTC) 453 * After the TT version/scaler type field, is the short 454 * representing the number of tables in the table directory. 455 * The table directory begins at 12 bytes after the header. 456 * Each table entry is 16 bytes long (4 32-bit ints) 457 */ 458 buffer = filereader.readBlock(headerOffset+4, 2); 459 numTables = buffer.getShort(); 460 int directoryOffset = headerOffset+DIRECTORYHEADERSIZE; 461 Buffer ibuffer = filereader. 462 readBlock(directoryOffset, numTables*DIRECTORYENTRYSIZE); 463 DirectoryEntry table; 464 tableDirectory = new DirectoryEntry[numTables]; 465 for (int i=0; i<numTables;i++) { 466 tableDirectory[i] = table = new DirectoryEntry(); 467 table.tag = ibuffer.getInt(); 468 /* checksum */ ibuffer.skip(4); 469 table.offset = ibuffer.getInt(); 470 table.length = ibuffer.getInt(); 471 if (table.offset + table.length > filesize) { 472 throw new Exception("bad table, tag="+table.tag); 473 } 474 } 475 476 DirectoryEntry headDE = getDirectoryEntry(headTag); 477 Buffer headTable = filereader.readBlock(headDE.offset, 478 headDE.length); 479 // Important font attribute must be set in order to prevent div by zero 480 upem = (float)(headTable.getShort(18) & 0xffff); 481 if (!(16 <= upem && upem <= 16384)) { 482 upem = 2048; 483 } 484 485 indexToLocFormat = headTable.getShort(50); 486 // 0 for short offsets, 1 for long 487 if (indexToLocFormat < 0 || indexToLocFormat > 1) { 488 throw new Exception("Bad indexToLocFormat"); 489 } 490 491 // In a conventional optimised layout, the 492 // hhea table immediately follows the 'head' table. 493 Buffer hhea = readTable(hheaTag); 494 if (hhea == null) { 495 numHMetrics = -1; 496 } else { 497 // the font table has the sign of ascent and descent 498 // reversed from our coordinate system. 499 ascent = -(float)hhea.getShort(4); 500 descent = -(float)hhea.getShort(6); 501 linegap = (float)hhea.getShort(8); 502 // advanceWidthMax is max horizontal advance of all glyphs in 503 // font. For some fonts advanceWidthMax is much larger then "M" 504 // advanceWidthMax = (float)hhea.getChar(10); 505 numHMetrics = hhea.getChar(34) & 0xffff; 506 } 507 508 // maxp table is before the OS/2 table. Read it now 509 // while file is open - will be very cheap as its just 510 // 32 bytes and we already have it in a byte[]. 511 getNumGlyphs(); 512 513 setStyle(); 514 515 /* Get names last, as the name table is far from the file header. 516 * Although its also likely too big to fit in the read cache 517 * in which case that would remain valid, but also will help 518 * any file read implementation which doesn't have random access. 519 */ 520 initNames(); 521 522 if (familyName == null || fullName == null) { 523 String fontName = name != null ? name : ""; 524 if (fullName == null) { 525 fullName = familyName != null ? familyName : fontName; 526 } 527 if (familyName == null) { 528 familyName = fullName != null ? fullName : fontName; 529 } 530 throw new Exception("Font name not found."); 531 } 532 533 /* update the font resource only if the file was decoded 534 * and initialized successfully. 535 */ 536 if (decoder != null) { 537 isDecoded = true; 538 filename = filereader.getFilename(); 539 PrismFontFactory.getFontFactory().addDecodedFont(this); 540 } 541 } catch (Exception e) { 542 if (decoder != null) { 543 decoder.deleteFile(); 544 } 545 throw e; 546 } finally { 547 filereader.closeFile(); 548 } 549 } 550 551 /* TrueTypeFont can use the fsSelection fields of OS/2 table 552 * or macStyleBits of the 'head' table to determine the style. 553 */ 554 private static final int fsSelectionItalicBit = 0x00001; 555 private static final int fsSelectionBoldBit = 0x00020; 556 557 private static final int MACSTYLE_BOLD_BIT = 0x1; 558 private static final int MACSTYLE_ITALIC_BIT = 0x2; 559 560 // Comment out some of this until we have both a need and a way to use it. 561 // private int embeddingInfo; 562 //private int fontWeight; 563 private boolean isBold; 564 private boolean isItalic; 565 private float upem; 566 private float ascent, descent, linegap; // in design units 567 private int numHMetrics; 568 569 private void setStyle() { 570 // A number of fonts on Mac OS X do not have an OS/2 571 // table. For those need to get info from a different source. 572 DirectoryEntry os2_DE = getDirectoryEntry(os_2Tag); 573 if (os2_DE != null) { 574 // os2 Table ver 4 DataType Offset 575 //version USHORT 0 576 //xAvgCharWidth SHORT 2 577 //usWeightClass USHORT 4 578 //usWidthClass USHORT 6 579 //fsType USHORT 8 580 //ySubscriptXSize SHORT 10 581 //ySubscriptYSize SHORT 12 582 //ySubscriptXOffset SHORT 14 583 //ySubscriptYOffset SHORT 16 584 //ySuperscriptXSize SHORT 18 585 //ySuperscriptYSize SHORT 20 586 //ySuperscriptXOffset SHORT 22 587 //ySuperscriptYOffset SHORT 24 588 //yStrikeoutSize SHORT 26 589 //yStrikeoutPosition SHORT 28 590 //sFamilyClass SHORT 30 591 //panose[10] BYTE 32 592 //ulUnicodeRange1 ULONG 42 593 //ulUnicodeRange2 ULONG 46 594 //ulUnicodeRange3 ULONG 50 595 //ulUnicodeRange4 ULONG 54 596 //achVendID[4] CHAR 58 597 //fsSelection USHORT 62 598 //usFirstCharIndex USHORT 64 599 //usLastCharIndex USHORT 66 600 //sTypoAscender SHORT 68 601 //sTypoDescender SHORT 70 602 //sTypoLineGap SHORT 72 603 //usWinAscent USHORT 74 604 //usWinDescent USHORT 76 605 //ulCodePageRange1 ULONG 78 606 //ulCodePageRange2 ULONG 82 607 //sxHeight SHORT 86 608 //sCapHeight SHORT 88 609 //usDefaultChar USHORT 90 610 //usBreakChar USHORT 92 611 //usMaxContext USHORT 94 612 613 Buffer os_2Table = filereader.readBlock(os2_DE.offset, 614 os2_DE.length); 615 int fsSelection = os_2Table.getChar(62) & 0xffff; 616 isItalic = (fsSelection & fsSelectionItalicBit) != 0; 617 isBold = (fsSelection & fsSelectionBoldBit) != 0; 618 } else { 619 DirectoryEntry headDE = getDirectoryEntry(headTag); 620 Buffer headTable = filereader.readBlock(headDE.offset, 621 headDE.length); 622 short macStyleBits = headTable.getShort(44); 623 isItalic = (macStyleBits & MACSTYLE_ITALIC_BIT) != 0; 624 isBold = (macStyleBits & MACSTYLE_BOLD_BIT) != 0; 625 } 626 } 627 628 public boolean isBold() { 629 return isBold; 630 } 631 632 public boolean isItalic() { 633 return isItalic; 634 } 635 636 public boolean isDecoded() { 637 return isDecoded; 638 } 639 640 public boolean isRegistered() { 641 return isRegistered; 642 } 643 644 public boolean isEmbeddedFont() { 645 return isEmbedded; 646 } 647 648 /** 649 * per the OT spec. this is an unsigned short. 650 */ 651 public int getUnitsPerEm() { 652 return (int)upem; 653 } 654 655 public short getIndexToLocFormat() { 656 return indexToLocFormat; 657 } 658 659 /** 660 * per the OT spec. this is an unsigned short. 661 */ 662 public int getNumHMetrics() { 663 return numHMetrics; 664 } 665 666 /* -- ID's used in the 'name' table */ 667 public static final int MAC_PLATFORM_ID = 1; 668 public static final int MACROMAN_SPECIFIC_ID = 0; 669 public static final int MACROMAN_ENGLISH_LANG = 0; 670 671 public static final int MS_PLATFORM_ID = 3; 672 /* MS locale id for US English is the "default" */ 673 public static final short MS_ENGLISH_LOCALE_ID = 0x0409; // 1033 decimal 674 public static final int FAMILY_NAME_ID = 1; 675 public static final int STYLE_NAME_ID = 2; 676 public static final int FULL_NAME_ID = 4; 677 public static final int PS_NAME_ID = 6; 678 679 void initNames() throws Exception { 680 byte[] name = new byte[256]; 681 682 DirectoryEntry nameDE = getDirectoryEntry(nameTag); 683 Buffer buffer = filereader.readBlock(nameDE.offset, nameDE.length); 684 685 buffer.skip(2); // format - not needed. 686 short numRecords = buffer.getShort(); 687 /* The name table uses unsigned shorts. Many of these 688 * are known small values that fit in a short. 689 * The values that are sizes or offsets into the table could be 690 * greater than 32767, so read and store those as ints 691 */ 692 int stringPtr = buffer.getShort() & 0xffff; 693 694 /* Microsoft Windows font names are preferred but numerous Mac 695 * fonts do not have these, so we must also accept these in the 696 * absence of the preferred Windows names. 697 */ 698 for (int i=0; i<numRecords; i++) { 699 short platformID = buffer.getShort(); 700 if (platformID != MS_PLATFORM_ID && 701 platformID != MAC_PLATFORM_ID) { 702 buffer.skip(10); 703 continue; // skip over this record. 704 } 705 short encodingID = buffer.getShort(); 706 // only want UTF-16 (inc. symbol) encodingIDs for Windows, 707 // or MacRoman on Mac. 708 if ((platformID == MS_PLATFORM_ID && encodingID > 1) || 709 (platformID == MAC_PLATFORM_ID && 710 encodingID != MACROMAN_SPECIFIC_ID)) { 711 buffer.skip(8); 712 continue; 713 } 714 short langID = buffer.getShort(); 715 if (platformID == MAC_PLATFORM_ID && 716 langID != MACROMAN_ENGLISH_LANG) { 717 buffer.skip(6); 718 continue; 719 } 720 short nameID = buffer.getShort(); 721 int nameLen = ((int)buffer.getShort()) & 0xffff; 722 int namePtr = (((int)buffer.getShort()) & 0xffff) + stringPtr; 723 String tmpName = null; 724 String enc; 725 switch (nameID) { 726 727 case FAMILY_NAME_ID: 728 729 if (familyName == null || langID == MS_ENGLISH_LOCALE_ID || 730 langID == nameLocaleID) 731 { 732 buffer.get(namePtr, name, 0, nameLen); 733 if (platformID == MAC_PLATFORM_ID) { 734 enc = "US-ASCII"; 735 } else { 736 enc = "UTF-16BE"; 737 } 738 tmpName = new String(name, 0, nameLen, enc); 739 740 if (familyName == null || 741 langID == MS_ENGLISH_LOCALE_ID){ 742 familyName = tmpName; 743 } 744 if (langID == nameLocaleID) { 745 localeFamilyName = tmpName; 746 } 747 } 748 break; 749 750 case FULL_NAME_ID: 751 752 if (fullName == null || 753 langID == MS_ENGLISH_LOCALE_ID || 754 langID == nameLocaleID) 755 { 756 buffer.get(namePtr, name, 0, nameLen); 757 if (platformID == MAC_PLATFORM_ID) { 758 enc = "US-ASCII"; 759 } else { 760 enc = "UTF-16BE"; 761 } 762 tmpName = new String(name, 0, nameLen, enc); 763 764 if (fullName == null || 765 langID == MS_ENGLISH_LOCALE_ID) { 766 fullName = tmpName; 767 } 768 if (langID == nameLocaleID) { 769 localeFullName = tmpName; 770 } 771 } 772 break; 773 774 case PS_NAME_ID: 775 776 if (psName == null) { 777 buffer.get(namePtr, name, 0, nameLen); 778 if (platformID == MAC_PLATFORM_ID) { 779 enc = "US-ASCII"; 780 } else { 781 enc = "UTF-16BE"; 782 } 783 psName = new String(name, 0, nameLen, enc); 784 } 785 break; 786 787 case STYLE_NAME_ID: 788 789 if (styleName == null || 790 langID == MS_ENGLISH_LOCALE_ID || 791 langID == nameLocaleID) 792 { 793 buffer.get(namePtr, name, 0, nameLen); 794 if (platformID == MAC_PLATFORM_ID) { 795 enc = "US-ASCII"; 796 } else { 797 enc = "UTF-16BE"; 798 } 799 tmpName = new String(name, 0, nameLen, enc); 800 801 if (styleName == null || 802 langID == MS_ENGLISH_LOCALE_ID) { 803 styleName = tmpName; 804 } 805 if (langID == nameLocaleID) { 806 localeStyleName = tmpName; 807 } 808 } 809 break; 810 811 default: 812 break; 813 } 814 815 if (localeFamilyName == null) { 816 localeFamilyName = familyName; 817 } 818 if (localeFullName == null) { 819 localeFullName = fullName; 820 } 821 if (localeStyleName == null) { 822 localeStyleName = styleName; 823 } 824 } 825 } 826 827 /*** BEGIN LOCALE_ID MAPPING ****/ 828 829 private static Map<String, Short> lcidMap; 830 831 // Return a Microsoft LCID from the given Locale. 832 // Used when getting localized font data. 833 834 private static void addLCIDMapEntry(Map<String, Short> map, 835 String key, short value) { 836 map.put(key, Short.valueOf(value)); 837 } 838 839 private static synchronized void createLCIDMap() { 840 if (lcidMap != null) { 841 return; 842 } 843 844 Map<String, Short> map = new HashMap<String, Short>(200); 845 addLCIDMapEntry(map, "ar", (short) 0x0401); 846 addLCIDMapEntry(map, "bg", (short) 0x0402); 847 addLCIDMapEntry(map, "ca", (short) 0x0403); 848 addLCIDMapEntry(map, "zh", (short) 0x0404); 849 addLCIDMapEntry(map, "cs", (short) 0x0405); 850 addLCIDMapEntry(map, "da", (short) 0x0406); 851 addLCIDMapEntry(map, "de", (short) 0x0407); 852 addLCIDMapEntry(map, "el", (short) 0x0408); 853 addLCIDMapEntry(map, "es", (short) 0x040a); 854 addLCIDMapEntry(map, "fi", (short) 0x040b); 855 addLCIDMapEntry(map, "fr", (short) 0x040c); 856 addLCIDMapEntry(map, "iw", (short) 0x040d); 857 addLCIDMapEntry(map, "hu", (short) 0x040e); 858 addLCIDMapEntry(map, "is", (short) 0x040f); 859 addLCIDMapEntry(map, "it", (short) 0x0410); 860 addLCIDMapEntry(map, "ja", (short) 0x0411); 861 addLCIDMapEntry(map, "ko", (short) 0x0412); 862 addLCIDMapEntry(map, "nl", (short) 0x0413); 863 addLCIDMapEntry(map, "no", (short) 0x0414); 864 addLCIDMapEntry(map, "pl", (short) 0x0415); 865 addLCIDMapEntry(map, "pt", (short) 0x0416); 866 addLCIDMapEntry(map, "rm", (short) 0x0417); 867 addLCIDMapEntry(map, "ro", (short) 0x0418); 868 addLCIDMapEntry(map, "ru", (short) 0x0419); 869 addLCIDMapEntry(map, "hr", (short) 0x041a); 870 addLCIDMapEntry(map, "sk", (short) 0x041b); 871 addLCIDMapEntry(map, "sq", (short) 0x041c); 872 addLCIDMapEntry(map, "sv", (short) 0x041d); 873 addLCIDMapEntry(map, "th", (short) 0x041e); 874 addLCIDMapEntry(map, "tr", (short) 0x041f); 875 addLCIDMapEntry(map, "ur", (short) 0x0420); 876 addLCIDMapEntry(map, "in", (short) 0x0421); 877 addLCIDMapEntry(map, "uk", (short) 0x0422); 878 addLCIDMapEntry(map, "be", (short) 0x0423); 879 addLCIDMapEntry(map, "sl", (short) 0x0424); 880 addLCIDMapEntry(map, "et", (short) 0x0425); 881 addLCIDMapEntry(map, "lv", (short) 0x0426); 882 addLCIDMapEntry(map, "lt", (short) 0x0427); 883 addLCIDMapEntry(map, "fa", (short) 0x0429); 884 addLCIDMapEntry(map, "vi", (short) 0x042a); 885 addLCIDMapEntry(map, "hy", (short) 0x042b); 886 addLCIDMapEntry(map, "eu", (short) 0x042d); 887 addLCIDMapEntry(map, "mk", (short) 0x042f); 888 addLCIDMapEntry(map, "tn", (short) 0x0432); 889 addLCIDMapEntry(map, "xh", (short) 0x0434); 890 addLCIDMapEntry(map, "zu", (short) 0x0435); 891 addLCIDMapEntry(map, "af", (short) 0x0436); 892 addLCIDMapEntry(map, "ka", (short) 0x0437); 893 addLCIDMapEntry(map, "fo", (short) 0x0438); 894 addLCIDMapEntry(map, "hi", (short) 0x0439); 895 addLCIDMapEntry(map, "mt", (short) 0x043a); 896 addLCIDMapEntry(map, "se", (short) 0x043b); 897 addLCIDMapEntry(map, "gd", (short) 0x043c); 898 addLCIDMapEntry(map, "ms", (short) 0x043e); 899 addLCIDMapEntry(map, "kk", (short) 0x043f); 900 addLCIDMapEntry(map, "ky", (short) 0x0440); 901 addLCIDMapEntry(map, "sw", (short) 0x0441); 902 addLCIDMapEntry(map, "tt", (short) 0x0444); 903 addLCIDMapEntry(map, "bn", (short) 0x0445); 904 addLCIDMapEntry(map, "pa", (short) 0x0446); 905 addLCIDMapEntry(map, "gu", (short) 0x0447); 906 addLCIDMapEntry(map, "ta", (short) 0x0449); 907 addLCIDMapEntry(map, "te", (short) 0x044a); 908 addLCIDMapEntry(map, "kn", (short) 0x044b); 909 addLCIDMapEntry(map, "ml", (short) 0x044c); 910 addLCIDMapEntry(map, "mr", (short) 0x044e); 911 addLCIDMapEntry(map, "sa", (short) 0x044f); 912 addLCIDMapEntry(map, "mn", (short) 0x0450); 913 addLCIDMapEntry(map, "cy", (short) 0x0452); 914 addLCIDMapEntry(map, "gl", (short) 0x0456); 915 addLCIDMapEntry(map, "dv", (short) 0x0465); 916 addLCIDMapEntry(map, "qu", (short) 0x046b); 917 addLCIDMapEntry(map, "mi", (short) 0x0481); 918 addLCIDMapEntry(map, "ar_IQ", (short) 0x0801); 919 addLCIDMapEntry(map, "zh_CN", (short) 0x0804); 920 addLCIDMapEntry(map, "de_CH", (short) 0x0807); 921 addLCIDMapEntry(map, "en_GB", (short) 0x0809); 922 addLCIDMapEntry(map, "es_MX", (short) 0x080a); 923 addLCIDMapEntry(map, "fr_BE", (short) 0x080c); 924 addLCIDMapEntry(map, "it_CH", (short) 0x0810); 925 addLCIDMapEntry(map, "nl_BE", (short) 0x0813); 926 addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814); 927 addLCIDMapEntry(map, "pt_PT", (short) 0x0816); 928 addLCIDMapEntry(map, "ro_MD", (short) 0x0818); 929 addLCIDMapEntry(map, "ru_MD", (short) 0x0819); 930 addLCIDMapEntry(map, "sr_CS", (short) 0x081a); 931 addLCIDMapEntry(map, "sv_FI", (short) 0x081d); 932 addLCIDMapEntry(map, "az_AZ", (short) 0x082c); 933 addLCIDMapEntry(map, "se_SE", (short) 0x083b); 934 addLCIDMapEntry(map, "ga_IE", (short) 0x083c); 935 addLCIDMapEntry(map, "ms_BN", (short) 0x083e); 936 addLCIDMapEntry(map, "uz_UZ", (short) 0x0843); 937 addLCIDMapEntry(map, "qu_EC", (short) 0x086b); 938 addLCIDMapEntry(map, "ar_EG", (short) 0x0c01); 939 addLCIDMapEntry(map, "zh_HK", (short) 0x0c04); 940 addLCIDMapEntry(map, "de_AT", (short) 0x0c07); 941 addLCIDMapEntry(map, "en_AU", (short) 0x0c09); 942 addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c); 943 addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a); 944 addLCIDMapEntry(map, "se_FI", (short) 0x0c3b); 945 addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b); 946 addLCIDMapEntry(map, "ar_LY", (short) 0x1001); 947 addLCIDMapEntry(map, "zh_SG", (short) 0x1004); 948 addLCIDMapEntry(map, "de_LU", (short) 0x1007); 949 addLCIDMapEntry(map, "en_CA", (short) 0x1009); 950 addLCIDMapEntry(map, "es_GT", (short) 0x100a); 951 addLCIDMapEntry(map, "fr_CH", (short) 0x100c); 952 addLCIDMapEntry(map, "hr_BA", (short) 0x101a); 953 addLCIDMapEntry(map, "ar_DZ", (short) 0x1401); 954 addLCIDMapEntry(map, "zh_MO", (short) 0x1404); 955 addLCIDMapEntry(map, "de_LI", (short) 0x1407); 956 addLCIDMapEntry(map, "en_NZ", (short) 0x1409); 957 addLCIDMapEntry(map, "es_CR", (short) 0x140a); 958 addLCIDMapEntry(map, "fr_LU", (short) 0x140c); 959 addLCIDMapEntry(map, "bs_BA", (short) 0x141a); 960 addLCIDMapEntry(map, "ar_MA", (short) 0x1801); 961 addLCIDMapEntry(map, "en_IE", (short) 0x1809); 962 addLCIDMapEntry(map, "es_PA", (short) 0x180a); 963 addLCIDMapEntry(map, "fr_MC", (short) 0x180c); 964 addLCIDMapEntry(map, "sr_BA", (short) 0x181a); 965 addLCIDMapEntry(map, "ar_TN", (short) 0x1c01); 966 addLCIDMapEntry(map, "en_ZA", (short) 0x1c09); 967 addLCIDMapEntry(map, "es_DO", (short) 0x1c0a); 968 addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a); 969 addLCIDMapEntry(map, "ar_OM", (short) 0x2001); 970 addLCIDMapEntry(map, "en_JM", (short) 0x2009); 971 addLCIDMapEntry(map, "es_VE", (short) 0x200a); 972 addLCIDMapEntry(map, "ar_YE", (short) 0x2401); 973 addLCIDMapEntry(map, "es_CO", (short) 0x240a); 974 addLCIDMapEntry(map, "ar_SY", (short) 0x2801); 975 addLCIDMapEntry(map, "en_BZ", (short) 0x2809); 976 addLCIDMapEntry(map, "es_PE", (short) 0x280a); 977 addLCIDMapEntry(map, "ar_JO", (short) 0x2c01); 978 addLCIDMapEntry(map, "en_TT", (short) 0x2c09); 979 addLCIDMapEntry(map, "es_AR", (short) 0x2c0a); 980 addLCIDMapEntry(map, "ar_LB", (short) 0x3001); 981 addLCIDMapEntry(map, "en_ZW", (short) 0x3009); 982 addLCIDMapEntry(map, "es_EC", (short) 0x300a); 983 addLCIDMapEntry(map, "ar_KW", (short) 0x3401); 984 addLCIDMapEntry(map, "en_PH", (short) 0x3409); 985 addLCIDMapEntry(map, "es_CL", (short) 0x340a); 986 addLCIDMapEntry(map, "ar_AE", (short) 0x3801); 987 addLCIDMapEntry(map, "es_UY", (short) 0x380a); 988 addLCIDMapEntry(map, "ar_BH", (short) 0x3c01); 989 addLCIDMapEntry(map, "es_PY", (short) 0x3c0a); 990 addLCIDMapEntry(map, "ar_QA", (short) 0x4001); 991 addLCIDMapEntry(map, "es_BO", (short) 0x400a); 992 addLCIDMapEntry(map, "es_SV", (short) 0x440a); 993 addLCIDMapEntry(map, "es_HN", (short) 0x480a); 994 addLCIDMapEntry(map, "es_NI", (short) 0x4c0a); 995 addLCIDMapEntry(map, "es_PR", (short) 0x500a); 996 997 lcidMap = map; 998 } 999 1000 private static short getLCIDFromLocale(Locale locale) { 1001 // optimize for common case 1002 if (locale.equals(Locale.US) || locale.getLanguage().equals("en")) { 1003 return MS_ENGLISH_LOCALE_ID; 1004 } 1005 1006 if (lcidMap == null) { 1007 createLCIDMap(); 1008 } 1009 1010 String key = locale.toString(); 1011 while (!key.isEmpty()) { 1012 Short lcidObject = (Short) lcidMap.get(key); 1013 if (lcidObject != null) { 1014 return lcidObject.shortValue(); 1015 } 1016 int pos = key.lastIndexOf('_'); 1017 if (pos < 1) { 1018 return MS_ENGLISH_LOCALE_ID; 1019 } 1020 key = key.substring(0, pos); 1021 } 1022 1023 return MS_ENGLISH_LOCALE_ID; 1024 } 1025 1026 1027 /* On Windows this is set to the System Locale, which matches how 1028 * GDI enumerates font names. For display purposes we may want 1029 * the user locale which could be different. 1030 */ 1031 static short nameLocaleID = getSystemLCID(); 1032 1033 private static short getSystemLCID() { 1034 if (PrismFontFactory.isWindows) { 1035 return PrismFontFactory.getSystemLCID(); 1036 } else { 1037 return getLCIDFromLocale(Locale.getDefault()); 1038 } 1039 } 1040 1041 private OpenTypeGlyphMapper mapper = null; 1042 1043 public CharToGlyphMapper getGlyphMapper() { 1044 if (mapper == null) { 1045 mapper = new OpenTypeGlyphMapper(this); 1046 } 1047 return mapper; 1048 } 1049 1050 public FontStrike getStrike(float size, BaseTransform transform) { 1051 return getStrike(size, transform, getDefaultAAMode()); 1052 } 1053 1054 char[] advanceWidths = null; 1055 /* 1056 * This is returning the unhinted advance, should be OK so 1057 * long as we do unhinted rendering. If we are doing hinted glyphs 1058 * and I suppose, integer metrics, then we can use the hdmx table. 1059 * But since the hdmx table doesn't provide anything except integers 1060 * it will only be useful for some cases. Also even then the ptSize 1061 * alone doesn't help, since we need to know the graphics scale 1062 * to know the real glyph size that's required, then of course we 1063 * have to translate that back into user space. So all of that will 1064 * need to be looked into, or we reserve this path for unhinted rendering. 1065 * Note that if there's no hdmx entry for a given size, then we need 1066 * to scale the glyph to get the hinted advance. However before doing 1067 * so we should consult the 'gasp' table to see it its a size at 1068 * which hinting should be performed anyway. 1069 * (1) The GASP table indicates size at which hinting should be applied 1070 * usually this is all larger sizes so probably wouldn't help, however 1071 * (2) If there is a LTSH (Linear Threshold) table, we can use that 1072 * to see if for the requested 'ppem' size, the glyph scales linearly. 1073 * 1074 * Interestingly Amble sets the 'head' flags bit to say non-linear 1075 * scaling and so legitimately has a LTSH table but this all may be 1076 * a hold-over from when its gasp table said to apply hints at some sizes. 1077 * I suppose I am not 100% certain if the gasp table can be trusted to 1078 * use as a short-cut for when you don't need to scale, or if choosing 1079 * not to hint means you can always just assume linear scaling, but I 1080 * do find that to be consistent with the data in Microsoft fonts where 1081 * they do not provide hdmx entry for sizes below that where hinting is 1082 * required, suggesting the htmx table is fine for such cases. 1083 */ 1084 public float getAdvance(int glyphCode, float ptSize) { 1085 if (glyphCode == CharToGlyphMapper.INVISIBLE_GLYPH_ID) 1086 return 0f; 1087 1088 // If we haven't initialised yet, do so now. 1089 if (advanceWidths == null && numHMetrics > 0) { 1090 synchronized (this) { 1091 Buffer hmtx = readTable(hmtxTag); 1092 if (hmtx == null) { 1093 numHMetrics = -1; 1094 return 0; 1095 } 1096 char[] aw = new char[numHMetrics]; 1097 for (int i=0; i<numHMetrics; i++) { 1098 aw[i] = hmtx.getChar(i*4); 1099 } 1100 advanceWidths = aw; 1101 } 1102 } 1103 1104 // If we have a valid numHMetrics, look up the advance 1105 if (numHMetrics > 0) { 1106 char cadv; 1107 if (glyphCode < numHMetrics) { 1108 cadv = advanceWidths[glyphCode]; 1109 } else { 1110 cadv = advanceWidths[numHMetrics-1]; 1111 } 1112 return ((float)(cadv & 0xffff)*ptSize)/upem; 1113 } else { // no valid lookup. 1114 return 0f; 1115 } 1116 } 1117 1118 public PrismMetrics getFontMetrics(float ptSize) { 1119 return new PrismMetrics((ascent*ptSize)/upem, 1120 (descent*ptSize)/upem, 1121 (linegap*ptSize)/upem, 1122 this, ptSize); 1123 } 1124 1125 private float[] styleMetrics; 1126 float[] getStyleMetrics(float ptSize) { 1127 if (styleMetrics == null) { 1128 float [] smetrics = new float[METRICS_TOTAL]; 1129 1130 Buffer os_2 = readTable(os_2Tag); 1131 int length = os_2 != null ? os_2.capacity() : 0; 1132 1133 if (length >= 30) { 1134 smetrics[STRIKETHROUGH_THICKNESS] = os_2.getShort(26) / upem; 1135 smetrics[STRIKETHROUGH_OFFSET] = -os_2.getShort(28) / upem; 1136 } else { 1137 smetrics[STRIKETHROUGH_THICKNESS] = 0.05f; 1138 smetrics[STRIKETHROUGH_OFFSET] = -0.4f; 1139 } 1140 if (length >= 74) { 1141 // ascent, descent, leading are set in constructor 1142 smetrics[TYPO_ASCENT] = -os_2.getShort(68) / upem; 1143 smetrics[TYPO_DESCENT] = -os_2.getShort(70) / upem; 1144 smetrics[TYPO_LINEGAP] = os_2.getShort(72) / upem; 1145 } else { 1146 smetrics[TYPO_ASCENT] = ascent / upem; 1147 smetrics[TYPO_DESCENT] = descent / upem; 1148 smetrics[TYPO_LINEGAP] = linegap / upem; 1149 } 1150 // REMIND : OpenType spec introduced xHeight, many fonts 1151 // won't have this info. 1152 // xHeight should be available in OS2 font table ver. 3 or greater 1153 if (length >= 90) { 1154 smetrics[XHEIGHT] = os_2.getShort(86) / upem; 1155 smetrics[CAPHEIGHT] = os_2.getShort(88); 1156 1157 /* Some fonts have bad values for capHeight. For example, 1158 * Comic Sans MS. The fix is to ignore the capHeight in the 1159 * font file when it is less than half of the ascent */ 1160 if ((smetrics[CAPHEIGHT] / ascent) < 0.5) { 1161 smetrics[CAPHEIGHT] = 0; 1162 } else { 1163 smetrics[CAPHEIGHT] /= upem; 1164 } 1165 } 1166 1167 if (smetrics[XHEIGHT] == 0 || smetrics[CAPHEIGHT] == 0) { 1168 FontStrike strike = getStrike(ptSize, BaseTransform.IDENTITY_TRANSFORM); 1169 CharToGlyphMapper mapper = getGlyphMapper(); 1170 int missingGlyph = mapper.getMissingGlyphCode(); 1171 1172 if (smetrics[XHEIGHT] == 0) { 1173 int gc = mapper.charToGlyph('x'); 1174 if (gc != missingGlyph) { 1175 RectBounds fbds = strike.getGlyph(gc).getBBox(); 1176 smetrics[XHEIGHT] = fbds.getHeight() / ptSize; 1177 } else { 1178 smetrics[XHEIGHT] = -ascent * 0.6f / upem; 1179 } 1180 } 1181 if (smetrics[CAPHEIGHT] == 0) { 1182 int gc = mapper.charToGlyph('H'); 1183 if (gc != missingGlyph) { 1184 RectBounds fbds = strike.getGlyph(gc).getBBox(); 1185 smetrics[CAPHEIGHT] = fbds.getHeight() / ptSize; 1186 } else { 1187 smetrics[CAPHEIGHT] = -ascent * 0.9f / upem; 1188 } 1189 } 1190 } 1191 1192 Buffer postTable = readTable(postTag); 1193 if (postTable == null || postTable.capacity() < 12) { 1194 smetrics[UNDERLINE_OFFSET] = 0.1f; 1195 smetrics[UNDERLINE_THICKESS] = 0.05f; 1196 } else { 1197 smetrics[UNDERLINE_OFFSET] = -postTable.getShort(8) / upem; 1198 smetrics[UNDERLINE_THICKESS] = postTable.getShort(10) / upem; 1199 } 1200 styleMetrics = smetrics; 1201 } 1202 1203 float[] metrics = new float[METRICS_TOTAL]; 1204 for (int i = 0; i < METRICS_TOTAL; i++) { 1205 metrics[i] = styleMetrics[i] * ptSize; 1206 } 1207 1208 return metrics; 1209 } 1210 1211 byte[] getTableBytes(int tag) { 1212 Buffer buffer = readTable(tag); 1213 byte[] table = null; 1214 if(buffer != null){ 1215 table = new byte[buffer.capacity()]; 1216 buffer.get(0, table, 0, buffer.capacity()); 1217 } 1218 return table; 1219 } 1220 1221 }