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