| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.fop.fonts.truetype; |
| |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import org.apache.xmlgraphics.fonts.Glyphs; |
| |
| import org.apache.fop.fonts.FontUtil; |
| |
| /** |
| * Reads a TrueType file or a TrueType Collection. |
| * The TrueType spec can be found at the Microsoft. |
| * Typography site: http://www.microsoft.com/truetype/ |
| */ |
| public class TTFFile { |
| |
| static final byte NTABS = 24; |
| static final int NMACGLYPHS = 258; |
| static final int MAX_CHAR_CODE = 255; |
| static final int ENC_BUF_SIZE = 1024; |
| |
| /** Set to true to get even more debug output than with level DEBUG */ |
| public static final boolean TRACE_ENABLED = false; |
| |
| private String encoding = "WinAnsiEncoding"; // Default encoding |
| |
| private short firstChar = 0; |
| private boolean isEmbeddable = true; |
| private boolean hasSerifs = true; |
| /** |
| * Table directory |
| */ |
| protected Map dirTabs; |
| private Map kerningTab; // for CIDs |
| private Map ansiKerningTab; // For winAnsiEncoding |
| private List cmaps; |
| private List unicodeMapping; |
| |
| private int upem; // unitsPerEm from "head" table |
| private int nhmtx; // Number of horizontal metrics |
| private int postFormat; |
| private int locaFormat; |
| /** |
| * Offset to last loca |
| */ |
| protected long lastLoca = 0; |
| private int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table) |
| private int nmGlyphs; // Used in fixWidths - remove? |
| |
| /** |
| * Contains glyph data |
| */ |
| protected TTFMtxEntry[] mtxTab; // Contains glyph data |
| private int[] mtxEncoded = null; |
| |
| private String postScriptName = ""; |
| private String fullName = ""; |
| private String notice = ""; |
| private Set familyNames = new java.util.HashSet(); //Set<String> |
| private String subFamilyName = ""; |
| |
| private long italicAngle = 0; |
| private long isFixedPitch = 0; |
| private int fontBBox1 = 0; |
| private int fontBBox2 = 0; |
| private int fontBBox3 = 0; |
| private int fontBBox4 = 0; |
| private int capHeight = 0; |
| private int os2CapHeight = 0; |
| private int underlinePosition = 0; |
| private int underlineThickness = 0; |
| private int xHeight = 0; |
| private int os2xHeight = 0; |
| //Effective ascender/descender |
| private int ascender = 0; |
| private int descender = 0; |
| //Ascender/descender from hhea table |
| private int hheaAscender = 0; |
| private int hheaDescender = 0; |
| //Ascender/descender from OS/2 table |
| private int os2Ascender = 0; |
| private int os2Descender = 0; |
| private int usWeightClass = 0; |
| |
| private short lastChar = 0; |
| |
| private int[] ansiWidth; |
| private Map ansiIndex; |
| |
| // internal mapping of glyph indexes to unicode indexes |
| // used for quick mappings in this class |
| private Map glyphToUnicodeMap = new java.util.HashMap(); |
| private Map unicodeToGlyphMap = new java.util.HashMap(); |
| |
| private TTFDirTabEntry currentDirTab; |
| |
| private boolean isCFF; |
| |
| /** |
| * logging instance |
| */ |
| protected Log log = LogFactory.getLog(TTFFile.class); |
| |
| /** |
| * Key-value helper class |
| */ |
| class UnicodeMapping { |
| |
| private int unicodeIndex; |
| private int glyphIndex; |
| |
| UnicodeMapping(int glyphIndex, int unicodeIndex) { |
| this.unicodeIndex = unicodeIndex; |
| this.glyphIndex = glyphIndex; |
| glyphToUnicodeMap.put(new Integer(glyphIndex), new Integer(unicodeIndex)); |
| unicodeToGlyphMap.put(new Integer(unicodeIndex), new Integer(glyphIndex)); |
| } |
| |
| /** |
| * Returns the glyphIndex. |
| * @return the glyph index |
| */ |
| public int getGlyphIndex() { |
| return glyphIndex; |
| } |
| |
| /** |
| * Returns the unicodeIndex. |
| * @return the Unicode index |
| */ |
| public int getUnicodeIndex() { |
| return unicodeIndex; |
| } |
| } |
| |
| /** |
| * Position inputstream to position indicated |
| * in the dirtab offset + offset |
| */ |
| boolean seekTab(FontFileReader in, String name, |
| long offset) throws IOException { |
| TTFDirTabEntry dt = (TTFDirTabEntry)dirTabs.get(name); |
| if (dt == null) { |
| log.error("Dirtab " + name + " not found."); |
| return false; |
| } else { |
| in.seekSet(dt.getOffset() + offset); |
| this.currentDirTab = dt; |
| } |
| return true; |
| } |
| |
| /** |
| * Convert from truetype unit to pdf unit based on the |
| * unitsPerEm field in the "head" table |
| * @param n truetype unit |
| * @return pdf unit |
| */ |
| public int convertTTFUnit2PDFUnit(int n) { |
| int ret; |
| if (n < 0) { |
| long rest1 = n % upem; |
| long storrest = 1000 * rest1; |
| long ledd2 = (storrest != 0 ? rest1 / storrest : 0); |
| ret = -((-1000 * n) / upem - (int)ledd2); |
| } else { |
| ret = (n / upem) * 1000 + ((n % upem) * 1000) / upem; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Read the cmap table, |
| * return false if the table is not present or only unsupported |
| * tables are present. Currently only unicode cmaps are supported. |
| * Set the unicodeIndex in the TTFMtxEntries and fills in the |
| * cmaps vector. |
| */ |
| private boolean readCMAP(FontFileReader in) throws IOException { |
| |
| unicodeMapping = new java.util.ArrayList(); |
| |
| //Read CMAP table and correct mtxTab.index |
| int mtxPtr = 0; |
| |
| seekTab(in, "cmap", 2); |
| int numCMap = in.readTTFUShort(); // Number of cmap subtables |
| long cmapUniOffset = 0; |
| |
| if (log.isDebugEnabled()) { |
| log.debug(numCMap + " cmap tables"); |
| } |
| |
| //Read offset for all tables. We are only interested in the unicode table |
| for (int i = 0; i < numCMap; i++) { |
| int cmapPID = in.readTTFUShort(); |
| int cmapEID = in.readTTFUShort(); |
| long cmapOffset = in.readTTFULong(); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); |
| } |
| |
| if (cmapPID == 3 && cmapEID == 1) { |
| cmapUniOffset = cmapOffset; |
| } |
| } |
| |
| if (cmapUniOffset <= 0) { |
| log.fatal("Unsupported TrueType font: Unicode cmap table not present. Aborting"); |
| return false; |
| } |
| |
| // Read unicode cmap |
| seekTab(in, "cmap", cmapUniOffset); |
| int cmapFormat = in.readTTFUShort(); |
| /*int cmap_length =*/ in.readTTFUShort(); //skip cmap length |
| |
| if (log.isDebugEnabled()) { |
| log.debug("CMAP format: " + cmapFormat); |
| } |
| |
| if (cmapFormat == 4) { |
| in.skip(2); // Skip version number |
| int cmapSegCountX2 = in.readTTFUShort(); |
| int cmapSearchRange = in.readTTFUShort(); |
| int cmapEntrySelector = in.readTTFUShort(); |
| int cmapRangeShift = in.readTTFUShort(); |
| |
| if (log.isDebugEnabled()) { |
| log.debug("segCountX2 : " + cmapSegCountX2); |
| log.debug("searchRange : " + cmapSearchRange); |
| log.debug("entrySelector: " + cmapEntrySelector); |
| log.debug("rangeShift : " + cmapRangeShift); |
| } |
| |
| |
| int[] cmapEndCounts = new int[cmapSegCountX2 / 2]; |
| int[] cmapStartCounts = new int[cmapSegCountX2 / 2]; |
| int[] cmapDeltas = new int[cmapSegCountX2 / 2]; |
| int[] cmapRangeOffsets = new int[cmapSegCountX2 / 2]; |
| |
| for (int i = 0; i < (cmapSegCountX2 / 2); i++) { |
| cmapEndCounts[i] = in.readTTFUShort(); |
| } |
| |
| in.skip(2); // Skip reservedPad |
| |
| for (int i = 0; i < (cmapSegCountX2 / 2); i++) { |
| cmapStartCounts[i] = in.readTTFUShort(); |
| } |
| |
| for (int i = 0; i < (cmapSegCountX2 / 2); i++) { |
| cmapDeltas[i] = in.readTTFShort(); |
| } |
| |
| //int startRangeOffset = in.getCurrentPos(); |
| |
| for (int i = 0; i < (cmapSegCountX2 / 2); i++) { |
| cmapRangeOffsets[i] = in.readTTFUShort(); |
| } |
| |
| int glyphIdArrayOffset = in.getCurrentPos(); |
| |
| // Insert the unicode id for the glyphs in mtxTab |
| // and fill in the cmaps ArrayList |
| |
| for (int i = 0; i < cmapStartCounts.length; i++) { |
| |
| if (log.isTraceEnabled()) { |
| log.trace(i + ": " + cmapStartCounts[i] |
| + " - " + cmapEndCounts[i]); |
| } |
| |
| for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { |
| |
| // Update lastChar |
| if (j < 256 && j > lastChar) { |
| lastChar = (short)j; |
| } |
| |
| if (mtxPtr < mtxTab.length) { |
| int glyphIdx; |
| // the last character 65535 = .notdef |
| // may have a range offset |
| if (cmapRangeOffsets[i] != 0 && j != 65535) { |
| int glyphOffset = glyphIdArrayOffset |
| + ((cmapRangeOffsets[i] / 2) |
| + (j - cmapStartCounts[i]) |
| + (i) |
| - cmapSegCountX2 / 2) * 2; |
| in.seekSet(glyphOffset); |
| glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) |
| & 0xffff; |
| |
| unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); |
| mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); |
| |
| |
| // Also add winAnsiWidth |
| List v = (List)ansiIndex.get(new Integer(j)); |
| if (v != null) { |
| Iterator e = v.listIterator(); |
| while (e.hasNext()) { |
| Integer aIdx = (Integer)e.next(); |
| ansiWidth[aIdx.intValue()] |
| = mtxTab[glyphIdx].getWx(); |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Added width " |
| + mtxTab[glyphIdx].getWx() |
| + " uni: " + j |
| + " ansi: " + aIdx.intValue()); |
| } |
| } |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("Idx: " |
| + glyphIdx |
| + " Delta: " + cmapDeltas[i] |
| + " Unicode: " + j |
| + " name: " + mtxTab[glyphIdx].getName()); |
| } |
| } else { |
| glyphIdx = (j + cmapDeltas[i]) & 0xffff; |
| |
| if (glyphIdx < mtxTab.length) { |
| mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); |
| } else { |
| log.debug("Glyph " + glyphIdx |
| + " out of range: " |
| + mtxTab.length); |
| } |
| |
| unicodeMapping.add(new UnicodeMapping(glyphIdx, j)); |
| if (glyphIdx < mtxTab.length) { |
| mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j)); |
| } else { |
| log.debug("Glyph " + glyphIdx |
| + " out of range: " |
| + mtxTab.length); |
| } |
| |
| // Also add winAnsiWidth |
| List v = (List)ansiIndex.get(new Integer(j)); |
| if (v != null) { |
| Iterator e = v.listIterator(); |
| while (e.hasNext()) { |
| Integer aIdx = (Integer)e.next(); |
| ansiWidth[aIdx.intValue()] = mtxTab[glyphIdx].getWx(); |
| } |
| } |
| |
| //getLogger().debug("IIdx: " + |
| // mtxPtr + |
| // " Delta: " + cmap_deltas[i] + |
| // " Unicode: " + j + |
| // " name: " + |
| // mtxTab[(j+cmap_deltas[i]) & 0xffff].name); |
| |
| } |
| if (glyphIdx < mtxTab.length) { |
| if (mtxTab[glyphIdx].getUnicodeIndex().size() < 2) { |
| mtxPtr++; |
| } |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Print first char/last char |
| */ |
| private void printMaxMin() { |
| int min = 255; |
| int max = 0; |
| for (int i = 0; i < mtxTab.length; i++) { |
| if (mtxTab[i].getIndex() < min) { |
| min = mtxTab[i].getIndex(); |
| } |
| if (mtxTab[i].getIndex() > max) { |
| max = mtxTab[i].getIndex(); |
| } |
| } |
| log.info("Min: " + min); |
| log.info("Max: " + max); |
| } |
| |
| |
| /** |
| * Reads the font using a FontFileReader. |
| * |
| * @param in The FontFileReader to use |
| * @throws IOException In case of an I/O problem |
| */ |
| public void readFont(FontFileReader in) throws IOException { |
| readFont(in, (String)null); |
| } |
| |
| /** |
| * initialize the ansiWidths array (for winAnsiEncoding) |
| * and fill with the missingwidth |
| */ |
| private void initAnsiWidths() { |
| ansiWidth = new int[256]; |
| for (int i = 0; i < 256; i++) { |
| ansiWidth[i] = mtxTab[0].getWx(); |
| } |
| |
| // Create an index hash to the ansiWidth |
| // Can't just index the winAnsiEncoding when inserting widths |
| // same char (eg bullet) is repeated more than one place |
| ansiIndex = new java.util.HashMap(); |
| for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { |
| Integer ansi = new Integer(i); |
| Integer uni = new Integer((int)Glyphs.WINANSI_ENCODING[i]); |
| |
| List v = (List)ansiIndex.get(uni); |
| if (v == null) { |
| v = new java.util.ArrayList(); |
| ansiIndex.put(uni, v); |
| } |
| v.add(ansi); |
| } |
| } |
| |
| /** |
| * Read the font data. |
| * If the fontfile is a TrueType Collection (.ttc file) |
| * the name of the font to read data for must be supplied, |
| * else the name is ignored. |
| * |
| * @param in The FontFileReader to use |
| * @param name The name of the font |
| * @return boolean Returns true if the font is valid |
| * @throws IOException In case of an I/O problem |
| */ |
| public boolean readFont(FontFileReader in, String name) throws IOException { |
| |
| /* |
| * Check if TrueType collection, and that the name |
| * exists in the collection |
| */ |
| if (!checkTTC(in, name)) { |
| if (name == null) { |
| throw new IllegalArgumentException( |
| "For TrueType collection you must specify which font " |
| + "to select (-ttcname)"); |
| } else { |
| throw new IOException( |
| "Name does not exist in the TrueType collection: " + name); |
| } |
| } |
| |
| readDirTabs(in); |
| readFontHeader(in); |
| getNumGlyphs(in); |
| if (log.isDebugEnabled()) { |
| log.debug("Number of glyphs in font: " + numberOfGlyphs); |
| } |
| readHorizontalHeader(in); |
| readHorizontalMetrics(in); |
| initAnsiWidths(); |
| readPostScript(in); |
| readOS2(in); |
| determineAscDesc(); |
| if (!isCFF) { |
| readIndexToLocation(in); |
| readGlyf(in); |
| } |
| readName(in); |
| boolean pcltFound = readPCLT(in); |
| // Read cmap table and fill in ansiwidths |
| boolean valid = readCMAP(in); |
| if (!valid) { |
| return false; |
| } |
| // Create cmaps for bfentries |
| createCMaps(); |
| // print_max_min(); |
| |
| readKerning(in); |
| guessVerticalMetricsFromGlyphBBox(); |
| return true; |
| } |
| |
| private void createCMaps() { |
| cmaps = new java.util.ArrayList(); |
| TTFCmapEntry tce = new TTFCmapEntry(); |
| |
| Iterator e = unicodeMapping.listIterator(); |
| UnicodeMapping um = (UnicodeMapping)e.next(); |
| UnicodeMapping lastMapping = um; |
| |
| tce.setUnicodeStart(um.getUnicodeIndex()); |
| tce.setGlyphStartIndex(um.getGlyphIndex()); |
| |
| while (e.hasNext()) { |
| um = (UnicodeMapping)e.next(); |
| if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) |
| || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { |
| tce.setUnicodeEnd(lastMapping.getUnicodeIndex()); |
| cmaps.add(tce); |
| |
| tce = new TTFCmapEntry(); |
| tce.setUnicodeStart(um.getUnicodeIndex()); |
| tce.setGlyphStartIndex(um.getGlyphIndex()); |
| } |
| lastMapping = um; |
| } |
| |
| tce.setUnicodeEnd(um.getUnicodeIndex()); |
| cmaps.add(tce); |
| } |
| |
| /** |
| * Returns the PostScript name of the font. |
| * @return String The PostScript name |
| */ |
| public String getPostScriptName() { |
| if (postScriptName.length() == 0) { |
| return FontUtil.stripWhiteSpace(getFullName()); |
| } else { |
| return postScriptName; |
| } |
| } |
| |
| /** |
| * Returns the font family names of the font. |
| * @return Set The family names (a Set of Strings) |
| */ |
| public Set getFamilyNames() { |
| return familyNames; |
| } |
| |
| /** |
| * Returns the font sub family name of the font. |
| * @return String The sub family name |
| */ |
| public String getSubFamilyName() { |
| return subFamilyName; |
| } |
| |
| /** |
| * Returns the full name of the font. |
| * @return String The full name |
| */ |
| public String getFullName() { |
| return fullName; |
| } |
| |
| /** |
| * Returns the name of the character set used. |
| * @return String The caracter set |
| */ |
| public String getCharSetName() { |
| return encoding; |
| } |
| |
| /** |
| * Returns the CapHeight attribute of the font. |
| * @return int The CapHeight |
| */ |
| public int getCapHeight() { |
| return (int)convertTTFUnit2PDFUnit(capHeight); |
| } |
| |
| /** |
| * Returns the XHeight attribute of the font. |
| * @return int The XHeight |
| */ |
| public int getXHeight() { |
| return (int)convertTTFUnit2PDFUnit(xHeight); |
| } |
| |
| /** |
| * Returns the Flags attribute of the font. |
| * @return int The Flags |
| */ |
| public int getFlags() { |
| int flags = 32; // Use Adobe Standard charset |
| if (italicAngle != 0) { |
| flags = flags | 64; |
| } |
| if (isFixedPitch != 0) { |
| flags = flags | 2; |
| } |
| if (hasSerifs) { |
| flags = flags | 1; |
| } |
| return flags; |
| } |
| |
| /** |
| * Returns the weight class of this font. Valid values are 100, 200....,800, 900. |
| * @return the weight class value (or 0 if there was no OS/2 table in the font) |
| */ |
| public int getWeightClass() { |
| return this.usWeightClass; |
| } |
| |
| /** |
| * Returns the StemV attribute of the font. |
| * @return String The StemV |
| */ |
| public String getStemV() { |
| return "0"; |
| } |
| |
| /** |
| * Returns the ItalicAngle attribute of the font. |
| * @return String The ItalicAngle |
| */ |
| public String getItalicAngle() { |
| String ia = Short.toString((short)(italicAngle / 0x10000)); |
| |
| // This is the correct italic angle, however only int italic |
| // angles are supported at the moment so this is commented out. |
| /* |
| * if ((italicAngle % 0x10000) > 0 ) |
| * ia=ia+(comma+Short.toString((short)((short)((italicAngle % 0x10000)*1000)/0x10000))); |
| */ |
| return ia; |
| } |
| |
| /** |
| * Returns the font bounding box. |
| * @return int[] The font bbox |
| */ |
| public int[] getFontBBox() { |
| final int[] fbb = new int[4]; |
| fbb[0] = (int)convertTTFUnit2PDFUnit(fontBBox1); |
| fbb[1] = (int)convertTTFUnit2PDFUnit(fontBBox2); |
| fbb[2] = (int)convertTTFUnit2PDFUnit(fontBBox3); |
| fbb[3] = (int)convertTTFUnit2PDFUnit(fontBBox4); |
| |
| return fbb; |
| } |
| |
| /** |
| * Returns the LowerCaseAscent attribute of the font. |
| * @return int The LowerCaseAscent |
| */ |
| public int getLowerCaseAscent() { |
| return (int)convertTTFUnit2PDFUnit(ascender); |
| } |
| |
| /** |
| * Returns the LowerCaseDescent attribute of the font. |
| * @return int The LowerCaseDescent |
| */ |
| public int getLowerCaseDescent() { |
| return (int)convertTTFUnit2PDFUnit(descender); |
| } |
| |
| /** |
| * Returns the index of the last character, but this is for WinAnsiEncoding |
| * only, so the last char is < 256. |
| * @return short Index of the last character (<256) |
| */ |
| public short getLastChar() { |
| return lastChar; |
| } |
| |
| /** |
| * Returns the index of the first character. |
| * @return short Index of the first character |
| */ |
| public short getFirstChar() { |
| return firstChar; |
| } |
| |
| /** |
| * Returns an array of character widths. |
| * @return int[] The character widths |
| */ |
| public int[] getWidths() { |
| int[] wx = new int[mtxTab.length]; |
| for (int i = 0; i < wx.length; i++) { |
| wx[i] = (int)convertTTFUnit2PDFUnit(mtxTab[i].getWx()); |
| } |
| |
| return wx; |
| } |
| |
| /** |
| * Returns the width of a given character. |
| * @param idx Index of the character |
| * @return int Standard width |
| */ |
| public int getCharWidth(int idx) { |
| return (int)convertTTFUnit2PDFUnit(ansiWidth[idx]); |
| } |
| |
| /** |
| * Returns the kerning table. |
| * @return Map The kerning table |
| */ |
| public Map getKerning() { |
| return kerningTab; |
| } |
| |
| /** |
| * Returns the ANSI kerning table. |
| * @return Map The ANSI kerning table |
| */ |
| public Map getAnsiKerning() { |
| return ansiKerningTab; |
| } |
| |
| /** |
| * Indicates if the font may be embedded. |
| * @return boolean True if it may be embedded |
| */ |
| public boolean isEmbeddable() { |
| return isEmbeddable; |
| } |
| |
| /** |
| * Indicates whether or not the font is an OpenType |
| * CFF font (rather than a TrueType font). |
| * @return true if the font is in OpenType CFF format. |
| */ |
| public boolean isCFF() { |
| return this.isCFF; |
| } |
| |
| /** |
| * Read Table Directory from the current position in the |
| * FontFileReader and fill the global HashMap dirTabs |
| * with the table name (String) as key and a TTFDirTabEntry |
| * as value. |
| * @param in FontFileReader to read the table directory from |
| * @throws IOException in case of an I/O problem |
| */ |
| protected void readDirTabs(FontFileReader in) throws IOException { |
| int sfntVersion = in.readTTFLong(); // TTF_FIXED_SIZE (4 bytes) |
| switch (sfntVersion) { |
| case 0x10000: |
| log.debug("sfnt version: OpenType 1.0"); |
| break; |
| case 0x4F54544F: //"OTTO" |
| this.isCFF = true; |
| log.debug("sfnt version: OpenType with CFF data"); |
| break; |
| case 0x74727565: //"true" |
| log.debug("sfnt version: Apple TrueType"); |
| break; |
| case 0x74797031: //"typ1" |
| log.debug("sfnt version: Apple Type 1 housed in sfnt wrapper"); |
| break; |
| default: |
| log.debug("Unknown sfnt version: " + Integer.toHexString(sfntVersion)); |
| break; |
| } |
| int ntabs = in.readTTFUShort(); |
| in.skip(6); // 3xTTF_USHORT_SIZE |
| |
| dirTabs = new java.util.HashMap(); |
| TTFDirTabEntry[] pd = new TTFDirTabEntry[ntabs]; |
| log.debug("Reading " + ntabs + " dir tables"); |
| for (int i = 0; i < ntabs; i++) { |
| pd[i] = new TTFDirTabEntry(); |
| dirTabs.put(pd[i].read(in), pd[i]); |
| } |
| log.debug("dir tables: " + dirTabs.keySet()); |
| } |
| |
| /** |
| * Read the "head" table, this reads the bounding box and |
| * sets the upem (unitsPerEM) variable |
| * @param in FontFileReader to read the header from |
| * @throws IOException in case of an I/O problem |
| */ |
| protected void readFontHeader(FontFileReader in) throws IOException { |
| seekTab(in, "head", 2 * 4 + 2 * 4 + 2); |
| upem = in.readTTFUShort(); |
| log.debug("unit per em: " + upem); |
| |
| in.skip(16); |
| |
| fontBBox1 = in.readTTFShort(); |
| fontBBox2 = in.readTTFShort(); |
| fontBBox3 = in.readTTFShort(); |
| fontBBox4 = in.readTTFShort(); |
| |
| in.skip(2 + 2 + 2); |
| |
| locaFormat = in.readTTFShort(); |
| } |
| |
| /** |
| * Read the number of glyphs from the "maxp" table |
| * @param in FontFileReader to read the number of glyphs from |
| * @throws IOException in case of an I/O problem |
| */ |
| protected void getNumGlyphs(FontFileReader in) throws IOException { |
| seekTab(in, "maxp", 4); |
| numberOfGlyphs = in.readTTFUShort(); |
| } |
| |
| |
| /** |
| * Read the "hhea" table to find the ascender and descender and |
| * size of "hmtx" table, as a fixed size font might have only |
| * one width. |
| * @param in FontFileReader to read the hhea table from |
| * @throws IOException in case of an I/O problem |
| */ |
| protected void readHorizontalHeader(FontFileReader in) |
| throws IOException { |
| seekTab(in, "hhea", 4); |
| hheaAscender = in.readTTFShort(); |
| log.debug("hhea.Ascender: " + hheaAscender + " " + convertTTFUnit2PDFUnit(hheaAscender)); |
| hheaDescender = in.readTTFShort(); |
| log.debug("hhea.Descender: " + hheaDescender + " " + convertTTFUnit2PDFUnit(hheaDescender)); |
| |
| in.skip(2 + 2 + 3 * 2 + 8 * 2); |
| nhmtx = in.readTTFUShort(); |
| log.debug("Number of horizontal metrics: " + nhmtx); |
| |
| |
| } |
| |
| /** |
| * Read "hmtx" table and put the horizontal metrics |
| * in the mtxTab array. If the number of metrics is less |
| * than the number of glyphs (eg fixed size fonts), extend |
| * the mtxTab array and fill in the missing widths |
| * @param in FontFileReader to read the hmtx table from |
| * @throws IOException in case of an I/O problem |
| */ |
| protected void readHorizontalMetrics(FontFileReader in) |
| throws IOException { |
| seekTab(in, "hmtx", 0); |
| |
| int mtxSize = Math.max(numberOfGlyphs, nhmtx); |
| mtxTab = new TTFMtxEntry[mtxSize]; |
| |
| if (TRACE_ENABLED) { |
| log.debug("*** Widths array: \n"); |
| } |
| for (int i = 0; i < mtxSize; i++) { |
| mtxTab[i] = new TTFMtxEntry(); |
| } |
| for (int i = 0; i < nhmtx; i++) { |
| mtxTab[i].setWx(in.readTTFUShort()); |
| mtxTab[i].setLsb(in.readTTFUShort()); |
| |
| if (TRACE_ENABLED) { |
| if (log.isDebugEnabled()) { |
| log.debug(" width[" + i + "] = " |
| + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); |
| } |
| } |
| } |
| |
| if (nhmtx < mtxSize) { |
| // Fill in the missing widths |
| int lastWidth = mtxTab[nhmtx - 1].getWx(); |
| for (int i = nhmtx; i < mtxSize; i++) { |
| mtxTab[i].setWx(lastWidth); |
| mtxTab[i].setLsb(in.readTTFUShort()); |
| } |
| } |
| } |
| |
| |
| /** |
| * Read the "post" table |
| * containing the PostScript names of the glyphs. |
| */ |
| private final void readPostScript(FontFileReader in) throws IOException { |
| seekTab(in, "post", 0); |
| postFormat = in.readTTFLong(); |
| italicAngle = in.readTTFULong(); |
| underlinePosition = in.readTTFShort(); |
| underlineThickness = in.readTTFShort(); |
| isFixedPitch = in.readTTFULong(); |
| |
| //Skip memory usage values |
| in.skip(4 * 4); |
| |
| log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); |
| switch (postFormat) { |
| case 0x00010000: |
| log.debug("PostScript format 1"); |
| for (int i = 0; i < Glyphs.MAC_GLYPH_NAMES.length; i++) { |
| mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[i]); |
| } |
| break; |
| case 0x00020000: |
| log.debug("PostScript format 2"); |
| int numGlyphStrings = 0; |
| |
| // Read Number of Glyphs |
| int l = in.readTTFUShort(); |
| |
| // Read indexes |
| for (int i = 0; i < l; i++) { |
| mtxTab[i].setIndex(in.readTTFUShort()); |
| |
| if (mtxTab[i].getIndex() > 257) { |
| //Index is not in the Macintosh standard set |
| numGlyphStrings++; |
| } |
| |
| if (log.isTraceEnabled()) { |
| log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); |
| } |
| } |
| |
| // firstChar=minIndex; |
| String[] psGlyphsBuffer = new String[numGlyphStrings]; |
| if (log.isDebugEnabled()) { |
| log.debug("Reading " + numGlyphStrings |
| + " glyphnames, that are not in the standard Macintosh" |
| + " set. Total number of glyphs=" + l); |
| } |
| for (int i = 0; i < psGlyphsBuffer.length; i++) { |
| psGlyphsBuffer[i] = in.readTTFString(in.readTTFUByte()); |
| } |
| |
| //Set glyph names |
| for (int i = 0; i < l; i++) { |
| if (mtxTab[i].getIndex() < NMACGLYPHS) { |
| mtxTab[i].setName(Glyphs.MAC_GLYPH_NAMES[mtxTab[i].getIndex()]); |
| } else { |
| if (!mtxTab[i].isIndexReserved()) { |
| int k = mtxTab[i].getIndex() - NMACGLYPHS; |
| |
| if (log.isTraceEnabled()) { |
| log.trace(k + " i=" + i + " mtx=" + mtxTab.length |
| + " ps=" + psGlyphsBuffer.length); |
| } |
| |
| mtxTab[i].setName(psGlyphsBuffer[k]); |
| } |
| } |
| } |
| |
| break; |
| case 0x00030000: |
| // PostScript format 3 contains no glyph names |
| log.debug("PostScript format 3"); |
| break; |
| default: |
| log.error("Unknown PostScript format: " + postFormat); |
| } |
| } |
| |
| |
| /** |
| * Read the "OS/2" table |
| */ |
| private final void readOS2(FontFileReader in) throws IOException { |
| // Check if font is embeddable |
| if (dirTabs.get("OS/2") != null) { |
| seekTab(in, "OS/2", 2 * 2); |
| this.usWeightClass = in.readTTFUShort(); |
| |
| // usWidthClass |
| in.skip(2); |
| |
| int fsType = in.readTTFUShort(); |
| if (fsType == 2) { |
| isEmbeddable = false; |
| } else { |
| isEmbeddable = true; |
| } |
| in.skip(11 * 2); |
| in.skip(10); //panose array |
| in.skip(4 * 4); //unicode ranges |
| in.skip(4); |
| in.skip(3 * 2); |
| int v; |
| os2Ascender = in.readTTFShort(); //sTypoAscender |
| log.debug("sTypoAscender: " + os2Ascender |
| + " " + convertTTFUnit2PDFUnit(os2Ascender)); |
| os2Descender = in.readTTFShort(); //sTypoDescender |
| log.debug("sTypoDescender: " + os2Descender |
| + " " + convertTTFUnit2PDFUnit(os2Descender)); |
| v = in.readTTFShort(); //sTypoLineGap |
| log.debug("sTypoLineGap: " + v); |
| v = in.readTTFUShort(); //usWinAscent |
| log.debug("usWinAscent: " + v + " " + convertTTFUnit2PDFUnit(v)); |
| v = in.readTTFUShort(); //usWinDescent |
| log.debug("usWinDescent: " + v + " " + convertTTFUnit2PDFUnit(v)); |
| in.skip(2 * 4); |
| this.os2xHeight = in.readTTFShort(); //sxHeight |
| log.debug("sxHeight: " + this.os2xHeight); |
| this.os2CapHeight = in.readTTFShort(); //sCapHeight |
| log.debug("sCapHeight: " + this.os2CapHeight); |
| |
| } else { |
| isEmbeddable = true; |
| } |
| } |
| |
| /** |
| * Read the "loca" table. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| protected final void readIndexToLocation(FontFileReader in) |
| throws IOException { |
| if (!seekTab(in, "loca", 0)) { |
| throw new IOException("'loca' table not found, happens when the font file doesn't" |
| + " contain TrueType outlines (trying to read an OpenType CFF font maybe?)"); |
| } |
| for (int i = 0; i < numberOfGlyphs; i++) { |
| mtxTab[i].setOffset(locaFormat == 1 ? in.readTTFULong() |
| : (in.readTTFUShort() << 1)); |
| } |
| lastLoca = (locaFormat == 1 ? in.readTTFULong() |
| : (in.readTTFUShort() << 1)); |
| } |
| |
| /** |
| * Read the "glyf" table to find the bounding boxes. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private final void readGlyf(FontFileReader in) throws IOException { |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("glyf"); |
| if (dirTab == null) { |
| throw new IOException("glyf table not found, cannot continue"); |
| } |
| for (int i = 0; i < (numberOfGlyphs - 1); i++) { |
| if (mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { |
| in.seekSet(dirTab.getOffset() + mtxTab[i].getOffset()); |
| in.skip(2); |
| final int[] bbox = { |
| in.readTTFShort(), |
| in.readTTFShort(), |
| in.readTTFShort(), |
| in.readTTFShort()}; |
| mtxTab[i].setBoundingBox(bbox); |
| } else { |
| mtxTab[i].setBoundingBox(mtxTab[0].getBoundingBox()); |
| } |
| } |
| |
| |
| long n = ((TTFDirTabEntry)dirTabs.get("glyf")).getOffset(); |
| for (int i = 0; i < numberOfGlyphs; i++) { |
| if ((i + 1) >= mtxTab.length |
| || mtxTab[i].getOffset() != mtxTab[i + 1].getOffset()) { |
| in.seekSet(n + mtxTab[i].getOffset()); |
| in.skip(2); |
| final int[] bbox = { |
| in.readTTFShort(), |
| in.readTTFShort(), |
| in.readTTFShort(), |
| in.readTTFShort()}; |
| mtxTab[i].setBoundingBox(bbox); |
| } else { |
| /**@todo Verify that this is correct, looks like a copy/paste bug (jm)*/ |
| final int bbox0 = mtxTab[0].getBoundingBox()[0]; |
| final int[] bbox = {bbox0, bbox0, bbox0, bbox0}; |
| mtxTab[i].setBoundingBox(bbox); |
| /* Original code |
| mtxTab[i].bbox[0] = mtxTab[0].bbox[0]; |
| mtxTab[i].bbox[1] = mtxTab[0].bbox[0]; |
| mtxTab[i].bbox[2] = mtxTab[0].bbox[0]; |
| mtxTab[i].bbox[3] = mtxTab[0].bbox[0]; */ |
| } |
| if (log.isTraceEnabled()) { |
| log.trace(mtxTab[i].toString(this)); |
| } |
| } |
| } |
| |
| /** |
| * Read the "name" table. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private final void readName(FontFileReader in) throws IOException { |
| seekTab(in, "name", 2); |
| int i = in.getCurrentPos(); |
| int n = in.readTTFUShort(); |
| int j = in.readTTFUShort() + i - 2; |
| i += 2 * 2; |
| |
| while (n-- > 0) { |
| // getLogger().debug("Iteration: " + n); |
| in.seekSet(i); |
| final int platformID = in.readTTFUShort(); |
| final int encodingID = in.readTTFUShort(); |
| final int languageID = in.readTTFUShort(); |
| |
| int k = in.readTTFUShort(); |
| int l = in.readTTFUShort(); |
| |
| if (((platformID == 1 || platformID == 3) |
| && (encodingID == 0 || encodingID == 1))) { |
| in.seekSet(j + in.readTTFUShort()); |
| String txt; |
| if (platformID == 3) { |
| txt = in.readTTFString(l, encodingID); |
| } else { |
| txt = in.readTTFString(l); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug(platformID + " " |
| + encodingID + " " |
| + languageID + " " |
| + k + " " + txt); |
| } |
| switch (k) { |
| case 0: |
| if (notice.length() == 0) { |
| notice = txt; |
| } |
| break; |
| case 1: //Font Family Name |
| case 16: //Preferred Family |
| familyNames.add(txt); |
| break; |
| case 2: |
| if (subFamilyName.length() == 0) { |
| subFamilyName = txt; |
| } |
| break; |
| case 4: |
| if (fullName.length() == 0 || (platformID == 3 && languageID == 1033)) { |
| fullName = txt; |
| } |
| break; |
| case 6: |
| if (postScriptName.length() == 0) { |
| postScriptName = txt; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| i += 6 * 2; |
| } |
| } |
| |
| /** |
| * Read the "PCLT" table to find xHeight and capHeight. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private final boolean readPCLT(FontFileReader in) throws IOException { |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("PCLT"); |
| if (dirTab != null) { |
| in.seekSet(dirTab.getOffset() + 4 + 4 + 2); |
| xHeight = in.readTTFUShort(); |
| log.debug("xHeight from PCLT: " + xHeight |
| + " " + convertTTFUnit2PDFUnit(xHeight)); |
| in.skip(2 * 2); |
| capHeight = in.readTTFUShort(); |
| log.debug("capHeight from PCLT: " + capHeight |
| + " " + convertTTFUnit2PDFUnit(capHeight)); |
| in.skip(2 + 16 + 8 + 6 + 1 + 1); |
| |
| int serifStyle = in.readTTFUByte(); |
| serifStyle = serifStyle >> 6; |
| serifStyle = serifStyle & 3; |
| if (serifStyle == 1) { |
| hasSerifs = false; |
| } else { |
| hasSerifs = true; |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| /** |
| * Determines the right source for the ascender and descender values. The problem here is |
| * that the interpretation of these values is not the same for every font. There doesn't seem |
| * to be a uniform definition of an ascender and a descender. In some fonts |
| * the hhea values are defined after the Apple interpretation, but not in every font. The |
| * same problem is in the OS/2 table. FOP needs the ascender and descender to determine the |
| * baseline so we need values which add up more or less to the "em box". However, due to |
| * accent modifiers a character can grow beyond the em box. |
| */ |
| private void determineAscDesc() { |
| int hheaBoxHeight = hheaAscender - hheaDescender; |
| int os2BoxHeight = os2Ascender - os2Descender; |
| if (os2Ascender > 0 && os2BoxHeight <= upem) { |
| ascender = os2Ascender; |
| descender = os2Descender; |
| } else if (hheaAscender > 0 && hheaBoxHeight <= upem) { |
| ascender = hheaAscender; |
| descender = hheaDescender; |
| } else { |
| if (os2Ascender > 0) { |
| //Fall back to info from OS/2 if possible |
| ascender = os2Ascender; |
| descender = os2Descender; |
| } else { |
| ascender = hheaAscender; |
| descender = hheaDescender; |
| } |
| } |
| |
| log.debug("Font box height: " + (ascender - descender)); |
| if (ascender - descender > upem) { |
| log.warn("Ascender and descender together are larger than the em box." |
| + " This could lead to a wrong baseline placement in Apache FOP."); |
| } |
| } |
| |
| private void guessVerticalMetricsFromGlyphBBox() { |
| // Approximate capHeight from height of "H" |
| // It's most unlikely that a font misses the PCLT table |
| // This also assumes that postscriptnames exists ("H") |
| // Should look it up int the cmap (that wouldn't help |
| // for charsets without H anyway...) |
| // Same for xHeight with the letter "x" |
| int localCapHeight = 0; |
| int localXHeight = 0; |
| int localAscender = 0; |
| int localDescender = 0; |
| for (int i = 0; i < mtxTab.length; i++) { |
| if ("H".equals(mtxTab[i].getName())) { |
| localCapHeight = mtxTab[i].getBoundingBox()[3]; |
| } else if ("x".equals(mtxTab[i].getName())) { |
| localXHeight = mtxTab[i].getBoundingBox()[3]; |
| } else if ("d".equals(mtxTab[i].getName())) { |
| localAscender = mtxTab[i].getBoundingBox()[3]; |
| } else if ("p".equals(mtxTab[i].getName())) { |
| localDescender = mtxTab[i].getBoundingBox()[1]; |
| } else { |
| // OpenType Fonts with a version 3.0 "post" table don't have glyph names. |
| // Use Unicode indices instead. |
| List unicodeIndex = mtxTab[i].getUnicodeIndex(); |
| if (unicodeIndex.size() > 0) { |
| //Only the first index is used |
| char ch = (char)((Integer)unicodeIndex.get(0)).intValue(); |
| if (ch == 'H') { |
| localCapHeight = mtxTab[i].getBoundingBox()[3]; |
| } else if (ch == 'x') { |
| localXHeight = mtxTab[i].getBoundingBox()[3]; |
| } else if (ch == 'd') { |
| localAscender = mtxTab[i].getBoundingBox()[3]; |
| } else if (ch == 'p') { |
| localDescender = mtxTab[i].getBoundingBox()[1]; |
| } |
| } |
| } |
| } |
| log.debug("Ascender from glyph 'd': " + localAscender |
| + " " + convertTTFUnit2PDFUnit(localAscender)); |
| log.debug("Descender from glyph 'p': " + localDescender |
| + " " + convertTTFUnit2PDFUnit(localDescender)); |
| if (ascender - descender > upem) { |
| log.debug("Replacing specified ascender/descender with derived values to get values" |
| + " which fit in the em box."); |
| ascender = localAscender; |
| descender = localDescender; |
| } |
| |
| log.debug("xHeight from glyph 'x': " + localXHeight |
| + " " + convertTTFUnit2PDFUnit(localXHeight)); |
| log.debug("CapHeight from glyph 'H': " + localCapHeight |
| + " " + convertTTFUnit2PDFUnit(localCapHeight)); |
| if (capHeight == 0) { |
| capHeight = localCapHeight; |
| if (capHeight == 0) { |
| capHeight = os2CapHeight; |
| } |
| if (capHeight == 0) { |
| log.warn("capHeight value could not be determined." |
| + " The font may not work as expected."); |
| } |
| } |
| if (xHeight == 0) { |
| xHeight = localXHeight; |
| if (xHeight == 0) { |
| xHeight = os2xHeight; |
| } |
| if (xHeight == 0) { |
| log.warn("xHeight value could not be determined." |
| + " The font may not work as expected."); |
| } |
| } |
| } |
| |
| /** |
| * Read the kerning table, create a table for both CIDs and |
| * winAnsiEncoding. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private final void readKerning(FontFileReader in) throws IOException { |
| // Read kerning |
| kerningTab = new java.util.HashMap(); |
| ansiKerningTab = new java.util.HashMap(); |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get("kern"); |
| if (dirTab != null) { |
| seekTab(in, "kern", 2); |
| for (int n = in.readTTFUShort(); n > 0; n--) { |
| in.skip(2 * 2); |
| int k = in.readTTFUShort(); |
| if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { |
| return; |
| } |
| if ((k >> 8) != 0) { |
| continue; |
| } |
| |
| k = in.readTTFUShort(); |
| in.skip(3 * 2); |
| while (k-- > 0) { |
| int i = in.readTTFUShort(); |
| int j = in.readTTFUShort(); |
| int kpx = in.readTTFShort(); |
| if (kpx != 0) { |
| // CID kerning table entry, using unicode indexes |
| final Integer iObj = glyphToUnicode(i); |
| final Integer u2 = glyphToUnicode(j); |
| if (iObj == null) { |
| // happens for many fonts (Ubuntu font set), |
| // stray entries in the kerning table?? |
| log.debug("Ignoring kerning pair because no Unicode index was" |
| + " found for the first glyph " + i); |
| } else if (u2 == null) { |
| log.debug("Ignoring kerning pair because Unicode index was" |
| + " found for the second glyph " + i); |
| } else { |
| Map adjTab = (Map)kerningTab.get(iObj); |
| if (adjTab == null) { |
| adjTab = new java.util.HashMap(); |
| } |
| adjTab.put(u2, new Integer((int)convertTTFUnit2PDFUnit(kpx))); |
| kerningTab.put(iObj, adjTab); |
| } |
| } |
| } |
| } |
| |
| // Create winAnsiEncoded kerning table from kerningTab |
| // (could probably be simplified, for now we remap back to CID indexes and |
| // then to winAnsi) |
| Iterator ae = kerningTab.keySet().iterator(); |
| while (ae.hasNext()) { |
| Integer unicodeKey1 = (Integer)ae.next(); |
| Integer cidKey1 = unicodeToGlyph(unicodeKey1.intValue()); |
| Map akpx = new java.util.HashMap(); |
| Map ckpx = (Map)kerningTab.get(unicodeKey1); |
| |
| Iterator aee = ckpx.keySet().iterator(); |
| while (aee.hasNext()) { |
| Integer unicodeKey2 = (Integer)aee.next(); |
| Integer cidKey2 = unicodeToGlyph(unicodeKey2.intValue()); |
| Integer kern = (Integer)ckpx.get(unicodeKey2); |
| |
| Iterator uniMap = mtxTab[cidKey2.intValue()].getUnicodeIndex().listIterator(); |
| while (uniMap.hasNext()) { |
| Integer unicodeKey = (Integer)uniMap.next(); |
| Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); |
| for (int u = 0; u < ansiKeys.length; u++) { |
| akpx.put(ansiKeys[u], kern); |
| } |
| } |
| } |
| |
| if (akpx.size() > 0) { |
| Iterator uniMap = mtxTab[cidKey1.intValue()].getUnicodeIndex().listIterator(); |
| while (uniMap.hasNext()) { |
| Integer unicodeKey = (Integer)uniMap.next(); |
| Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey.intValue()); |
| for (int u = 0; u < ansiKeys.length; u++) { |
| ansiKerningTab.put(ansiKeys[u], akpx); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Return a List with TTFCmapEntry. |
| * @return A list of TTFCmapEntry objects |
| */ |
| public List getCMaps() { |
| return cmaps; |
| } |
| |
| /** |
| * Check if this is a TrueType collection and that the given |
| * name exists in the collection. |
| * If it does, set offset in fontfile to the beginning of |
| * the Table Directory for that font. |
| * @param in FontFileReader to read from |
| * @param name The name to check |
| * @return True if not collection or font name present, false otherwise |
| * @throws IOException In case of an I/O problem |
| */ |
| protected final boolean checkTTC(FontFileReader in, String name) throws IOException { |
| String tag = in.readTTFString(4); |
| |
| if ("ttcf".equals(tag)) { |
| // This is a TrueType Collection |
| in.skip(4); |
| |
| // Read directory offsets |
| int numDirectories = (int)in.readTTFULong(); |
| // int numDirectories=in.readTTFUShort(); |
| long[] dirOffsets = new long[numDirectories]; |
| for (int i = 0; i < numDirectories; i++) { |
| dirOffsets[i] = in.readTTFULong(); |
| } |
| |
| log.info("This is a TrueType collection file with " |
| + numDirectories + " fonts"); |
| log.info("Containing the following fonts: "); |
| // Read all the directories and name tables to check |
| // If the font exists - this is a bit ugly, but... |
| boolean found = false; |
| |
| // Iterate through all name tables even if font |
| // Is found, just to show all the names |
| long dirTabOffset = 0; |
| for (int i = 0; (i < numDirectories); i++) { |
| in.seekSet(dirOffsets[i]); |
| readDirTabs(in); |
| |
| readName(in); |
| |
| if (fullName.equals(name)) { |
| found = true; |
| dirTabOffset = dirOffsets[i]; |
| log.info(fullName + " <-- selected"); |
| } else { |
| log.info(fullName); |
| } |
| |
| // Reset names |
| notice = ""; |
| fullName = ""; |
| familyNames.clear(); |
| postScriptName = ""; |
| subFamilyName = ""; |
| } |
| |
| in.seekSet(dirTabOffset); |
| return found; |
| } else { |
| in.seekSet(0); |
| return true; |
| } |
| } |
| |
| /** |
| * Return TTC font names |
| * @param in FontFileReader to read from |
| * @return True if not collection or font name present, false otherwise |
| * @throws IOException In case of an I/O problem |
| */ |
| public final List getTTCnames(FontFileReader in) throws IOException { |
| List fontNames = new java.util.ArrayList(); |
| |
| String tag = in.readTTFString(4); |
| |
| if ("ttcf".equals(tag)) { |
| // This is a TrueType Collection |
| in.skip(4); |
| |
| // Read directory offsets |
| int numDirectories = (int)in.readTTFULong(); |
| long[] dirOffsets = new long[numDirectories]; |
| for (int i = 0; i < numDirectories; i++) { |
| dirOffsets[i] = in.readTTFULong(); |
| } |
| |
| if (log.isDebugEnabled()) { |
| log.debug("This is a TrueType collection file with " |
| + numDirectories + " fonts"); |
| log.debug("Containing the following fonts: "); |
| } |
| |
| for (int i = 0; (i < numDirectories); i++) { |
| in.seekSet(dirOffsets[i]); |
| readDirTabs(in); |
| |
| readName(in); |
| |
| log.debug(fullName); |
| fontNames.add(fullName); |
| |
| // Reset names |
| notice = ""; |
| fullName = ""; |
| familyNames.clear(); |
| postScriptName = ""; |
| subFamilyName = ""; |
| } |
| |
| in.seekSet(0); |
| return fontNames; |
| } else { |
| log.error("Not a TTC!"); |
| return null; |
| } |
| } |
| |
| /* |
| * Helper classes, they are not very efficient, but that really |
| * doesn't matter... |
| */ |
| private Integer[] unicodeToWinAnsi(int unicode) { |
| List ret = new java.util.ArrayList(); |
| for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { |
| if (unicode == Glyphs.WINANSI_ENCODING[i]) { |
| ret.add(new Integer(i)); |
| } |
| } |
| return (Integer[])ret.toArray(new Integer[0]); |
| } |
| |
| /** |
| * Dumps a few informational values to System.out. |
| */ |
| public void printStuff() { |
| System.out.println("Font name: " + postScriptName); |
| System.out.println("Full name: " + fullName); |
| System.out.println("Family name: " + familyNames); |
| System.out.println("Subfamily name: " + subFamilyName); |
| System.out.println("Notice: " + notice); |
| System.out.println("xHeight: " + (int)convertTTFUnit2PDFUnit(xHeight)); |
| System.out.println("capheight: " + (int)convertTTFUnit2PDFUnit(capHeight)); |
| |
| int italic = (int)(italicAngle >> 16); |
| System.out.println("Italic: " + italic); |
| System.out.print("ItalicAngle: " + (short)(italicAngle / 0x10000)); |
| if ((italicAngle % 0x10000) > 0) { |
| System.out.print("." |
| + (short)((italicAngle % 0x10000) * 1000) |
| / 0x10000); |
| } |
| System.out.println(); |
| System.out.println("Ascender: " + convertTTFUnit2PDFUnit(ascender)); |
| System.out.println("Descender: " + convertTTFUnit2PDFUnit(descender)); |
| System.out.println("FontBBox: [" + (int)convertTTFUnit2PDFUnit(fontBBox1) |
| + " " + (int)convertTTFUnit2PDFUnit(fontBBox2) + " " |
| + (int)convertTTFUnit2PDFUnit(fontBBox3) + " " |
| + (int)convertTTFUnit2PDFUnit(fontBBox4) + "]"); |
| } |
| |
| /** |
| * Map a glyph index to the corresponding unicode code point |
| * |
| * @param glyphIndex |
| * @return unicode code point |
| * @throws IOException if glyphIndex not found |
| */ |
| private Integer glyphToUnicode(int glyphIndex) throws IOException { |
| return (Integer) glyphToUnicodeMap.get(new Integer(glyphIndex)); |
| } |
| |
| /** |
| * Map a unicode code point to the corresponding glyph index |
| * |
| * @param unicodeIndex unicode code point |
| * @return glyph index |
| * @throws IOException if unicodeIndex not found |
| */ |
| private Integer unicodeToGlyph(int unicodeIndex) throws IOException { |
| final Integer result |
| = (Integer) unicodeToGlyphMap.get(new Integer(unicodeIndex)); |
| if (result == null) { |
| throw new IOException( |
| "Glyph index not found for unicode value " + unicodeIndex); |
| } |
| return result; |
| } |
| |
| /** |
| * Static main method to get info about a TrueType font. |
| * @param args The command line arguments |
| */ |
| public static void main(String[] args) { |
| try { |
| TTFFile ttfFile = new TTFFile(); |
| |
| FontFileReader reader = new FontFileReader(args[0]); |
| |
| String name = null; |
| if (args.length >= 2) { |
| name = args[1]; |
| } |
| |
| ttfFile.readFont(reader, name); |
| ttfFile.printStuff(); |
| |
| } catch (IOException ioe) { |
| System.err.println("Problem reading font: " + ioe.toString()); |
| ioe.printStackTrace(System.err); |
| } |
| } |
| } |