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