/* * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.javafx.font; import java.lang.ref.WeakReference; import java.io.File; import java.io.FileNotFoundException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import com.sun.javafx.geom.RectBounds; import com.sun.javafx.geom.transform.BaseTransform; import com.sun.javafx.font.FontFileReader.Buffer; import static com.sun.javafx.font.PrismMetrics.*; public abstract class PrismFontFile implements FontResource, FontConstants { private int fontInstallationType = -1; // unknown, 0=embedded, 1=system // TrueType fonts can have multiple names, most notably split up by // platform and locale. Whilst fonts that have different names for // different platforms are arguable buggy, those with localised names // are not. This can cause problems. Suppose that a font has English, // French and German names, and the platform enumerates the name that // is most appropriate for the user locale. Then suppose a French // developer uses the French name, but for his German user this font // is not located by the platform because it reports the German name // for that font. At runtime we no longer have any connection to the // locale of the developer so we can't look for the name for that // locale, even if the platform have us a performant option for that. // // The English name which some might think is supposed // to be the interoperable name is not treated at all specially in // the font format and doesn't even come up for either the user or // the developer, and in fact doesn't even have to be present. // Having said that we'll probably have the best luck for most users // and fonts by assuming the English name if the locale name doesn't // work. But either way, without platform API support for this // then its really expensive as all font files need to be opened. // String familyName; /* Family font name (English) */ String fullName; /* Full font name (English) */ String psName; /* PostScript font name */ String localeFamilyName; String localeFullName; String styleName; String localeStyleName; String filename; int filesize; FontFileReader filereader; int numGlyphs = -1; short indexToLocFormat; int fontIndex; // into a TTC. boolean isCFF; boolean isEmbedded = false; boolean isCopy = false; boolean isTracked = false; boolean isDecoded = false; boolean isRegistered = true; /* The glyph image data is stored only in a texture, and we * manage how much of that is kept around. We clearly want * to keep a reference to the strike that created that data. */ Map> strikeMap = new ConcurrentHashMap>(); protected PrismFontFile(String name, String filename, int fIndex, boolean register, boolean embedded, boolean copy, boolean tracked) throws Exception { this.filename = filename; this.isRegistered = register; this.isEmbedded = embedded; this.isCopy = copy; this.isTracked = tracked; init(name, fIndex); } WeakReference createFileDisposer(PrismFontFactory factory) { FileDisposer disposer = new FileDisposer(filename, isTracked); WeakReference ref = Disposer.addRecord(this, disposer); disposer.setFactory(factory, ref); return ref; } void setIsDecoded(boolean decoded) { isDecoded = decoded; } /* This is called only for fonts where a temp file was created */ protected synchronized void disposeOnShutdown() { if (isCopy || isDecoded) { AccessController.doPrivileged( (PrivilegedAction) () -> { try { (new File(filename)).delete(); /* Embedded fonts (copy) can also be decoded. * Set both flags to false to avoid double deletes. */ isCopy = isDecoded = false; } catch (Exception e) { } return null; } ); if (PrismFontFactory.debugFonts) { System.err.println("Temp file deleted: " + filename); } } } public int getDefaultAAMode() { return AA_GREYSCALE; } public boolean isInstalledFont() { if (fontInstallationType == -1) { PrismFontFactory factory = PrismFontFactory.getFontFactory(); fontInstallationType = factory.isInstalledFont(filename) ? 1 : 0; } return fontInstallationType > 0; } static class FileDisposer implements DisposerRecord { String fileName; boolean isTracked; PrismFontFactory factory; WeakReference refKey; public FileDisposer(String fileName, boolean isTracked) { this.fileName = fileName; this.isTracked = isTracked; } public void setFactory(PrismFontFactory factory, WeakReference refKey) { this.factory = factory; this.refKey = refKey; } public synchronized void dispose() { if (fileName != null) { AccessController.doPrivileged( (PrivilegedAction) () -> { try { File file = new File(fileName); int size = (int)file.length(); file.delete(); // decrement tracker only after // successful deletion. if (isTracked) { FontFileWriter.FontTracker. getTracker().subBytes(size); } if (factory != null && refKey != null) { Object o = refKey.get(); if (o == null) { factory.removeTmpFont(refKey); factory = null; refKey = null; } } if (PrismFontFactory.debugFonts) { System.err.println("FileDisposer=" + fileName); } } catch (Exception e) { if (PrismFontFactory.debugFonts) { e.printStackTrace(); } } return null; } ); fileName = null; } } } public String getFileName() { return filename; } protected int getFileSize() { return filesize; } protected int getFontIndex() { return fontIndex; } public String getFullName() { return fullName; } public String getPSName() { if (psName == null) { psName = fullName; } return psName; } public String getFamilyName() { return familyName; } public String getStyleName() { return styleName; } public String getLocaleFullName() { return localeFullName; } public String getLocaleFamilyName() { return localeFamilyName; } public String getLocaleStyleName() { return localeStyleName; } /* * Returns the features the font supports. */ public int getFeatures() { //TODO check font file for features return -1; } public Map getStrikeMap() { return strikeMap; } /* Used to determine if this size has embedded bitmaps, which * for CJK fonts should be used in preference to LCD glyphs. */ public boolean useEmbeddedBitmapsForSize(int ptSize) { Buffer eblcTable = readTable(EBLCTag); if (eblcTable == null) return false; int numSizes = eblcTable.getInt(4); /* The bitmapSizeTable's start at offset of 8. * Each bitmapSizeTable entry is 48 bytes. * The offset of ppemY in the entry is 45. */ for (int i=0;i ref = strikeMap.get(desc); PrismFontStrike strike = null; if (ref != null) { strike = ref.get(); } if (strike == null) { strike = createStrike(size, transform, aaMode, desc); DisposerRecord disposer = strike.getDisposer(); if (disposer != null) { ref = Disposer.addRecord(strike, disposer); } else { ref = new WeakReference(strike); } strikeMap.put(desc, ref); } return strike; } HashMap bbCache = null; static final int[] EMPTY_BOUNDS = new int[4]; protected abstract int[] createGlyphBoundingBox(int gc); @Override public float[] getGlyphBoundingBox(int gc, float size, float[] retArr) { if (retArr == null || retArr.length < 4) { retArr = new float[4]; } if (gc >= getNumGlyphs()) { retArr[0] = retArr[1] = retArr[2] = retArr[3] = 0; return retArr; } if (bbCache == null) { bbCache = new HashMap(); } int[] bb = bbCache.get(gc); if (bb == null) { bb = createGlyphBoundingBox(gc); if (bb == null) bb = EMPTY_BOUNDS; bbCache.put(gc, bb); } float scale = size / getUnitsPerEm(); retArr[0] = bb[0] * scale; retArr[1] = bb[1] * scale; retArr[2] = bb[2] * scale; retArr[3] = bb[3] * scale; return retArr; } int getNumGlyphs() { if (numGlyphs == -1) { Buffer buffer = readTable(maxpTag); numGlyphs = buffer.getChar(4); // offset 4 bytes in MAXP table. } return numGlyphs; } protected boolean isCFF() { return isCFF; } private Object peer; public Object getPeer() { return peer; } public void setPeer(Object peer) { this.peer = peer; } synchronized Buffer readTable(int tag) { Buffer buffer = null; boolean openedFile = false; try { DirectoryEntry tagDE = getDirectoryEntry(tag); if (tagDE != null) { openedFile = filereader.openFile(); buffer = filereader.readBlock(tagDE.offset, tagDE.length); } } catch (Exception e) { if (PrismFontFactory.debugFonts) { e.printStackTrace(); } } finally { if (openedFile) { try { filereader.closeFile(); } catch (Exception e2) { } } } return buffer; } int directoryCount = 1; /** * @return number of logical fonts. Is "1" for all but TTC files */ public int getFontCount() { return directoryCount; } int numTables; DirectoryEntry[] tableDirectory; static class DirectoryEntry { int tag; int offset; int length; } DirectoryEntry getDirectoryEntry(int tag) { for (int i=0;i= directoryCount) { throw new Exception("Bad collection index"); } fontIndex = fIndex; buffer = filereader.readBlock(TTCHEADERSIZE+4*fIndex, 4); headerOffset = buffer.getInt(); buffer = filereader.readBlock(headerOffset, 4); sfntTag = buffer.getInt(); } switch (sfntTag) { case v1ttTag: case trueTag: break; case ottoTag: isCFF = true; break; default: throw new Exception("Unsupported sfnt " + filename); } /* Now have the offset of this TT font (possibly within a TTC) * After the TT version/scaler type field, is the short * representing the number of tables in the table directory. * The table directory begins at 12 bytes after the header. * Each table entry is 16 bytes long (4 32-bit ints) */ buffer = filereader.readBlock(headerOffset+4, 2); numTables = buffer.getShort(); int directoryOffset = headerOffset+DIRECTORYHEADERSIZE; Buffer ibuffer = filereader. readBlock(directoryOffset, numTables*DIRECTORYENTRYSIZE); DirectoryEntry table; tableDirectory = new DirectoryEntry[numTables]; for (int i=0; i filesize) { throw new Exception("bad table, tag="+table.tag); } } DirectoryEntry headDE = getDirectoryEntry(headTag); Buffer headTable = filereader.readBlock(headDE.offset, headDE.length); // Important font attribute must be set in order to prevent div by zero upem = (float)(headTable.getShort(18) & 0xffff); if (!(16 <= upem && upem <= 16384)) { upem = 2048; } indexToLocFormat = headTable.getShort(50); // 0 for short offsets, 1 for long if (indexToLocFormat < 0 || indexToLocFormat > 1) { throw new Exception("Bad indexToLocFormat"); } // In a conventional optimised layout, the // hhea table immediately follows the 'head' table. Buffer hhea = readTable(hheaTag); if (hhea == null) { numHMetrics = -1; } else { // the font table has the sign of ascent and descent // reversed from our coordinate system. ascent = -(float)hhea.getShort(4); descent = -(float)hhea.getShort(6); linegap = (float)hhea.getShort(8); // advanceWidthMax is max horizontal advance of all glyphs in // font. For some fonts advanceWidthMax is much larger then "M" // advanceWidthMax = (float)hhea.getChar(10); numHMetrics = hhea.getChar(34) & 0xffff; } // maxp table is before the OS/2 table. Read it now // while file is open - will be very cheap as its just // 32 bytes and we already have it in a byte[]. getNumGlyphs(); setStyle(); /* Get names last, as the name table is far from the file header. * Although its also likely too big to fit in the read cache * in which case that would remain valid, but also will help * any file read implementation which doesn't have random access. */ initNames(); if (familyName == null || fullName == null) { String fontName = name != null ? name : ""; if (fullName == null) { fullName = familyName != null ? familyName : fontName; } if (familyName == null) { familyName = fullName != null ? fullName : fontName; } throw new Exception("Font name not found."); } /* update the font resource only if the file was decoded * and initialized successfully. */ if (decoder != null) { isDecoded = true; filename = filereader.getFilename(); PrismFontFactory.getFontFactory().addDecodedFont(this); } } catch (Exception e) { if (decoder != null) { decoder.deleteFile(); } throw e; } finally { filereader.closeFile(); } } /* TrueTypeFont can use the fsSelection fields of OS/2 table * or macStyleBits of the 'head' table to determine the style. */ private static final int fsSelectionItalicBit = 0x00001; private static final int fsSelectionBoldBit = 0x00020; private static final int MACSTYLE_BOLD_BIT = 0x1; private static final int MACSTYLE_ITALIC_BIT = 0x2; // Comment out some of this until we have both a need and a way to use it. // private int embeddingInfo; //private int fontWeight; private boolean isBold; private boolean isItalic; private float upem; private float ascent, descent, linegap; // in design units private int numHMetrics; private void setStyle() { // A number of fonts on Mac OS X do not have an OS/2 // table. For those need to get info from a different source. DirectoryEntry os2_DE = getDirectoryEntry(os_2Tag); if (os2_DE != null) { // os2 Table ver 4 DataType Offset //version USHORT 0 //xAvgCharWidth SHORT 2 //usWeightClass USHORT 4 //usWidthClass USHORT 6 //fsType USHORT 8 //ySubscriptXSize SHORT 10 //ySubscriptYSize SHORT 12 //ySubscriptXOffset SHORT 14 //ySubscriptYOffset SHORT 16 //ySuperscriptXSize SHORT 18 //ySuperscriptYSize SHORT 20 //ySuperscriptXOffset SHORT 22 //ySuperscriptYOffset SHORT 24 //yStrikeoutSize SHORT 26 //yStrikeoutPosition SHORT 28 //sFamilyClass SHORT 30 //panose[10] BYTE 32 //ulUnicodeRange1 ULONG 42 //ulUnicodeRange2 ULONG 46 //ulUnicodeRange3 ULONG 50 //ulUnicodeRange4 ULONG 54 //achVendID[4] CHAR 58 //fsSelection USHORT 62 //usFirstCharIndex USHORT 64 //usLastCharIndex USHORT 66 //sTypoAscender SHORT 68 //sTypoDescender SHORT 70 //sTypoLineGap SHORT 72 //usWinAscent USHORT 74 //usWinDescent USHORT 76 //ulCodePageRange1 ULONG 78 //ulCodePageRange2 ULONG 82 //sxHeight SHORT 86 //sCapHeight SHORT 88 //usDefaultChar USHORT 90 //usBreakChar USHORT 92 //usMaxContext USHORT 94 Buffer os_2Table = filereader.readBlock(os2_DE.offset, os2_DE.length); int fsSelection = os_2Table.getChar(62) & 0xffff; isItalic = (fsSelection & fsSelectionItalicBit) != 0; isBold = (fsSelection & fsSelectionBoldBit) != 0; } else { DirectoryEntry headDE = getDirectoryEntry(headTag); Buffer headTable = filereader.readBlock(headDE.offset, headDE.length); short macStyleBits = headTable.getShort(44); isItalic = (macStyleBits & MACSTYLE_ITALIC_BIT) != 0; isBold = (macStyleBits & MACSTYLE_BOLD_BIT) != 0; } } public boolean isBold() { return isBold; } public boolean isItalic() { return isItalic; } public boolean isDecoded() { return isDecoded; } public boolean isRegistered() { return isRegistered; } public boolean isEmbeddedFont() { return isEmbedded; } /** * per the OT spec. this is an unsigned short. */ public int getUnitsPerEm() { return (int)upem; } public short getIndexToLocFormat() { return indexToLocFormat; } /** * per the OT spec. this is an unsigned short. */ public int getNumHMetrics() { return numHMetrics; } /* -- ID's used in the 'name' table */ public static final int MAC_PLATFORM_ID = 1; public static final int MACROMAN_SPECIFIC_ID = 0; public static final int MACROMAN_ENGLISH_LANG = 0; public static final int MS_PLATFORM_ID = 3; /* MS locale id for US English is the "default" */ public static final short MS_ENGLISH_LOCALE_ID = 0x0409; // 1033 decimal public static final int FAMILY_NAME_ID = 1; public static final int STYLE_NAME_ID = 2; public static final int FULL_NAME_ID = 4; public static final int PS_NAME_ID = 6; void initNames() throws Exception { byte[] name = new byte[256]; DirectoryEntry nameDE = getDirectoryEntry(nameTag); Buffer buffer = filereader.readBlock(nameDE.offset, nameDE.length); buffer.skip(2); // format - not needed. short numRecords = buffer.getShort(); /* The name table uses unsigned shorts. Many of these * are known small values that fit in a short. * The values that are sizes or offsets into the table could be * greater than 32767, so read and store those as ints */ int stringPtr = buffer.getShort() & 0xffff; /* Microsoft Windows font names are preferred but numerous Mac * fonts do not have these, so we must also accept these in the * absence of the preferred Windows names. */ for (int i=0; i 1) || (platformID == MAC_PLATFORM_ID && encodingID != MACROMAN_SPECIFIC_ID)) { buffer.skip(8); continue; } short langID = buffer.getShort(); if (platformID == MAC_PLATFORM_ID && langID != MACROMAN_ENGLISH_LANG) { buffer.skip(6); continue; } short nameID = buffer.getShort(); int nameLen = ((int)buffer.getShort()) & 0xffff; int namePtr = (((int)buffer.getShort()) & 0xffff) + stringPtr; String tmpName = null; String enc; switch (nameID) { case FAMILY_NAME_ID: if (familyName == null || langID == MS_ENGLISH_LOCALE_ID || langID == nameLocaleID) { buffer.get(namePtr, name, 0, nameLen); if (platformID == MAC_PLATFORM_ID) { enc = "US-ASCII"; } else { enc = "UTF-16BE"; } tmpName = new String(name, 0, nameLen, enc); if (familyName == null || langID == MS_ENGLISH_LOCALE_ID){ familyName = tmpName; } if (langID == nameLocaleID) { localeFamilyName = tmpName; } } break; case FULL_NAME_ID: if (fullName == null || langID == MS_ENGLISH_LOCALE_ID || langID == nameLocaleID) { buffer.get(namePtr, name, 0, nameLen); if (platformID == MAC_PLATFORM_ID) { enc = "US-ASCII"; } else { enc = "UTF-16BE"; } tmpName = new String(name, 0, nameLen, enc); if (fullName == null || langID == MS_ENGLISH_LOCALE_ID) { fullName = tmpName; } if (langID == nameLocaleID) { localeFullName = tmpName; } } break; case PS_NAME_ID: if (psName == null) { buffer.get(namePtr, name, 0, nameLen); if (platformID == MAC_PLATFORM_ID) { enc = "US-ASCII"; } else { enc = "UTF-16BE"; } psName = new String(name, 0, nameLen, enc); } break; case STYLE_NAME_ID: if (styleName == null || langID == MS_ENGLISH_LOCALE_ID || langID == nameLocaleID) { buffer.get(namePtr, name, 0, nameLen); if (platformID == MAC_PLATFORM_ID) { enc = "US-ASCII"; } else { enc = "UTF-16BE"; } tmpName = new String(name, 0, nameLen, enc); if (styleName == null || langID == MS_ENGLISH_LOCALE_ID) { styleName = tmpName; } if (langID == nameLocaleID) { localeStyleName = tmpName; } } break; default: break; } if (localeFamilyName == null) { localeFamilyName = familyName; } if (localeFullName == null) { localeFullName = fullName; } if (localeStyleName == null) { localeStyleName = styleName; } } } /*** BEGIN LOCALE_ID MAPPING ****/ private static Map lcidMap; // Return a Microsoft LCID from the given Locale. // Used when getting localized font data. private static void addLCIDMapEntry(Map map, String key, short value) { map.put(key, Short.valueOf(value)); } private static synchronized void createLCIDMap() { if (lcidMap != null) { return; } Map map = new HashMap(200); addLCIDMapEntry(map, "ar", (short) 0x0401); addLCIDMapEntry(map, "bg", (short) 0x0402); addLCIDMapEntry(map, "ca", (short) 0x0403); addLCIDMapEntry(map, "zh", (short) 0x0404); addLCIDMapEntry(map, "cs", (short) 0x0405); addLCIDMapEntry(map, "da", (short) 0x0406); addLCIDMapEntry(map, "de", (short) 0x0407); addLCIDMapEntry(map, "el", (short) 0x0408); addLCIDMapEntry(map, "es", (short) 0x040a); addLCIDMapEntry(map, "fi", (short) 0x040b); addLCIDMapEntry(map, "fr", (short) 0x040c); addLCIDMapEntry(map, "iw", (short) 0x040d); addLCIDMapEntry(map, "hu", (short) 0x040e); addLCIDMapEntry(map, "is", (short) 0x040f); addLCIDMapEntry(map, "it", (short) 0x0410); addLCIDMapEntry(map, "ja", (short) 0x0411); addLCIDMapEntry(map, "ko", (short) 0x0412); addLCIDMapEntry(map, "nl", (short) 0x0413); addLCIDMapEntry(map, "no", (short) 0x0414); addLCIDMapEntry(map, "pl", (short) 0x0415); addLCIDMapEntry(map, "pt", (short) 0x0416); addLCIDMapEntry(map, "rm", (short) 0x0417); addLCIDMapEntry(map, "ro", (short) 0x0418); addLCIDMapEntry(map, "ru", (short) 0x0419); addLCIDMapEntry(map, "hr", (short) 0x041a); addLCIDMapEntry(map, "sk", (short) 0x041b); addLCIDMapEntry(map, "sq", (short) 0x041c); addLCIDMapEntry(map, "sv", (short) 0x041d); addLCIDMapEntry(map, "th", (short) 0x041e); addLCIDMapEntry(map, "tr", (short) 0x041f); addLCIDMapEntry(map, "ur", (short) 0x0420); addLCIDMapEntry(map, "in", (short) 0x0421); addLCIDMapEntry(map, "uk", (short) 0x0422); addLCIDMapEntry(map, "be", (short) 0x0423); addLCIDMapEntry(map, "sl", (short) 0x0424); addLCIDMapEntry(map, "et", (short) 0x0425); addLCIDMapEntry(map, "lv", (short) 0x0426); addLCIDMapEntry(map, "lt", (short) 0x0427); addLCIDMapEntry(map, "fa", (short) 0x0429); addLCIDMapEntry(map, "vi", (short) 0x042a); addLCIDMapEntry(map, "hy", (short) 0x042b); addLCIDMapEntry(map, "eu", (short) 0x042d); addLCIDMapEntry(map, "mk", (short) 0x042f); addLCIDMapEntry(map, "tn", (short) 0x0432); addLCIDMapEntry(map, "xh", (short) 0x0434); addLCIDMapEntry(map, "zu", (short) 0x0435); addLCIDMapEntry(map, "af", (short) 0x0436); addLCIDMapEntry(map, "ka", (short) 0x0437); addLCIDMapEntry(map, "fo", (short) 0x0438); addLCIDMapEntry(map, "hi", (short) 0x0439); addLCIDMapEntry(map, "mt", (short) 0x043a); addLCIDMapEntry(map, "se", (short) 0x043b); addLCIDMapEntry(map, "gd", (short) 0x043c); addLCIDMapEntry(map, "ms", (short) 0x043e); addLCIDMapEntry(map, "kk", (short) 0x043f); addLCIDMapEntry(map, "ky", (short) 0x0440); addLCIDMapEntry(map, "sw", (short) 0x0441); addLCIDMapEntry(map, "tt", (short) 0x0444); addLCIDMapEntry(map, "bn", (short) 0x0445); addLCIDMapEntry(map, "pa", (short) 0x0446); addLCIDMapEntry(map, "gu", (short) 0x0447); addLCIDMapEntry(map, "ta", (short) 0x0449); addLCIDMapEntry(map, "te", (short) 0x044a); addLCIDMapEntry(map, "kn", (short) 0x044b); addLCIDMapEntry(map, "ml", (short) 0x044c); addLCIDMapEntry(map, "mr", (short) 0x044e); addLCIDMapEntry(map, "sa", (short) 0x044f); addLCIDMapEntry(map, "mn", (short) 0x0450); addLCIDMapEntry(map, "cy", (short) 0x0452); addLCIDMapEntry(map, "gl", (short) 0x0456); addLCIDMapEntry(map, "dv", (short) 0x0465); addLCIDMapEntry(map, "qu", (short) 0x046b); addLCIDMapEntry(map, "mi", (short) 0x0481); addLCIDMapEntry(map, "ar_IQ", (short) 0x0801); addLCIDMapEntry(map, "zh_CN", (short) 0x0804); addLCIDMapEntry(map, "de_CH", (short) 0x0807); addLCIDMapEntry(map, "en_GB", (short) 0x0809); addLCIDMapEntry(map, "es_MX", (short) 0x080a); addLCIDMapEntry(map, "fr_BE", (short) 0x080c); addLCIDMapEntry(map, "it_CH", (short) 0x0810); addLCIDMapEntry(map, "nl_BE", (short) 0x0813); addLCIDMapEntry(map, "no_NO_NY", (short) 0x0814); addLCIDMapEntry(map, "pt_PT", (short) 0x0816); addLCIDMapEntry(map, "ro_MD", (short) 0x0818); addLCIDMapEntry(map, "ru_MD", (short) 0x0819); addLCIDMapEntry(map, "sr_CS", (short) 0x081a); addLCIDMapEntry(map, "sv_FI", (short) 0x081d); addLCIDMapEntry(map, "az_AZ", (short) 0x082c); addLCIDMapEntry(map, "se_SE", (short) 0x083b); addLCIDMapEntry(map, "ga_IE", (short) 0x083c); addLCIDMapEntry(map, "ms_BN", (short) 0x083e); addLCIDMapEntry(map, "uz_UZ", (short) 0x0843); addLCIDMapEntry(map, "qu_EC", (short) 0x086b); addLCIDMapEntry(map, "ar_EG", (short) 0x0c01); addLCIDMapEntry(map, "zh_HK", (short) 0x0c04); addLCIDMapEntry(map, "de_AT", (short) 0x0c07); addLCIDMapEntry(map, "en_AU", (short) 0x0c09); addLCIDMapEntry(map, "fr_CA", (short) 0x0c0c); addLCIDMapEntry(map, "sr_CS", (short) 0x0c1a); addLCIDMapEntry(map, "se_FI", (short) 0x0c3b); addLCIDMapEntry(map, "qu_PE", (short) 0x0c6b); addLCIDMapEntry(map, "ar_LY", (short) 0x1001); addLCIDMapEntry(map, "zh_SG", (short) 0x1004); addLCIDMapEntry(map, "de_LU", (short) 0x1007); addLCIDMapEntry(map, "en_CA", (short) 0x1009); addLCIDMapEntry(map, "es_GT", (short) 0x100a); addLCIDMapEntry(map, "fr_CH", (short) 0x100c); addLCIDMapEntry(map, "hr_BA", (short) 0x101a); addLCIDMapEntry(map, "ar_DZ", (short) 0x1401); addLCIDMapEntry(map, "zh_MO", (short) 0x1404); addLCIDMapEntry(map, "de_LI", (short) 0x1407); addLCIDMapEntry(map, "en_NZ", (short) 0x1409); addLCIDMapEntry(map, "es_CR", (short) 0x140a); addLCIDMapEntry(map, "fr_LU", (short) 0x140c); addLCIDMapEntry(map, "bs_BA", (short) 0x141a); addLCIDMapEntry(map, "ar_MA", (short) 0x1801); addLCIDMapEntry(map, "en_IE", (short) 0x1809); addLCIDMapEntry(map, "es_PA", (short) 0x180a); addLCIDMapEntry(map, "fr_MC", (short) 0x180c); addLCIDMapEntry(map, "sr_BA", (short) 0x181a); addLCIDMapEntry(map, "ar_TN", (short) 0x1c01); addLCIDMapEntry(map, "en_ZA", (short) 0x1c09); addLCIDMapEntry(map, "es_DO", (short) 0x1c0a); addLCIDMapEntry(map, "sr_BA", (short) 0x1c1a); addLCIDMapEntry(map, "ar_OM", (short) 0x2001); addLCIDMapEntry(map, "en_JM", (short) 0x2009); addLCIDMapEntry(map, "es_VE", (short) 0x200a); addLCIDMapEntry(map, "ar_YE", (short) 0x2401); addLCIDMapEntry(map, "es_CO", (short) 0x240a); addLCIDMapEntry(map, "ar_SY", (short) 0x2801); addLCIDMapEntry(map, "en_BZ", (short) 0x2809); addLCIDMapEntry(map, "es_PE", (short) 0x280a); addLCIDMapEntry(map, "ar_JO", (short) 0x2c01); addLCIDMapEntry(map, "en_TT", (short) 0x2c09); addLCIDMapEntry(map, "es_AR", (short) 0x2c0a); addLCIDMapEntry(map, "ar_LB", (short) 0x3001); addLCIDMapEntry(map, "en_ZW", (short) 0x3009); addLCIDMapEntry(map, "es_EC", (short) 0x300a); addLCIDMapEntry(map, "ar_KW", (short) 0x3401); addLCIDMapEntry(map, "en_PH", (short) 0x3409); addLCIDMapEntry(map, "es_CL", (short) 0x340a); addLCIDMapEntry(map, "ar_AE", (short) 0x3801); addLCIDMapEntry(map, "es_UY", (short) 0x380a); addLCIDMapEntry(map, "ar_BH", (short) 0x3c01); addLCIDMapEntry(map, "es_PY", (short) 0x3c0a); addLCIDMapEntry(map, "ar_QA", (short) 0x4001); addLCIDMapEntry(map, "es_BO", (short) 0x400a); addLCIDMapEntry(map, "es_SV", (short) 0x440a); addLCIDMapEntry(map, "es_HN", (short) 0x480a); addLCIDMapEntry(map, "es_NI", (short) 0x4c0a); addLCIDMapEntry(map, "es_PR", (short) 0x500a); lcidMap = map; } private static short getLCIDFromLocale(Locale locale) { // optimize for common case if (locale.equals(Locale.US) || locale.getLanguage().equals("en")) { return MS_ENGLISH_LOCALE_ID; } if (lcidMap == null) { createLCIDMap(); } String key = locale.toString(); while (!key.isEmpty()) { Short lcidObject = (Short) lcidMap.get(key); if (lcidObject != null) { return lcidObject.shortValue(); } int pos = key.lastIndexOf('_'); if (pos < 1) { return MS_ENGLISH_LOCALE_ID; } key = key.substring(0, pos); } return MS_ENGLISH_LOCALE_ID; } /* On Windows this is set to the System Locale, which matches how * GDI enumerates font names. For display purposes we may want * the user locale which could be different. */ static short nameLocaleID = getSystemLCID(); private static short getSystemLCID() { if (PrismFontFactory.isWindows) { return PrismFontFactory.getSystemLCID(); } else { return getLCIDFromLocale(Locale.getDefault()); } } private OpenTypeGlyphMapper mapper = null; public CharToGlyphMapper getGlyphMapper() { if (mapper == null) { mapper = new OpenTypeGlyphMapper(this); } return mapper; } public FontStrike getStrike(float size, BaseTransform transform) { return getStrike(size, transform, getDefaultAAMode()); } char[] advanceWidths = null; /* * This is returning the unhinted advance, should be OK so * long as we do unhinted rendering. If we are doing hinted glyphs * and I suppose, integer metrics, then we can use the hdmx table. * But since the hdmx table doesn't provide anything except integers * it will only be useful for some cases. Also even then the ptSize * alone doesn't help, since we need to know the graphics scale * to know the real glyph size that's required, then of course we * have to translate that back into user space. So all of that will * need to be looked into, or we reserve this path for unhinted rendering. * Note that if there's no hdmx entry for a given size, then we need * to scale the glyph to get the hinted advance. However before doing * so we should consult the 'gasp' table to see it its a size at * which hinting should be performed anyway. * (1) The GASP table indicates size at which hinting should be applied * usually this is all larger sizes so probably wouldn't help, however * (2) If there is a LTSH (Linear Threshold) table, we can use that * to see if for the requested 'ppem' size, the glyph scales linearly. * * Interestingly Amble sets the 'head' flags bit to say non-linear * scaling and so legitimately has a LTSH table but this all may be * a hold-over from when its gasp table said to apply hints at some sizes. * I suppose I am not 100% certain if the gasp table can be trusted to * use as a short-cut for when you don't need to scale, or if choosing * not to hint means you can always just assume linear scaling, but I * do find that to be consistent with the data in Microsoft fonts where * they do not provide hdmx entry for sizes below that where hinting is * required, suggesting the htmx table is fine for such cases. */ public float getAdvance(int glyphCode, float ptSize) { if (glyphCode == CharToGlyphMapper.INVISIBLE_GLYPH_ID) return 0f; // If we haven't initialised yet, do so now. if (advanceWidths == null && numHMetrics > 0) { synchronized (this) { Buffer hmtx = readTable(hmtxTag); if (hmtx == null) { numHMetrics = -1; return 0; } char[] aw = new char[numHMetrics]; for (int i=0; i 0) { char cadv; if (glyphCode < numHMetrics) { cadv = advanceWidths[glyphCode]; } else { cadv = advanceWidths[numHMetrics-1]; } return ((float)(cadv & 0xffff)*ptSize)/upem; } else { // no valid lookup. return 0f; } } public PrismMetrics getFontMetrics(float ptSize) { return new PrismMetrics((ascent*ptSize)/upem, (descent*ptSize)/upem, (linegap*ptSize)/upem, this, ptSize); } private float[] styleMetrics; float[] getStyleMetrics(float ptSize) { if (styleMetrics == null) { float [] smetrics = new float[METRICS_TOTAL]; Buffer os_2 = readTable(os_2Tag); int length = os_2 != null ? os_2.capacity() : 0; if (length >= 30) { smetrics[STRIKETHROUGH_THICKNESS] = os_2.getShort(26) / upem; smetrics[STRIKETHROUGH_OFFSET] = -os_2.getShort(28) / upem; } else { smetrics[STRIKETHROUGH_THICKNESS] = 0.05f; smetrics[STRIKETHROUGH_OFFSET] = -0.4f; } if (length >= 74) { // ascent, descent, leading are set in constructor smetrics[TYPO_ASCENT] = -os_2.getShort(68) / upem; smetrics[TYPO_DESCENT] = -os_2.getShort(70) / upem; smetrics[TYPO_LINEGAP] = os_2.getShort(72) / upem; } else { smetrics[TYPO_ASCENT] = ascent / upem; smetrics[TYPO_DESCENT] = descent / upem; smetrics[TYPO_LINEGAP] = linegap / upem; } // REMIND : OpenType spec introduced xHeight, many fonts // won't have this info. // xHeight should be available in OS2 font table ver. 3 or greater if (length >= 90) { smetrics[XHEIGHT] = os_2.getShort(86) / upem; smetrics[CAPHEIGHT] = os_2.getShort(88); /* Some fonts have bad values for capHeight. For example, * Comic Sans MS. The fix is to ignore the capHeight in the * font file when it is less than half of the ascent */ if ((smetrics[CAPHEIGHT] / ascent) < 0.5) { smetrics[CAPHEIGHT] = 0; } else { smetrics[CAPHEIGHT] /= upem; } } if (smetrics[XHEIGHT] == 0 || smetrics[CAPHEIGHT] == 0) { FontStrike strike = getStrike(ptSize, BaseTransform.IDENTITY_TRANSFORM); CharToGlyphMapper mapper = getGlyphMapper(); int missingGlyph = mapper.getMissingGlyphCode(); if (smetrics[XHEIGHT] == 0) { int gc = mapper.charToGlyph('x'); if (gc != missingGlyph) { RectBounds fbds = strike.getGlyph(gc).getBBox(); smetrics[XHEIGHT] = fbds.getHeight() / ptSize; } else { smetrics[XHEIGHT] = -ascent * 0.6f / upem; } } if (smetrics[CAPHEIGHT] == 0) { int gc = mapper.charToGlyph('H'); if (gc != missingGlyph) { RectBounds fbds = strike.getGlyph(gc).getBBox(); smetrics[CAPHEIGHT] = fbds.getHeight() / ptSize; } else { smetrics[CAPHEIGHT] = -ascent * 0.9f / upem; } } } Buffer postTable = readTable(postTag); if (postTable == null || postTable.capacity() < 12) { smetrics[UNDERLINE_OFFSET] = 0.1f; smetrics[UNDERLINE_THICKESS] = 0.05f; } else { smetrics[UNDERLINE_OFFSET] = -postTable.getShort(8) / upem; smetrics[UNDERLINE_THICKESS] = postTable.getShort(10) / upem; } styleMetrics = smetrics; } float[] metrics = new float[METRICS_TOTAL]; for (int i = 0; i < METRICS_TOTAL; i++) { metrics[i] = styleMetrics[i] * ptSize; } return metrics; } byte[] getTableBytes(int tag) { Buffer buffer = readTable(tag); byte[] table = null; if(buffer != null){ table = new byte[buffer.capacity()]; buffer.get(0, table, 0, buffer.capacity()); } return table; } }