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