/* * Copyright (c) 2003, 2020, 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 sun.font; import java.awt.Font; import java.awt.FontFormatException; import java.awt.GraphicsEnvironment; import java.awt.geom.Point2D; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.FileChannel; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import sun.java2d.Disposer; import sun.java2d.DisposerRecord; import sun.security.action.GetPropertyAction; /** * TrueTypeFont is not called SFntFont because it is not expected * to handle all types that may be housed in a such a font file. * If additional types are supported later, it may make sense to * create an SFnt superclass. Eg to handle sfnt-housed postscript fonts. * OpenType fonts are handled by this class, and possibly should be * represented by a subclass. * An instance stores some information from the font file to faciliate * faster access. File size, the table directory and the names of the font * are the most important of these. It amounts to approx 400 bytes * for a typical font. Systems with mutiple locales sometimes have up to 400 * font files, and an app which loads all font files would need around * 160Kbytes. So storing any more info than this would be expensive. */ public class TrueTypeFont extends FileFont { /* -- Tags for required TrueType tables */ public static final int cmapTag = 0x636D6170; // 'cmap' public static final int glyfTag = 0x676C7966; // 'glyf' public static final int headTag = 0x68656164; // 'head' public static final int hheaTag = 0x68686561; // 'hhea' public static final int hmtxTag = 0x686D7478; // 'hmtx' public static final int locaTag = 0x6C6F6361; // 'loca' public static final int maxpTag = 0x6D617870; // 'maxp' public static final int nameTag = 0x6E616D65; // 'name' public static final int postTag = 0x706F7374; // 'post' public static final int os_2Tag = 0x4F532F32; // 'OS/2' /* -- Tags for opentype related tables */ public static final int GDEFTag = 0x47444546; // 'GDEF' public static final int GPOSTag = 0x47504F53; // 'GPOS' public static final int GSUBTag = 0x47535542; // 'GSUB' public static final int mortTag = 0x6D6F7274; // 'mort' public static final int morxTag = 0x6D6F7278; // 'morx' /* -- Tags for non-standard tables */ public static final int fdscTag = 0x66647363; // 'fdsc' - gxFont descriptor public static final int fvarTag = 0x66766172; // 'fvar' - gxFont variations public static final int featTag = 0x66656174; // 'feat' - layout features public static final int EBLCTag = 0x45424C43; // 'EBLC' - embedded bitmaps public static final int gaspTag = 0x67617370; // 'gasp' - hint/smooth sizes /* -- Other tags */ public static final int ttcfTag = 0x74746366; // 'ttcf' - TTC file public static final int v1ttTag = 0x00010000; // 'v1tt' - Version 1 TT font public static final int trueTag = 0x74727565; // 'true' - Version 2 TT font public static final int ottoTag = 0x4f54544f; // 'otto' - OpenType font /* -- 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 ENGLISH_LOCALE_ID = 0x0409; // 1033 decimal public static final int FAMILY_NAME_ID = 1; // public static final int STYLE_WEIGHT_ID = 2; // currently unused. public static final int FULL_NAME_ID = 4; public static final int POSTSCRIPT_NAME_ID = 6; private static final short US_LCID = 0x0409; // US English - default private static Map lcidMap; static class DirectoryEntry { int tag; int offset; int length; } /* There is a pool which limits the number of fd's that are in * use. Normally fd's are closed as they are replaced in the pool. * But if an instance of this class becomes unreferenced, then there * needs to be a way to close the fd. A finalize() method could do this, * but using the Disposer class will ensure its called in a more timely * manner. This is not something which should be relied upon to free * fd's - its a safeguard. */ private static class TTDisposerRecord implements DisposerRecord { FileChannel channel = null; public synchronized void dispose() { try { if (channel != null) { channel.close(); } } catch (IOException e) { } finally { channel = null; } } } TTDisposerRecord disposerRecord = new TTDisposerRecord(); /* > 0 only if this font is a part of a collection */ int fontIndex = 0; /* Number of fonts in this collection. ==1 if not a collection */ int directoryCount = 1; /* offset in file of table directory for this font */ int directoryOffset; // 12 if its not a collection. /* number of table entries in the directory/offsets table */ int numTables; /* The contents of the directory/offsets table */ DirectoryEntry []tableDirectory; // protected byte []gposTable = null; // protected byte []gdefTable = null; // protected byte []gsubTable = null; // protected byte []mortTable = null; // protected boolean hintsTabledChecked = false; // protected boolean containsHintsTable = false; /* These fields are set from os/2 table info. */ private boolean supportsJA; private boolean supportsCJK; /* These are for faster access to the name of the font as * typically exposed via API to applications. */ private Locale nameLocale; private String localeFamilyName; private String localeFullName; /* * Used on Windows to validate the font selected by GDI for (sub-pixel * antialiased) rendering. For 'standalone' fonts it's equal to the font * file size, for collection (TTC, OTC) members it's the number of bytes in * the collection file from the start of this font's offset table till the * end of the file. */ int fontDataSize; public TrueTypeFont(String platname, Object nativeNames, int fIndex, boolean javaRasterizer) throws FontFormatException { this(platname, nativeNames, fIndex, javaRasterizer, true); } /** * - does basic verification of the file * - reads the header table for this font (within a collection) * - reads the names (full, family). * - determines the style of the font. * - initializes the CMAP * @throws FontFormatException if the font can't be opened * or fails verification, or there's no usable cmap */ public TrueTypeFont(String platname, Object nativeNames, int fIndex, boolean javaRasterizer, boolean useFilePool) throws FontFormatException { super(platname, nativeNames); useJavaRasterizer = javaRasterizer; fontRank = Font2D.TTF_RANK; try { verify(useFilePool); init(fIndex); if (!useFilePool) { close(); } } catch (Throwable t) { close(); if (t instanceof FontFormatException) { throw (FontFormatException)t; } else { throw new FontFormatException("Unexpected runtime exception."); } } Disposer.addObjectRecord(this, disposerRecord); } private synchronized FileChannel open() throws FontFormatException { return open(true); } /* This is intended to be called, and the returned value used, * from within a block synchronized on this font object. * ie the channel returned may be nulled out at any time by "close()" * unless the caller holds a lock. * Deadlock warning: FontManager.addToPool(..) acquires a global lock, * which means nested locks may be in effect. */ private synchronized FileChannel open(boolean usePool) throws FontFormatException { if (disposerRecord.channel == null) { if (FontUtilities.isLogging()) { FontUtilities.getLogger().info("open TTF: " + platName); } try { RandomAccessFile raf = AccessController.doPrivileged( new PrivilegedExceptionAction() { public RandomAccessFile run() throws FileNotFoundException { return new RandomAccessFile(platName, "r"); } }); disposerRecord.channel = raf.getChannel(); fileSize = (int)disposerRecord.channel.size(); if (usePool) { FontManager fm = FontManagerFactory.getInstance(); if (fm instanceof SunFontManager) { ((SunFontManager) fm).addToPool(this); } } } catch (PrivilegedActionException e) { close(); Throwable reason = e.getCause(); if (reason == null) { reason = e; } throw new FontFormatException(reason.toString()); } catch (ClosedChannelException e) { /* NIO I/O is interruptible, recurse to retry operation. * The call to channel.size() above can throw this exception. * Clear interrupts before recursing in case NIO didn't. * Note that close() sets disposerRecord.channel to null. */ Thread.interrupted(); close(); open(); } catch (IOException e) { close(); throw new FontFormatException(e.toString()); } } return disposerRecord.channel; } protected synchronized void close() { disposerRecord.dispose(); } int readBlock(ByteBuffer buffer, int offset, int length) { int bread = 0; try { synchronized (this) { if (disposerRecord.channel == null) { open(); } if (offset + length > fileSize) { if (offset >= fileSize) { /* Since the caller ensures that offset is < fileSize * this condition suggests that fileSize is now * different than the value we originally provided * to native when the scaler was created. * Also fileSize is updated every time we * open() the file here, but in native the value * isn't updated. If the file has changed whilst we * are executing we want to bail, not spin. */ if (FontUtilities.isLogging()) { String msg = "Read offset is " + offset + " file size is " + fileSize+ " file is " + platName; FontUtilities.getLogger().severe(msg); } return -1; } else { length = fileSize - offset; } } buffer.clear(); disposerRecord.channel.position(offset); while (bread < length) { int cnt = disposerRecord.channel.read(buffer); if (cnt == -1) { String msg = "Unexpected EOF " + this; int currSize = (int)disposerRecord.channel.size(); if (currSize != fileSize) { msg += " File size was " + fileSize + " and now is " + currSize; } if (FontUtilities.isLogging()) { FontUtilities.getLogger().severe(msg); } // We could still flip() the buffer here because // it's possible that we did read some data in // an earlier loop, and we probably should // return that to the caller. Although if // the caller expected 8K of data and we return // only a few bytes then maybe it's better instead to // set bread = -1 to indicate failure. // The following is therefore using arbitrary values // but is meant to allow cases where enough // data was read to probably continue. if (bread > length/2 || bread > 16384) { buffer.flip(); if (FontUtilities.isLogging()) { msg = "Returning " + bread + " bytes instead of " + length; FontUtilities.getLogger().severe(msg); } } else { bread = -1; } throw new IOException(msg); } bread += cnt; } buffer.flip(); if (bread > length) { // possible if buffer.size() > length bread = length; } } } catch (FontFormatException e) { if (FontUtilities.isLogging()) { FontUtilities.getLogger().severe( "While reading " + platName, e); } bread = -1; // signal EOF deregisterFontAndClearStrikeCache(); } catch (ClosedChannelException e) { /* NIO I/O is interruptible, recurse to retry operation. * Clear interrupts before recursing in case NIO didn't. */ Thread.interrupted(); close(); return readBlock(buffer, offset, length); } catch (IOException e) { /* If we did not read any bytes at all and the exception is * not a recoverable one (ie is not ClosedChannelException) then * we should indicate that there is no point in re-trying. * Other than an attempt to read past the end of the file it * seems unlikely this would occur as problems opening the * file are handled as a FontFormatException. */ if (FontUtilities.isLogging()) { FontUtilities.getLogger().severe( "While reading " + platName, e); } if (bread == 0) { bread = -1; // signal EOF deregisterFontAndClearStrikeCache(); } } return bread; } ByteBuffer readBlock(int offset, int length) { ByteBuffer buffer = ByteBuffer.allocate(length); try { synchronized (this) { if (disposerRecord.channel == null) { open(); } if (offset + length > fileSize) { if (offset > fileSize) { return null; // assert? } else { buffer = ByteBuffer.allocate(fileSize-offset); } } disposerRecord.channel.position(offset); disposerRecord.channel.read(buffer); buffer.flip(); } } catch (FontFormatException e) { return null; } catch (ClosedChannelException e) { /* NIO I/O is interruptible, recurse to retry operation. * Clear interrupts before recursing in case NIO didn't. */ Thread.interrupted(); close(); readBlock(buffer, offset, length); } catch (IOException e) { return null; } return buffer; } /* This is used by native code which can't allocate a direct byte * buffer because of bug 4845371. It, and references to it in native * code in scalerMethods.c can be removed once that bug is fixed. * 4845371 is now fixed but we'll keep this around as it doesn't cost * us anything if its never used/called. */ byte[] readBytes(int offset, int length) { ByteBuffer buffer = readBlock(offset, length); if (buffer.hasArray()) { return buffer.array(); } else { byte[] bufferBytes = new byte[buffer.limit()]; buffer.get(bufferBytes); return bufferBytes; } } private void verify(boolean usePool) throws FontFormatException { open(usePool); } /* sizes, in bytes, of TT/TTC header records */ private static final int TTCHEADERSIZE = 12; private static final int DIRECTORYHEADERSIZE = 12; private static final int DIRECTORYENTRYSIZE = 16; protected void init(int fIndex) throws FontFormatException { int headerOffset = 0; ByteBuffer buffer = readBlock(0, TTCHEADERSIZE); try { switch (buffer.getInt()) { case ttcfTag: buffer.getInt(); // skip TTC version ID directoryCount = buffer.getInt(); if (fIndex >= directoryCount) { throw new FontFormatException("Bad collection index"); } fontIndex = fIndex; buffer = readBlock(TTCHEADERSIZE+4*fIndex, 4); headerOffset = buffer.getInt(); fontDataSize = Math.max(0, fileSize - headerOffset); break; case v1ttTag: case trueTag: case ottoTag: fontDataSize = fileSize; break; default: throw new FontFormatException("Unsupported sfnt " + getPublicFileName()); } /* 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 = readBlock(headerOffset+4, 2); numTables = buffer.getShort(); directoryOffset = headerOffset+DIRECTORYHEADERSIZE; ByteBuffer bbuffer = readBlock(directoryOffset, numTables*DIRECTORYENTRYSIZE); IntBuffer ibuffer = bbuffer.asIntBuffer(); DirectoryEntry table; tableDirectory = new DirectoryEntry[numTables]; for (int i=0; i fileSize) { throw new FontFormatException("bad table, tag="+table.tag); } } if (getDirectoryEntry(headTag) == null) { throw new FontFormatException("missing head table"); } if (getDirectoryEntry(maxpTag) == null) { throw new FontFormatException("missing maxp table"); } if (getDirectoryEntry(hmtxTag) != null && getDirectoryEntry(hheaTag) == null) { throw new FontFormatException("missing hhea table"); } initNames(); } catch (Exception e) { if (FontUtilities.isLogging()) { FontUtilities.getLogger().severe(e.toString()); } if (e instanceof FontFormatException) { throw (FontFormatException)e; } else { throw new FontFormatException(e.toString()); } } if (familyName == null || fullName == null) { throw new FontFormatException("Font name not found"); } /* The os2_Table is needed to gather some info, but we don't * want to keep it around (as a field) so obtain it once and * pass it to the code that needs it. */ ByteBuffer os2_Table = getTableBuffer(os_2Tag); setStyle(os2_Table); setCJKSupport(os2_Table); } /* The array index corresponds to a bit offset in the TrueType * font's OS/2 compatibility table's code page ranges fields. * These are two 32 bit unsigned int fields at offsets 78 and 82. * We are only interested in determining if the font supports * the windows encodings we expect as the default encoding in * supported locales, so we only map the first of these fields. */ static final String[] encoding_mapping = { "cp1252", /* 0:Latin 1 */ "cp1250", /* 1:Latin 2 */ "cp1251", /* 2:Cyrillic */ "cp1253", /* 3:Greek */ "cp1254", /* 4:Turkish/Latin 5 */ "cp1255", /* 5:Hebrew */ "cp1256", /* 6:Arabic */ "cp1257", /* 7:Windows Baltic */ "", /* 8:reserved for alternate ANSI */ "", /* 9:reserved for alternate ANSI */ "", /* 10:reserved for alternate ANSI */ "", /* 11:reserved for alternate ANSI */ "", /* 12:reserved for alternate ANSI */ "", /* 13:reserved for alternate ANSI */ "", /* 14:reserved for alternate ANSI */ "", /* 15:reserved for alternate ANSI */ "ms874", /* 16:Thai */ "ms932", /* 17:JIS/Japanese */ "gbk", /* 18:PRC GBK Cp950 */ "ms949", /* 19:Korean Extended Wansung */ "ms950", /* 20:Chinese (Taiwan, Hongkong, Macau) */ "ms1361", /* 21:Korean Johab */ "", /* 22 */ "", /* 23 */ "", /* 24 */ "", /* 25 */ "", /* 26 */ "", /* 27 */ "", /* 28 */ "", /* 29 */ "", /* 30 */ "", /* 31 */ }; /* This maps two letter language codes to a Windows code page. * Note that eg Cp1252 (the first subarray) is not exactly the same as * Latin-1 since Windows code pages are do not necessarily correspond. * There are two codepages for zh and ko so if a font supports * only one of these ranges then we need to distinguish based on * country. So far this only seems to matter for zh. * REMIND: Unicode locales such as Hindi do not have a code page so * this whole mechanism needs to be revised to map languages to * the Unicode ranges either when this fails, or as an additional * validating test. Basing it on Unicode ranges should get us away * from needing to map to this small and incomplete set of Windows * code pages which looks odd on non-Windows platforms. */ private static final String[][] languages = { /* cp1252/Latin 1 */ { "en", "ca", "da", "de", "es", "fi", "fr", "is", "it", "nl", "no", "pt", "sq", "sv", }, /* cp1250/Latin2 */ { "cs", "cz", "et", "hr", "hu", "nr", "pl", "ro", "sk", "sl", "sq", "sr", }, /* cp1251/Cyrillic */ { "bg", "mk", "ru", "sh", "uk" }, /* cp1253/Greek*/ { "el" }, /* cp1254/Turkish,Latin 5 */ { "tr" }, /* cp1255/Hebrew */ { "he" }, /* cp1256/Arabic */ { "ar" }, /* cp1257/Windows Baltic */ { "et", "lt", "lv" }, /* ms874/Thai */ { "th" }, /* ms932/Japanese */ { "ja" }, /* gbk/Chinese (PRC GBK Cp950) */ { "zh", "zh_CN", }, /* ms949/Korean Extended Wansung */ { "ko" }, /* ms950/Chinese (Taiwan, Hongkong, Macau) */ { "zh_HK", "zh_TW", }, /* ms1361/Korean Johab */ { "ko" }, }; private static final String[] codePages = { "cp1252", "cp1250", "cp1251", "cp1253", "cp1254", "cp1255", "cp1256", "cp1257", "ms874", "ms932", "gbk", "ms949", "ms950", "ms1361", }; private static String defaultCodePage = null; static String getCodePage() { if (defaultCodePage != null) { return defaultCodePage; } if (FontUtilities.isWindows) { defaultCodePage = AccessController.doPrivileged(new GetPropertyAction("file.encoding")); } else { if (languages.length != codePages.length) { throw new InternalError("wrong code pages array length"); } Locale locale = sun.awt.SunToolkit.getStartupLocale(); String language = locale.getLanguage(); if (language != null) { if (language.equals("zh")) { String country = locale.getCountry(); if (country != null) { language = language + "_" + country; } } for (int i=0; i fileSize) { return null; } int bread = 0; ByteBuffer buffer = ByteBuffer.allocate(entry.length); synchronized (this) { try { if (disposerRecord.channel == null) { open(); } disposerRecord.channel.position(entry.offset); bread = disposerRecord.channel.read(buffer); buffer.flip(); } catch (ClosedChannelException e) { /* NIO I/O is interruptible, recurse to retry operation. * Clear interrupts before recursing in case NIO didn't. */ Thread.interrupted(); close(); return getTableBuffer(tag); } catch (IOException e) { return null; } catch (FontFormatException e) { return null; } if (bread < entry.length) { return null; } else { return buffer; } } } @Override protected byte[] getTableBytes(int tag) { ByteBuffer buffer = getTableBuffer(tag); if (buffer == null) { return null; } else if (buffer.hasArray()) { try { return buffer.array(); } catch (Exception re) { } } byte []data = new byte[getTableSize(tag)]; buffer.get(data); return data; } int getTableSize(int tag) { for (int i=0;i 0) ? fontWidth : super.getWidth(); } private int fontWeight = 0; @Override public int getWeight() { return (fontWeight > 0) ? fontWeight : super.getWeight(); } /* TrueTypeFont can use the fsSelection fields of OS/2 table * to determine the style. In the unlikely case that doesn't exist, * can use macStyle in the 'head' table but simpler to * fall back to super class algorithm of looking for well known string. * A very few fonts don't specify this information, but I only * came across one: Lucida Sans Thai Typewriter Oblique in * /usr/openwin/lib/locale/th_TH/X11/fonts/TrueType/lucidai.ttf * that explicitly specified the wrong value. It says its regular. * I didn't find any fonts that were inconsistent (ie regular plus some * other value). */ private static final int fsSelectionItalicBit = 0x00001; private static final int fsSelectionBoldBit = 0x00020; private static final int fsSelectionRegularBit = 0x00040; private void setStyle(ByteBuffer os_2Table) { if (os_2Table == null) { return; } if (os_2Table.capacity() >= 8) { fontWeight = os_2Table.getChar(4) & 0xffff; fontWidth = os_2Table.getChar(6) & 0xffff; } /* fsSelection is unsigned short at buffer offset 62 */ if (os_2Table.capacity() < 64) { super.setStyle(); return; } int fsSelection = os_2Table.getChar(62) & 0xffff; int italic = fsSelection & fsSelectionItalicBit; int bold = fsSelection & fsSelectionBoldBit; int regular = fsSelection & fsSelectionRegularBit; // System.out.println("platname="+platName+" font="+fullName+ // " family="+familyName+ // " R="+regular+" I="+italic+" B="+bold); if (regular!=0 && ((italic|bold)!=0)) { /* This is inconsistent. Try using the font name algorithm */ super.setStyle(); return; } else if ((regular|italic|bold) == 0) { /* No style specified. Try using the font name algorithm */ super.setStyle(); return; } switch (bold|italic) { case fsSelectionItalicBit: style = Font.ITALIC; break; case fsSelectionBoldBit: style = Font.BOLD; break; case fsSelectionBoldBit|fsSelectionItalicBit: style = Font.BOLD|Font.ITALIC; } } private float stSize, stPos, ulSize, ulPos; private void setStrikethroughMetrics(ByteBuffer os_2Table, int upem) { if (os_2Table == null || os_2Table.capacity() < 30 || upem < 0) { stSize = .05f; stPos = -.4f; return; } ShortBuffer sb = os_2Table.asShortBuffer(); stSize = sb.get(13) / (float)upem; stPos = -sb.get(14) / (float)upem; } private void setUnderlineMetrics(ByteBuffer postTable, int upem) { if (postTable == null || postTable.capacity() < 12 || upem < 0) { ulSize = .05f; ulPos = .1f; return; } ShortBuffer sb = postTable.asShortBuffer(); ulSize = sb.get(5) / (float)upem; ulPos = -sb.get(4) / (float)upem; } @Override public void getStyleMetrics(float pointSize, float[] metrics, int offset) { if (ulSize == 0f && ulPos == 0f) { ByteBuffer head_Table = getTableBuffer(headTag); int upem = -1; if (head_Table != null && head_Table.capacity() >= 18) { ShortBuffer sb = head_Table.asShortBuffer(); upem = sb.get(9) & 0xffff; if (upem < 16 || upem > 16384) { upem = 2048; } } ByteBuffer os2_Table = getTableBuffer(os_2Tag); setStrikethroughMetrics(os2_Table, upem); ByteBuffer post_Table = getTableBuffer(postTag); setUnderlineMetrics(post_Table, upem); } metrics[offset] = stPos * pointSize; metrics[offset+1] = stSize * pointSize; metrics[offset+2] = ulPos * pointSize; metrics[offset+3] = ulSize * pointSize; } private String makeString(byte[] bytes, int len, short platformID, short encoding) { if (platformID == MAC_PLATFORM_ID) { encoding = -1; // hack so we can re-use the code below. } /* Check for fonts using encodings 2->6 is just for * some old DBCS fonts, apparently mostly on Solaris. * Some of these fonts encode ascii names as double-byte characters. * ie with a leading zero byte for what properly should be a * single byte-char. */ if (encoding >=2 && encoding <= 6) { byte[] oldbytes = bytes; int oldlen = len; bytes = new byte[oldlen]; len = 0; for (int i=0; i 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); // the following statements are derived from the langIDMap // in src/windows/native/java/lang/java_props_md.c using the following // awk script: // $1~/\/\*/ { next} // $3~/\?\?/ { next } // $3!~/_/ { next } // $1~/0x0409/ { next } // $1~/0x0c0a/ { next } // $1~/0x042c/ { next } // $1~/0x0443/ { next } // $1~/0x0812/ { next } // $1~/0x04/ { print " addLCIDMapEntry(map, " substr($3, 0, 3) "\", (short) " substr($1, 0, 6) ");" ; next } // $3~/,/ { print " addLCIDMapEntry(map, " $3 " (short) " substr($1, 0, 6) ");" ; next } // { print " addLCIDMapEntry(map, " $3 ", (short) " substr($1, 0, 6) ");" ; next } // The lines of this script: // - eliminate comments // - eliminate questionable locales // - eliminate language-only locales // - eliminate the default LCID value // - eliminate a few other unneeded LCID values // - print language-only locale entries for x04* LCID values // (apparently Microsoft doesn't use language-only LCID values - // see http://www.microsoft.com/OpenType/otspec/name.htm // - print complete entries for all other LCID values // Run // awk -f awk-script langIDMap > statements 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)) { return US_LCID; } if (lcidMap == null) { createLCIDMap(); } String key = locale.toString(); while (!"".equals(key)) { Short lcidObject = lcidMap.get(key); if (lcidObject != null) { return lcidObject.shortValue(); } int pos = key.lastIndexOf('_'); if (pos < 1) { return US_LCID; } key = key.substring(0, pos); } return US_LCID; } @Override public String getFamilyName(Locale locale) { if (locale == null) { return familyName; } else if (locale.equals(nameLocale) && localeFamilyName != null) { return localeFamilyName; } else { short localeID = getLCIDFromLocale(locale); String name = lookupName(localeID, FAMILY_NAME_ID); if (name == null) { return familyName; } else { return name; } } } public CharToGlyphMapper getMapper() { if (mapper == null) { mapper = new TrueTypeGlyphMapper(this); } return mapper; } /* This duplicates initNames() but that has to run fast as its used * during typical start-up and the information here is likely never * needed. */ protected void initAllNames(int requestedID, HashSet names) { byte[] name = new byte[256]; ByteBuffer buffer = getTableBuffer(nameTag); if (buffer != null) { ShortBuffer sbuffer = buffer.asShortBuffer(); sbuffer.get(); // format - not needed. short numRecords = sbuffer.get(); /* 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 = ((int) sbuffer.get()) & 0xffff; for (int i=0; i aSet = new HashSet<>(); try { initAllNames(FAMILY_NAME_ID, aSet); } catch (Exception e) { /* In case of malformed font */ } return aSet.toArray(new String[0]); } String[] getAllFullNames() { HashSet aSet = new HashSet<>(); try { initAllNames(FULL_NAME_ID, aSet); } catch (Exception e) { /* In case of malformed font */ } return aSet.toArray(new String[0]); } /* Used by the OpenType engine for mark positioning. */ @Override Point2D.Float getGlyphPoint(long pScalerContext, int glyphCode, int ptNumber) { try { return getScaler().getGlyphPoint(pScalerContext, glyphCode, ptNumber); } catch(FontScalerException fe) { return null; } } private char[] gaspTable; private char[] getGaspTable() { if (gaspTable != null) { return gaspTable; } ByteBuffer buffer = getTableBuffer(gaspTag); if (buffer == null) { return gaspTable = new char[0]; } CharBuffer cbuffer = buffer.asCharBuffer(); char format = cbuffer.get(); /* format "1" has appeared for some Windows Vista fonts. * Its presently undocumented but the existing values * seem to be still valid so we can use it. */ if (format > 1) { // unrecognised format return gaspTable = new char[0]; } char numRanges = cbuffer.get(); if (4+numRanges*4 > getTableSize(gaspTag)) { // sanity check return gaspTable = new char[0]; } gaspTable = new char[2*numRanges]; cbuffer.get(gaspTable); return gaspTable; } /* This is to obtain info from the TT 'gasp' (grid-fitting and * scan-conversion procedure) table which specifies three combinations: * Hint, Smooth (greyscale), Hint and Smooth. * In this simplified scheme we don't distinguish the latter two. We * hint even at small sizes, so as to preserve metrics consistency. * If the information isn't available default values are substituted. * The more precise defaults we'd do if we distinguished the cases are: * Bold (no other style) fonts : * 0-8 : Smooth ( do grey) * 9+ : Hint + smooth (gridfit + grey) * Plain, Italic and Bold-Italic fonts : * 0-8 : Smooth ( do grey) * 9-17 : Hint (gridfit) * 18+ : Hint + smooth (gridfit + grey) * The defaults should rarely come into play as most TT fonts provide * better defaults. * REMIND: consider unpacking the table into an array of booleans * for faster use. */ @Override public boolean useAAForPtSize(int ptsize) { char[] gasp = getGaspTable(); if (gasp.length > 0) { for (int i=0;i= 18; } } @Override public boolean hasSupplementaryChars() { return ((TrueTypeGlyphMapper)getMapper()).hasSupplementaryChars(); } @Override public String toString() { return "** TrueType Font: Family="+familyName+ " Name="+fullName+ " style="+style+" fileName="+getPublicFileName(); } private static Map lcidLanguageCompatibilityMap; private static final short[] EMPTY_COMPATIBLE_LCIDS = new short[0]; // the language compatible LCIDs for this font's nameLocale private short[] languageCompatibleLCIDs; /* * Returns true if the given lcid's language is compatible * to the language of the startup Locale. I.e. if * startupLocale.getLanguage().equals(lcidLocale.getLanguage()) would * return true. */ private boolean isLanguageCompatible(short lcid){ for (short s : languageCompatibleLCIDs) { if (s == lcid) { return true; } } return false; } /* * Returns an array of all the language compatible LCIDs for the * given Locale. This array is later used to find compatible * locales. */ private static short[] getLanguageCompatibleLCIDsFromLocale(Locale locale) { if (lcidLanguageCompatibilityMap == null) { createLCIDMap(); createLCIDLanguageCompatibilityMap(); } String language = locale.getLanguage(); short[] result = lcidLanguageCompatibilityMap.get(language); return result == null ? EMPTY_COMPATIBLE_LCIDS : result; } // private static void prtLine(String s) { // System.out.println(s); // } // /* // * Initializes the map from Locale keys (e.g. "en_BZ" or "de") // * to language compatible LCIDs. // * This map could be statically created based on the fixed known set // * added to lcidMap. // */ // private static void createLCIDLanguageCompatibilityMap() { // if (lcidLanguageCompatibilityMap != null) { // return; // } // HashMap> result = new HashMap<>(); // for (Entry e : lcidMap.entrySet()) { // String language = e.getKey(); // int index = language.indexOf('_'); // if (index != -1) { // language = language.substring(0, index); // } // List list = result.get(language); // if (list == null) { // list = new ArrayList<>(); // result.put(language, list); // } // if (index == -1) { // list.add(0, e.getValue()); // } else{ // list.add(e.getValue()); // } // } // Map compMap = new HashMap<>(); // for (Entry> e : result.entrySet()) { // if (e.getValue().size() > 1) { // List list = e.getValue(); // short[] shorts = new short[list.size()]; // for (int i = 0; i < shorts.length; i++) { // shorts[i] = list.get(i); // } // compMap.put(e.getKey(), shorts); // } // } // /* Now dump code to init the map to System.out */ // prtLine(" private static void createLCIDLanguageCompatibilityMap() {"); // prtLine(""); // prtLine(" Map map = new HashMap<>();"); // prtLine(""); // prtLine(" short[] sarr;"); // for (Entry e : compMap.entrySet()) { // String lang = e.getKey(); // short[] ids = e.getValue(); // StringBuilder sb = new StringBuilder("sarr = new short[] { "); // for (int i = 0; i < ids.length; i++) { // sb.append(ids[i]+", "); // } // sb.append("}"); // prtLine(" " + sb + ";"); // prtLine(" map.put(\"" + lang + "\", sarr);"); // } // prtLine(""); // prtLine(" lcidLanguageCompatibilityMap = map;"); // prtLine(" }"); // /* done dumping map */ // lcidLanguageCompatibilityMap = compMap; // } private static void createLCIDLanguageCompatibilityMap() { Map map = new HashMap<>(); short[] sarr; sarr = new short[] { 1031, 3079, 5127, 2055, 4103, }; map.put("de", sarr); sarr = new short[] { 1044, 2068, }; map.put("no", sarr); sarr = new short[] { 1049, 2073, }; map.put("ru", sarr); sarr = new short[] { 1053, 2077, }; map.put("sv", sarr); sarr = new short[] { 1046, 2070, }; map.put("pt", sarr); sarr = new short[] { 1131, 3179, 2155, }; map.put("qu", sarr); sarr = new short[] { 1086, 2110, }; map.put("ms", sarr); sarr = new short[] { 11273, 3081, 12297, 8201, 10249, 4105, 13321, 6153, 7177, 5129, 2057, }; map.put("en", sarr); sarr = new short[] { 1050, 4122, }; map.put("hr", sarr); sarr = new short[] { 1040, 2064, }; map.put("it", sarr); sarr = new short[] { 1036, 5132, 6156, 2060, 3084, 4108, }; map.put("fr", sarr); sarr = new short[] { 1034, 12298, 14346, 2058, 8202, 19466, 17418, 9226, 13322, 5130, 7178, 11274, 16394, 4106, 10250, 6154, 18442, 20490, 15370, }; map.put("es", sarr); sarr = new short[] { 1028, 3076, 5124, 4100, 2052, }; map.put("zh", sarr); sarr = new short[] { 1025, 8193, 16385, 9217, 2049, 14337, 15361, 11265, 13313, 10241, 7169, 12289, 4097, 5121, 6145, 3073, }; map.put("ar", sarr); sarr = new short[] { 1083, 3131, 2107, }; map.put("se", sarr); sarr = new short[] { 1048, 2072, }; map.put("ro", sarr); sarr = new short[] { 1043, 2067, }; map.put("nl", sarr); sarr = new short[] { 7194, 3098, }; map.put("sr", sarr); lcidLanguageCompatibilityMap = map; } }