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 }