/*
 * 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.awt.Rectangle;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.xmlgraphics.fonts.Glyphs;

import org.apache.fop.complexscripts.fonts.AdvancedTypographicTableFormatException;
import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
import org.apache.fop.complexscripts.fonts.OTFAdvancedTypographicTableReader;
import org.apache.fop.fonts.CMapSegment;
import org.apache.fop.fonts.FontUtil;
import org.apache.fop.fonts.MultiByteFont;

public abstract class OpenFont {

    static final byte NTABS = 24;
    static final int MAX_CHAR_CODE = 255;
    static final int ENC_BUF_SIZE = 1024;

    private static final String[] MAC_GLYPH_ORDERING = {
        /* 0x000 */
        ".notdef",          ".null",         "nonmarkingreturn", "space",
        "exclam",           "quotedbl",      "numbersign",       "dollar",
        "percent",          "ampersand",     "quotesingle",      "parenleft",
        "parenright",       "asterisk",      "plus",             "comma",
        /* 0x010 */
        "hyphen",           "period",        "slash",            "zero",
        "one",              "two",           "three",            "four",
        "five",             "six",           "seven",            "eight",
        "nine",             "colon",         "semicolon",        "less",
        /* 0x020 */
        "equal",            "greater",       "question",         "at",
        "A",                "B",             "C",                "D",
        "E",                "F",             "G",                "H",
        "I",                "J",             "K",                "L",
        /* 0x030 */
        "M",                "N",             "O",                "P",
        "Q",                "R",             "S",                "T",
        "U",                "V",             "W",                "X",
        "Y",                "Z",             "bracketleft",      "backslash",
        /* 0x040 */
        "bracketright",     "asciicircum",   "underscore",       "grave",
        "a",                "b",             "c",                "d",
        "e",                "f",             "g",                "h",
        "i",                "j",             "k",                "l",
        /* 0x050 */
        "m",                "n",             "o",                "p",
        "q",                "r",             "s",                "t",
        "u",                "v",             "w",                "x",
        "y",                "z",             "braceleft",        "bar",
        /* 0x060 */
        "braceright",       "asciitilde",    "Adieresis",        "Aring",
        "Ccedilla",         "Eacute",        "Ntilde",           "Odieresis",
        "Udieresis",        "aacute",        "agrave",           "acircumflex",
        "adieresis",        "atilde",        "aring",            "ccedilla",
        /* 0x070 */
        "eacute",           "egrave",        "ecircumflex",      "edieresis",
        "iacute",           "igrave",        "icircumflex",      "idieresis",
        "ntilde",           "oacute",        "ograve",           "ocircumflex",
        "odieresis",        "otilde",        "uacute",           "ugrave",
        /* 0x080 */
        "ucircumflex",      "udieresis",     "dagger",           "degree",
        "cent",             "sterling",      "section",          "bullet",
        "paragraph",        "germandbls",    "registered",       "copyright",
        "trademark",        "acute",         "dieresis",         "notequal",
        /* 0x090 */
        "AE",               "Oslash",        "infinity",         "plusminus",
        "lessequal",        "greaterequal",  "yen",              "mu",
        "partialdiff",      "summation",     "product",          "pi",
        "integral",         "ordfeminine",   "ordmasculine",     "Omega",
        /* 0x0A0 */
        "ae",               "oslash",        "questiondown",     "exclamdown",
        "logicalnot",       "radical",       "florin",           "approxequal",
        "Delta",            "guillemotleft", "guillemotright",   "ellipsis",
        "nonbreakingspace", "Agrave",        "Atilde",           "Otilde",
        /* 0x0B0 */
        "OE",               "oe",            "endash",           "emdash",
        "quotedblleft",     "quotedblright", "quoteleft",        "quoteright",
        "divide",           "lozenge",       "ydieresis",        "Ydieresis",
        "fraction",         "currency",      "guilsinglleft",    "guilsinglright",
        /* 0x0C0 */
        "fi",               "fl",            "daggerdbl",        "periodcentered",
        "quotesinglbase",   "quotedblbase",  "perthousand",      "Acircumflex",
        "Ecircumflex",      "Aacute",        "Edieresis",        "Egrave",
        "Iacute",           "Icircumflex",   "Idieresis",        "Igrave",
        /* 0x0D0 */
        "Oacute",           "Ocircumflex",   "apple",            "Ograve",
        "Uacute",           "Ucircumflex",   "Ugrave",           "dotlessi",
        "circumflex",       "tilde",         "macron",           "breve",
        "dotaccent",        "ring",          "cedilla",          "hungarumlaut",
        /* 0x0E0 */
        "ogonek",           "caron",         "Lslash",           "lslash",
        "Scaron",           "scaron",        "Zcaron",           "zcaron",
        "brokenbar",        "Eth",           "eth",              "Yacute",
        "yacute",           "Thorn",         "thorn",            "minus",
        /* 0x0F0 */
        "multiply",         "onesuperior",   "twosuperior",      "threesuperior",
        "onehalf",          "onequarter",    "threequarters",    "franc",
        "Gbreve",           "gbreve",        "Idotaccent",       "Scedilla",
        "scedilla",         "Cacute",        "cacute",           "Ccaron",
        /* 0x100 */
        "ccaron",           "dcroat"
    };

    
The FontFileReader used to read this TrueType font.
/** The FontFileReader used to read this TrueType font. */
protected FontFileReader fontFile;
Set to true to get even more debug output than with level DEBUG
/** Set to true to get even more debug output than with level DEBUG */
public static final boolean TRACE_ENABLED = false; private static final String ENCODING = "WinAnsiEncoding"; // Default encoding private static final short FIRST_CHAR = 0; protected boolean useKerning; private boolean isEmbeddable = true; private boolean hasSerifs = true;
Table directory
/** * Table directory */
protected Map<OFTableName, OFDirTabEntry> dirTabs; private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding private List<CMapSegment> cmaps; protected List<UnicodeMapping> unicodeMappings; private int upem; // unitsPerEm from "head" table protected int nhmtx; // Number of horizontal metrics private PostScriptVersion postScriptVersion; protected int locaFormat;
Offset to last loca
/** * Offset to last loca */
protected long lastLoca; protected int numberOfGlyphs; // Number of glyphs in font (read from "maxp" table)
Contains glyph data
/** * Contains glyph data */
protected OFMtxEntry[] mtxTab; // Contains glyph data protected String postScriptName = ""; protected String fullName = ""; protected String embedFontName = ""; protected String notice = ""; protected final Set<String> familyNames = new HashSet<String>(); protected String subFamilyName = ""; protected boolean cid = true; private long italicAngle; private long isFixedPitch; private int fontBBox1; private int fontBBox2; private int fontBBox3; private int fontBBox4; private int capHeight; private int os2CapHeight; private int underlinePosition; private int underlineThickness; private int strikeoutPosition; private int strikeoutThickness; private int xHeight; private int os2xHeight; //Effective ascender/descender private int ascender; private int descender; //Ascender/descender from hhea table private int hheaAscender; private int hheaDescender; //Ascender/descender from OS/2 table private int os2Ascender; private int os2Descender; private int usWeightClass; private short lastChar; private int[] ansiWidth; private Map<Integer, List<Integer>> ansiIndex; // internal mapping of glyph indexes to unicode indexes // used for quick mappings in this class private final Map<Integer, Integer> glyphToUnicodeMap = new HashMap<Integer, Integer>(); private final Map<Integer, Integer> unicodeToGlyphMap = new HashMap<Integer, Integer>(); private boolean isCFF; // advanced typographic table support protected boolean useAdvanced; protected OTFAdvancedTypographicTableReader advancedTableReader;
Version of the PostScript table (post) contained in this font.
/** * Version of the PostScript table (post) contained in this font. */
public static enum PostScriptVersion {
PostScript table version 1.0.
/** PostScript table version 1.0. */
V1,
PostScript table version 2.0.
/** PostScript table version 2.0. */
V2,
PostScript table version 3.0.
/** PostScript table version 3.0. */
V3,
Unknown version of the PostScript table.
/** Unknown version of the PostScript table. */
UNKNOWN; }
logging instance
/** * logging instance */
protected Log log = LogFactory.getLog(TTFFile.class); public OpenFont() { this(true, false); }
Constructor
Params:
  • useKerning – true if kerning data should be loaded
  • useAdvanced – true if advanced typographic tables should be loaded
/** * Constructor * @param useKerning true if kerning data should be loaded * @param useAdvanced true if advanced typographic tables should be loaded */
public OpenFont(boolean useKerning, boolean useAdvanced) { this.useKerning = useKerning; this.useAdvanced = useAdvanced; }
Key-value helper class.
/** * Key-value helper class. */
static final class UnicodeMapping implements Comparable { private final int unicodeIndex; private final int glyphIndex; UnicodeMapping(OpenFont font, int glyphIndex, int unicodeIndex) { this.unicodeIndex = unicodeIndex; this.glyphIndex = glyphIndex; font.glyphToUnicodeMap.put(glyphIndex, unicodeIndex); font.unicodeToGlyphMap.put(unicodeIndex, glyphIndex); }
Returns the glyphIndex.
Returns:the glyph index
/** * Returns the glyphIndex. * @return the glyph index */
public int getGlyphIndex() { return glyphIndex; }
Returns the unicodeIndex.
Returns:the Unicode index
/** * Returns the unicodeIndex. * @return the Unicode index */
public int getUnicodeIndex() { return unicodeIndex; }
{@inheritDoc}
/** {@inheritDoc} */
public int hashCode() { int hc = unicodeIndex; hc = 19 * hc + (hc ^ glyphIndex); return hc; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean equals(Object o) { if (o instanceof UnicodeMapping) { UnicodeMapping m = (UnicodeMapping) o; if (unicodeIndex != m.unicodeIndex) { return false; } else { return (glyphIndex == m.glyphIndex); } } else { return false; } }
{@inheritDoc}
/** {@inheritDoc} */
public int compareTo(Object o) { if (o instanceof UnicodeMapping) { UnicodeMapping m = (UnicodeMapping) o; if (unicodeIndex > m.unicodeIndex) { return 1; } else if (unicodeIndex < m.unicodeIndex) { return -1; } else { return 0; } } else { return -1; } } }
Obtain directory table entry.
Params:
  • name – (tag) of entry
Returns:a directory table entry or null if none found
/** * Obtain directory table entry. * @param name (tag) of entry * @return a directory table entry or null if none found */
public OFDirTabEntry getDirectoryEntry(OFTableName name) { return dirTabs.get(name); }
Position inputstream to position indicated in the dirtab offset + offset
Params:
  • in – font file reader
  • tableName – (tag) of table
  • offset – from start of table
Throws:
Returns:true if seek succeeded
/** * Position inputstream to position indicated * in the dirtab offset + offset * @param in font file reader * @param tableName (tag) of table * @param offset from start of table * @return true if seek succeeded * @throws IOException if I/O exception occurs during seek */
public boolean seekTab(FontFileReader in, OFTableName tableName, long offset) throws IOException { OFDirTabEntry dt = dirTabs.get(tableName); if (dt == null) { log.info("Dirtab " + tableName.getName() + " not found."); return false; } else { in.seekSet(dt.getOffset() + offset); } return true; }
Convert from truetype unit to pdf unit based on the unitsPerEm field in the "head" table
Params:
  • n – truetype unit
Returns:pdf unit
/** * 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.
See Also:
/** * 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. * * @see <a href="https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html"> * TrueType-Reference-Manual * </a> */
protected boolean readCMAP() throws IOException { unicodeMappings = new ArrayList<OpenFont.UnicodeMapping>(); if (!seekTab(fontFile, OFTableName.CMAP, 2)) { return true; } int numCMap = fontFile.readTTFUShort(); // Number of cmap subtables long cmapUniOffset = 0; long symbolMapOffset = 0; long surrogateMapOffset = 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 = fontFile.readTTFUShort(); int cmapEID = fontFile.readTTFUShort(); long cmapOffset = fontFile.readTTFLong(); if (log.isDebugEnabled()) { log.debug("Platform ID: " + cmapPID + " Encoding: " + cmapEID); } if (cmapPID == 3 && cmapEID == 1) { cmapUniOffset = cmapOffset; } if (cmapPID == 3 && cmapEID == 0) { symbolMapOffset = cmapOffset; } if (cmapPID == 3 && cmapEID == 10) { surrogateMapOffset = cmapOffset; } } if (surrogateMapOffset > 0) { // TODO maybe for SingleByte fonts instances we should not reach this branch return readUnicodeCmap(surrogateMapOffset, 10); } else if (cmapUniOffset > 0) { return readUnicodeCmap(cmapUniOffset, 1); } else if (symbolMapOffset > 0) { return readUnicodeCmap(symbolMapOffset, 0); } else { log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" + " not present. Aborting"); return false; } } private boolean readUnicodeCmap(long cmapUniOffset, int encodingID) throws IOException { //Read CMAP table and correct mtxTab.index int mtxPtr = 0; // Read unicode cmap seekTab(fontFile, OFTableName.CMAP, cmapUniOffset); int cmapFormat = fontFile.readTTFUShort(); if (cmapFormat < 8) { fontFile.readTTFUShort(); //skip cmap length fontFile.readTTFUShort(); //skip cmap version } else { fontFile.readTTFUShort(); //skip 2 bytes to read a Fixed32 fontFile.readTTFULong(); //skip cmap length fontFile.readTTFULong(); //skip cmap version } if (log.isDebugEnabled()) { log.debug("CMAP format: " + cmapFormat); } if (cmapFormat == 4) { int cmapSegCountX2 = fontFile.readTTFUShort(); int cmapSearchRange = fontFile.readTTFUShort(); int cmapEntrySelector = fontFile.readTTFUShort(); int cmapRangeShift = fontFile.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] = fontFile.readTTFUShort(); } fontFile.skip(2); // Skip reservedPad for (int i = 0; i < (cmapSegCountX2 / 2); i++) { cmapStartCounts[i] = fontFile.readTTFUShort(); } for (int i = 0; i < (cmapSegCountX2 / 2); i++) { cmapDeltas[i] = fontFile.readTTFShort(); } //int startRangeOffset = in.getCurrentPos(); for (int i = 0; i < (cmapSegCountX2 / 2); i++) { cmapRangeOffsets[i] = fontFile.readTTFUShort(); } int glyphIdArrayOffset = fontFile.getCurrentPos(); BitSet eightBitGlyphs = new BitSet(256); // 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]); } if (log.isDebugEnabled()) { if (isInPrivateUseArea(cmapStartCounts[i], cmapEndCounts[i])) { log.debug("Font contains glyphs in the Unicode private use area: " + Integer.toHexString(cmapStartCounts[i]) + " - " + Integer.toHexString(cmapEndCounts[i])); } } for (int j = cmapStartCounts[i]; j <= cmapEndCounts[i]; j++) { // Update lastChar if (j < 256 && j > lastChar) { lastChar = (short)j; } if (j < 256) { eightBitGlyphs.set(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; fontFile.seekSet(glyphOffset); glyphIdx = (fontFile.readTTFUShort() + cmapDeltas[i]) & 0xffff; //mtxTab[glyphIdx].setName(mtxTab[glyphIdx].getName() + " - "+(char)j); unicodeMappings.add(new UnicodeMapping(this, glyphIdx, j)); mtxTab[glyphIdx].getUnicodeIndex().add(j); if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) { //Experimental: Mapping 0xF020-0xF0FF to 0x0020-0x00FF //Tested with Wingdings and Symbol TTF fonts which map their //glyphs in the region 0xF020-0xF0FF. int mapped = j - 0xF000; if (!eightBitGlyphs.get(mapped)) { //Only map if Unicode code point hasn't been mapped before unicodeMappings.add(new UnicodeMapping(this, glyphIdx, mapped)); mtxTab[glyphIdx].getUnicodeIndex().add(mapped); } } // Also add winAnsiWidth List<Integer> v = ansiIndex.get(j); if (v != null) { for (Integer aIdx : v) { ansiWidth[aIdx] = mtxTab[glyphIdx].getWx(); if (log.isTraceEnabled()) { log.trace("Added width " + mtxTab[glyphIdx].getWx() + " uni: " + j + " ansi: " + aIdx); } } } 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(j); } else { log.debug("Glyph " + glyphIdx + " out of range: " + mtxTab.length); } unicodeMappings.add(new UnicodeMapping(this, glyphIdx, j)); if (glyphIdx < mtxTab.length) { mtxTab[glyphIdx].getUnicodeIndex().add(j); } else { log.debug("Glyph " + glyphIdx + " out of range: " + mtxTab.length); } // Also add winAnsiWidth List<Integer> v = ansiIndex.get(j); if (v != null) { for (Integer aIdx : v) { ansiWidth[aIdx] = 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++; } } } } } } else if (cmapFormat == 12) { long nGroups = fontFile.readTTFULong(); for (long i = 0; i < nGroups; ++i) { long startCharCode = fontFile.readTTFULong(); long endCharCode = fontFile.readTTFULong(); long startGlyphCode = fontFile.readTTFULong(); if (startCharCode < 0 || startCharCode > 0x10FFFFL) { log.warn("startCharCode outside Unicode range"); continue; } if (startCharCode >= 0xD800 && startCharCode <= 0xDFFF) { log.warn("startCharCode is a surrogate pair: " + startCharCode); } //endCharCode outside unicode range or is surrogate pair. if (endCharCode > 0 && endCharCode < startCharCode || endCharCode > 0x10FFFFL) { log.warn("startCharCode outside Unicode range"); continue; } if (endCharCode >= 0xD800 && endCharCode <= 0xDFFF) { log.warn("endCharCode is a surrogate pair: " + startCharCode); } for (long offset = 0; offset <= endCharCode - startCharCode; ++offset) { long glyphIndexL = startGlyphCode + offset; long charCodeL = startCharCode + offset; if (glyphIndexL >= numberOfGlyphs) { log.warn("Format 12 cmap contains an invalid glyph index"); break; } if (charCodeL > 0x10FFFFL) { log.warn("Format 12 cmap contains character beyond UCS-4"); } if (glyphIndexL > Integer.MAX_VALUE) { log.error("glyphIndex > Integer.MAX_VALUE"); continue; } if (charCodeL > Integer.MAX_VALUE) { log.error("startCharCode + j > Integer.MAX_VALUE"); continue; } // Update lastChar if (charCodeL < 0xFF && charCodeL > lastChar) { lastChar = (short) charCodeL; } int charCode = (int) charCodeL; int glyphIndex = (int) glyphIndexL; // Also add winAnsiWidth. List<Integer> ansiIndexes = null; if (charCodeL <= Character.MAX_VALUE) { ansiIndexes = ansiIndex.get((int) charCodeL); } unicodeMappings.add(new UnicodeMapping(this, glyphIndex, charCode)); mtxTab[glyphIndex].getUnicodeIndex().add(charCode); if (ansiIndexes == null) { continue; } for (Integer aIdx : ansiIndexes) { ansiWidth[aIdx] = mtxTab[glyphIndex].getWx(); if (log.isTraceEnabled()) { log.trace("Added width " + mtxTab[glyphIndex].getWx() + " uni: " + offset + " ansi: " + aIdx); } } } } } else { log.error("Cmap format not supported: " + cmapFormat); return false; } return true; } private boolean isInPrivateUseArea(int start, int end) { return (isInPrivateUseArea(start) || isInPrivateUseArea(end)); } private boolean isInPrivateUseArea(int unicode) { return (unicode >= 0xE000 && unicode <= 0xF8FF); }
Returns:mmtx data
/** * * @return mmtx data */
public List<OFMtxEntry> getMtx() { return Collections.unmodifiableList(Arrays.asList(mtxTab)); } /** * Print first char/last char */ /* not used 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.
Params:
  • in – The FontFileReader to use
Throws:
/** * 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, String header) throws IOException { readFont(in, header, (String)null); }
initialize the ansiWidths array (for winAnsiEncoding) and fill with the missingwidth
/** * initialize the ansiWidths array (for winAnsiEncoding) * and fill with the missingwidth */
protected 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 HashMap<Integer, List<Integer>>(); for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { Integer ansi = i; Integer uni = (int) Glyphs.WINANSI_ENCODING[i]; List<Integer> v = ansiIndex.get(uni); if (v == null) { v = new ArrayList<Integer>(); 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.
Params:
  • in – The FontFileReader to use
  • name – The name of the font
Throws:
Returns:boolean Returns true if the font is valid
/** * 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 header, String name) throws IOException { initializeFont(in); /* * Check if TrueType collection, and that the name * exists in the collection */ if (!checkTTC(header, 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(); readFontHeader(); getNumGlyphs(); if (log.isDebugEnabled()) { log.debug("Number of glyphs in font: " + numberOfGlyphs); } readHorizontalHeader(); readHorizontalMetrics(); initAnsiWidths(); readPostScript(); readOS2(); determineAscDesc(); readName(); boolean pcltFound = readPCLT(); // Read cmap table and fill in ansiwidths boolean valid = readCMAP(); if (!valid) { return false; } // Create cmaps for bfentries createCMaps(); updateBBoxAndOffset(); if (useKerning) { readKerning(); } handleCharacterSpacing(in); guessVerticalMetricsFromGlyphBBox(); return true; }
Reads a font.
Params:
  • in – FontFileReader to read from
Throws:
/** * Reads a font. * * @param in FontFileReader to read from * @throws IOException in case of an I/O problem */
public void readFont(FontFileReader in, String header, MultiByteFont mbfont) throws IOException { readFont(in, header, mbfont.getTTCName()); } protected abstract void updateBBoxAndOffset() throws IOException; protected abstract void readName() throws IOException; protected abstract void initializeFont(FontFileReader in) throws IOException; protected void handleCharacterSpacing(FontFileReader in) throws IOException { // Read advanced typographic tables. if (useAdvanced) { try { OTFAdvancedTypographicTableReader atr = new OTFAdvancedTypographicTableReader(this, in); atr.readAll(); this.advancedTableReader = atr; } catch (AdvancedTypographicTableFormatException e) { log.warn( "Encountered format constraint violation in advanced (typographic) table (AT) " + "in font '" + getFullName() + "', ignoring AT data: " + e.getMessage() ); } } } protected void createCMaps() { cmaps = new ArrayList<CMapSegment>(); int unicodeStart; int glyphStart; int unicodeEnd; if (unicodeMappings.isEmpty()) { return; } Iterator<UnicodeMapping> e = unicodeMappings.iterator(); UnicodeMapping um = e.next(); UnicodeMapping lastMapping = um; unicodeStart = um.getUnicodeIndex(); glyphStart = um.getGlyphIndex(); while (e.hasNext()) { um = e.next(); if (((lastMapping.getUnicodeIndex() + 1) != um.getUnicodeIndex()) || ((lastMapping.getGlyphIndex() + 1) != um.getGlyphIndex())) { unicodeEnd = lastMapping.getUnicodeIndex(); cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); unicodeStart = um.getUnicodeIndex(); glyphStart = um.getGlyphIndex(); } lastMapping = um; } unicodeEnd = lastMapping.getUnicodeIndex(); cmaps.add(new CMapSegment(unicodeStart, unicodeEnd, glyphStart)); }
Returns the PostScript name of the font.
Returns:String The PostScript name
/** * 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; } } PostScriptVersion getPostScriptVersion() { return postScriptVersion; }
Returns the font family names of the font.
Returns:Set The family names (a Set of Strings)
/** * Returns the font family names of the font. * @return Set The family names (a Set of Strings) */
public Set<String> getFamilyNames() { return familyNames; }
Returns the font sub family name of the font.
Returns:String The sub family name
/** * 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.
Returns:String The full name
/** * 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.
Returns:String The caracter set
/** * 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.
Returns:int The CapHeight
/** * Returns the CapHeight attribute of the font. * @return int The CapHeight */
public int getCapHeight() { return convertTTFUnit2PDFUnit(capHeight); }
Returns the XHeight attribute of the font.
Returns:int The XHeight
/** * Returns the XHeight attribute of the font. * @return int The XHeight */
public int getXHeight() { return convertTTFUnit2PDFUnit(xHeight); }
Returns the number of bytes necessary to pad the currentPosition so that a table begins on a 4-byte boundary.
Params:
  • currentPosition – the position to pad.
Returns:int the number of bytes to pad.
/** * Returns the number of bytes necessary to pad the currentPosition so that a table begins * on a 4-byte boundary. * @param currentPosition the position to pad. * @return int the number of bytes to pad. */
protected int getPadSize(int currentPosition) { int padSize = 4 - (currentPosition % 4); return padSize < 4 ? padSize : 0; }
Returns the Flags attribute of the font.
Returns:int The Flags
/** * 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 |= 64; } if (isFixedPitch != 0) { flags |= 2; } if (hasSerifs) { flags |= 1; } return flags; }
Returns the weight class of this font. Valid values are 100, 200....,800, 900.
Returns:the weight class value (or 0 if there was no OS/2 table in the font)
/** * 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.
Returns:String The StemV
/** * Returns the StemV attribute of the font. * @return String The StemV */
public String getStemV() { return "0"; }
Returns the ItalicAngle attribute of the font.
Returns:String The ItalicAngle
/** * 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:int[] The font bbox
/** * @return int[] The font bbox */
public int[] getFontBBox() { final int[] fbb = new int[4]; fbb[0] = convertTTFUnit2PDFUnit(fontBBox1); fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); return fbb; }
Returns the original bounding box values from the HEAD table
Returns:An array of bounding box values
/** * Returns the original bounding box values from the HEAD table * @return An array of bounding box values */
public int[] getBBoxRaw() { int[] bbox = {fontBBox1, fontBBox2, fontBBox3, fontBBox4}; return bbox; }
Returns the LowerCaseAscent attribute of the font.
Returns:int The LowerCaseAscent
/** * Returns the LowerCaseAscent attribute of the font. * @return int The LowerCaseAscent */
public int getLowerCaseAscent() { return convertTTFUnit2PDFUnit(ascender); }
Returns the LowerCaseDescent attribute of the font.
Returns:int The LowerCaseDescent
/** * Returns the LowerCaseDescent attribute of the font. * @return int The LowerCaseDescent */
public int getLowerCaseDescent() { return convertTTFUnit2PDFUnit(descender); }
Returns the index of the last character, but this is for WinAnsiEncoding only, so the last char is < 256.
Returns:short Index of the last character (<256)
/** * Returns the index of the last character, but this is for WinAnsiEncoding * only, so the last char is &lt; 256. * @return short Index of the last character (&lt;256) */
public short getLastChar() { return lastChar; }
Returns the index of the first character.
Returns:short Index of the first character
/** * Returns the index of the first character. * @return short Index of the first character */
public short getFirstChar() { return FIRST_CHAR; }
Returns an array of character widths.
Returns:int[] The character widths
/** * 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] = convertTTFUnit2PDFUnit(mtxTab[i].getWx()); } return wx; } public Rectangle[] getBoundingBoxes() { Rectangle[] boundingBoxes = new Rectangle[mtxTab.length]; for (int i = 0; i < boundingBoxes.length; i++) { int[] boundingBox = mtxTab[i].getBoundingBox(); boundingBoxes[i] = new Rectangle( convertTTFUnit2PDFUnit(boundingBox[0]), convertTTFUnit2PDFUnit(boundingBox[1]), convertTTFUnit2PDFUnit(boundingBox[2] - boundingBox[0]), convertTTFUnit2PDFUnit(boundingBox[3] - boundingBox[1])); } return boundingBoxes; }
Returns an array (xMin, yMin, xMax, yMax) for a glyph.
Params:
  • glyphIndex – the index of the glyph
Returns:int[] Array defining bounding box.
/** * Returns an array (xMin, yMin, xMax, yMax) for a glyph. * * @param glyphIndex the index of the glyph * @return int[] Array defining bounding box. */
public int[] getBBox(int glyphIndex) { int[] bbox = new int[4]; if (glyphIndex < mtxTab.length) { int[] bboxInTTFUnits = mtxTab[glyphIndex].getBoundingBox(); for (int i = 0; i < 4; i++) { bbox[i] = convertTTFUnit2PDFUnit(bboxInTTFUnits[i]); } } return bbox; }
Returns the width of a given character.
Params:
  • idx – Index of the character
Returns:int Standard width
/** * Returns the width of a given character. * @param idx Index of the character * @return int Standard width */
public int getCharWidth(int idx) { return convertTTFUnit2PDFUnit(ansiWidth[idx]); }
Returns the width of a given character in raw units
Params:
  • idx – Index of the character
Returns:int Width in it's raw form stored in the font
/** * Returns the width of a given character in raw units * @param idx Index of the character * @return int Width in it's raw form stored in the font */
public int getCharWidthRaw(int idx) { if (ansiWidth != null) { return ansiWidth[idx]; } return -1; }
Returns the kerning table.
Returns:Map The kerning table
/** * Returns the kerning table. * @return Map The kerning table */
public Map<Integer, Map<Integer, Integer>> getKerning() { return kerningTab; }
Returns the ANSI kerning table.
Returns:Map The ANSI kerning table
/** * Returns the ANSI kerning table. * @return Map The ANSI kerning table */
public Map<Integer, Map<Integer, Integer>> getAnsiKerning() { return ansiKerningTab; } public int getUnderlinePosition() { return convertTTFUnit2PDFUnit(underlinePosition); } public int getUnderlineThickness() { return convertTTFUnit2PDFUnit(underlineThickness); } public int getStrikeoutPosition() { return convertTTFUnit2PDFUnit(strikeoutPosition); } public int getStrikeoutThickness() { return convertTTFUnit2PDFUnit(strikeoutThickness); }
Indicates if the font may be embedded.
Returns:boolean True if it may be embedded
/** * 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).
Returns:true if the font is in OpenType CFF format.
/** * 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.
Throws:
  • IOException – in case of an I/O problem
/** * 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. * @throws IOException in case of an I/O problem */
protected void readDirTabs() throws IOException { int sfntVersion = fontFile.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 = fontFile.readTTFUShort(); fontFile.skip(6); // 3xTTF_USHORT_SIZE dirTabs = new HashMap<OFTableName, OFDirTabEntry>(); OFDirTabEntry[] pd = new OFDirTabEntry[ntabs]; log.debug("Reading " + ntabs + " dir tables"); for (int i = 0; i < ntabs; i++) { pd[i] = new OFDirTabEntry(); String tableName = pd[i].read(fontFile); dirTabs.put(OFTableName.getValue(tableName), pd[i]); } dirTabs.put(OFTableName.TABLE_DIRECTORY, new OFDirTabEntry(0L, fontFile.getCurrentPos())); log.debug("dir tables: " + dirTabs.keySet()); }
Read the "head" table, this reads the bounding box and sets the upem (unitsPerEM) variable
Throws:
  • IOException – in case of an I/O problem
/** * Read the "head" table, this reads the bounding box and * sets the upem (unitsPerEM) variable * @throws IOException in case of an I/O problem */
protected void readFontHeader() throws IOException { seekTab(fontFile, OFTableName.HEAD, 2 * 4 + 2 * 4); int flags = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); } upem = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("unit per em: " + upem); } fontFile.skip(16); fontBBox1 = fontFile.readTTFShort(); fontBBox2 = fontFile.readTTFShort(); fontBBox3 = fontFile.readTTFShort(); fontBBox4 = fontFile.readTTFShort(); if (log.isDebugEnabled()) { log.debug("font bbox: xMin=" + fontBBox1 + " yMin=" + fontBBox2 + " xMax=" + fontBBox3 + " yMax=" + fontBBox4); } fontFile.skip(2 + 2 + 2); locaFormat = fontFile.readTTFShort(); }
Read the number of glyphs from the "maxp" table
Throws:
  • IOException – in case of an I/O problem
/** * Read the number of glyphs from the "maxp" table * @throws IOException in case of an I/O problem */
protected void getNumGlyphs() throws IOException { seekTab(fontFile, OFTableName.MAXP, 4); numberOfGlyphs = fontFile.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.
Throws:
  • IOException – in case of an I/O problem
/** * 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. * @throws IOException in case of an I/O problem */
protected void readHorizontalHeader() throws IOException { seekTab(fontFile, OFTableName.HHEA, 4); hheaAscender = fontFile.readTTFShort(); hheaDescender = fontFile.readTTFShort(); fontFile.skip(2 + 2 + 3 * 2 + 8 * 2); nhmtx = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("hhea.Ascender: " + formatUnitsForDebug(hheaAscender)); log.debug("hhea.Descender: " + formatUnitsForDebug(hheaDescender)); 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
Throws:
  • IOException – in case of an I/O problem
/** * 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 * @throws IOException in case of an I/O problem */
protected void readHorizontalMetrics() throws IOException { seekTab(fontFile, OFTableName.HMTX, 0); int mtxSize = Math.max(numberOfGlyphs, nhmtx); mtxTab = new OFMtxEntry[mtxSize]; if (log.isTraceEnabled()) { log.trace("*** Widths array: \n"); } for (int i = 0; i < mtxSize; i++) { mtxTab[i] = new OFMtxEntry(); } for (int i = 0; i < nhmtx; i++) { mtxTab[i].setWx(fontFile.readTTFUShort()); mtxTab[i].setLsb(fontFile.readTTFUShort()); if (log.isTraceEnabled()) { log.trace(" width[" + i + "] = " + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";"); } } if (cid && 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(fontFile.readTTFUShort()); } } }
Read the "post" table containing the PostScript names of the glyphs.
/** * Read the "post" table * containing the PostScript names of the glyphs. */
protected void readPostScript() throws IOException { seekTab(fontFile, OFTableName.POST, 0); int postFormat = fontFile.readTTFLong(); italicAngle = fontFile.readTTFULong(); underlinePosition = fontFile.readTTFShort(); underlineThickness = fontFile.readTTFShort(); isFixedPitch = fontFile.readTTFULong(); //Skip memory usage values fontFile.skip(4 * 4); log.debug("PostScript format: 0x" + Integer.toHexString(postFormat)); switch (postFormat) { case 0x00010000: log.debug("PostScript format 1"); postScriptVersion = PostScriptVersion.V1; for (int i = 0; i < MAC_GLYPH_ORDERING.length; i++) { mtxTab[i].setName(MAC_GLYPH_ORDERING[i]); } break; case 0x00020000: log.debug("PostScript format 2"); postScriptVersion = PostScriptVersion.V2; int numGlyphStrings = 257; // Read Number of Glyphs int l = fontFile.readTTFUShort(); // Read indexes for (int i = 0; i < l; i++) { mtxTab[i].setIndex(fontFile.readTTFUShort()); if (mtxTab[i].getIndex() > numGlyphStrings) { numGlyphStrings = mtxTab[i].getIndex(); } if (log.isTraceEnabled()) { log.trace("PostScript index: " + mtxTab[i].getIndexAsString()); } } // firstChar=minIndex; String[] psGlyphsBuffer = new String[numGlyphStrings - 257]; 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] = fontFile.readTTFString(fontFile.readTTFUByte()); } //Set glyph names for (int i = 0; i < l; i++) { if (mtxTab[i].getIndex() < MAC_GLYPH_ORDERING.length) { mtxTab[i].setName(MAC_GLYPH_ORDERING[mtxTab[i].getIndex()]); } else { if (!mtxTab[i].isIndexReserved()) { int k = mtxTab[i].getIndex() - MAC_GLYPH_ORDERING.length; 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"); postScriptVersion = PostScriptVersion.V3; break; default: log.error("Unknown PostScript format: " + postFormat); postScriptVersion = PostScriptVersion.UNKNOWN; } }
Read the "OS/2" table
/** * Read the "OS/2" table */
protected void readOS2() throws IOException { // Check if font is embeddable OFDirTabEntry os2Entry = dirTabs.get(OFTableName.OS2); if (os2Entry != null) { seekTab(fontFile, OFTableName.OS2, 0); int version = fontFile.readTTFUShort(); if (log.isDebugEnabled()) { log.debug("OS/2 table: version=" + version + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); } fontFile.skip(2); //xAvgCharWidth this.usWeightClass = fontFile.readTTFUShort(); // usWidthClass fontFile.skip(2); int fsType = fontFile.readTTFUShort(); if (fsType == 2) { isEmbeddable = false; } else { isEmbeddable = true; } fontFile.skip(8 * 2); strikeoutThickness = fontFile.readTTFShort(); strikeoutPosition = fontFile.readTTFShort(); fontFile.skip(2); fontFile.skip(10); //panose array fontFile.skip(4 * 4); //unicode ranges fontFile.skip(4); fontFile.skip(3 * 2); int v; os2Ascender = fontFile.readTTFShort(); //sTypoAscender os2Descender = fontFile.readTTFShort(); //sTypoDescender if (log.isDebugEnabled()) { log.debug("sTypoAscender: " + os2Ascender + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); log.debug("sTypoDescender: " + os2Descender + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); } v = fontFile.readTTFShort(); //sTypoLineGap if (log.isDebugEnabled()) { log.debug("sTypoLineGap: " + v); } v = fontFile.readTTFUShort(); //usWinAscent if (log.isDebugEnabled()) { log.debug("usWinAscent: " + formatUnitsForDebug(v)); } v = fontFile.readTTFUShort(); //usWinDescent if (log.isDebugEnabled()) { log.debug("usWinDescent: " + formatUnitsForDebug(v)); } //version 1 OS/2 table might end here if (os2Entry.getLength() >= 78 + (2 * 4) + (2 * 2)) { fontFile.skip(2 * 4); this.os2xHeight = fontFile.readTTFShort(); //sxHeight this.os2CapHeight = fontFile.readTTFShort(); //sCapHeight if (log.isDebugEnabled()) { log.debug("sxHeight: " + this.os2xHeight); log.debug("sCapHeight: " + this.os2CapHeight); } } } else { isEmbeddable = true; } }
Read the "PCLT" table to find xHeight and capHeight.
Throws:
  • IOException – In case of a I/O problem
/** * Read the "PCLT" table to find xHeight and capHeight. * @throws IOException In case of a I/O problem */
protected boolean readPCLT() throws IOException { OFDirTabEntry dirTab = dirTabs.get(OFTableName.PCLT); if (dirTab != null) { fontFile.seekSet(dirTab.getOffset() + 4 + 4 + 2); xHeight = fontFile.readTTFUShort(); log.debug("xHeight from PCLT: " + formatUnitsForDebug(xHeight)); fontFile.skip(2 * 2); capHeight = fontFile.readTTFUShort(); log.debug("capHeight from PCLT: " + formatUnitsForDebug(capHeight)); fontFile.skip(2 + 16 + 8 + 6 + 1 + 1); int serifStyle = fontFile.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.
/** * 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. */
protected 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; } } if (log.isDebugEnabled()) { log.debug("Font box height: " + (ascender - descender)); if (ascender - descender > upem) { log.debug("Ascender and descender together are larger than the em box."); } } } protected 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 in 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 (OFMtxEntry aMtxTab : mtxTab) { if ("H".equals(aMtxTab.getName())) { localCapHeight = aMtxTab.getBoundingBox()[3]; } else if ("x".equals(aMtxTab.getName())) { localXHeight = aMtxTab.getBoundingBox()[3]; } else if ("d".equals(aMtxTab.getName())) { localAscender = aMtxTab.getBoundingBox()[3]; } else if ("p".equals(aMtxTab.getName())) { localDescender = aMtxTab.getBoundingBox()[1]; } else { // OpenType Fonts with a version 3.0 "post" table don't have glyph names. // Use Unicode indices instead. List unicodeIndex = aMtxTab.getUnicodeIndex(); if (unicodeIndex.size() > 0) { //Only the first index is used char ch = (char) ((Integer) unicodeIndex.get(0)).intValue(); if (ch == 'H') { localCapHeight = aMtxTab.getBoundingBox()[3]; } else if (ch == 'x') { localXHeight = aMtxTab.getBoundingBox()[3]; } else if (ch == 'd') { localAscender = aMtxTab.getBoundingBox()[3]; } else if (ch == 'p') { localDescender = aMtxTab.getBoundingBox()[1]; } } } } if (log.isDebugEnabled()) { log.debug("Ascender from glyph 'd': " + formatUnitsForDebug(localAscender)); log.debug("Descender from glyph 'p': " + formatUnitsForDebug(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; } if (log.isDebugEnabled()) { log.debug("xHeight from glyph 'x': " + formatUnitsForDebug(localXHeight)); log.debug("CapHeight from glyph 'H': " + formatUnitsForDebug(localCapHeight)); } if (capHeight == 0) { capHeight = localCapHeight; if (capHeight == 0) { capHeight = os2CapHeight; } if (capHeight == 0) { log.debug("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.debug("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.
Throws:
  • IOException – In case of a I/O problem
/** * Read the kerning table, create a table for both CIDs and * winAnsiEncoding. * @throws IOException In case of a I/O problem */
protected void readKerning() throws IOException { // Read kerning kerningTab = new HashMap<Integer, Map<Integer, Integer>>(); ansiKerningTab = new HashMap<Integer, Map<Integer, Integer>>(); OFDirTabEntry dirTab = dirTabs.get(OFTableName.KERN); if (dirTab != null) { seekTab(fontFile, OFTableName.KERN, 2); for (int n = fontFile.readTTFUShort(); n > 0; n--) { fontFile.skip(2 * 2); int k = fontFile.readTTFUShort(); if (!((k & 1) != 0) || (k & 2) != 0 || (k & 4) != 0) { return; } if ((k >> 8) != 0) { continue; } k = fontFile.readTTFUShort(); fontFile.skip(3 * 2); while (k-- > 0) { int i = fontFile.readTTFUShort(); int j = fontFile.readTTFUShort(); int kpx = fontFile.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<Integer, Integer> adjTab = kerningTab.get(iObj); if (adjTab == null) { adjTab = new HashMap<Integer, Integer>(); } adjTab.put(u2, 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) for (Map.Entry<Integer, Map<Integer, Integer>> e1 : kerningTab.entrySet()) { Integer unicodeKey1 = e1.getKey(); Integer cidKey1 = unicodeToGlyph(unicodeKey1); Map<Integer, Integer> akpx = new HashMap<Integer, Integer>(); Map<Integer, Integer> ckpx = e1.getValue(); for (Map.Entry<Integer, Integer> e : ckpx.entrySet()) { Integer unicodeKey2 = e.getKey(); Integer cidKey2 = unicodeToGlyph(unicodeKey2); Integer kern = e.getValue(); for (Object o : mtxTab[cidKey2].getUnicodeIndex()) { Integer unicodeKey = (Integer) o; Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey); for (Integer ansiKey : ansiKeys) { akpx.put(ansiKey, kern); } } } if (akpx.size() > 0) { for (Object o : mtxTab[cidKey1].getUnicodeIndex()) { Integer unicodeKey = (Integer) o; Integer[] ansiKeys = unicodeToWinAnsi(unicodeKey); for (Integer ansiKey : ansiKeys) { ansiKerningTab.put(ansiKey, akpx); } } } } } }
Streams a font.
Params:
  • ttfOut – The interface for streaming TrueType tables.
Throws:
/** * Streams a font. * @param ttfOut The interface for streaming TrueType tables. * @exception IOException file write error */
public void stream(TTFOutputStream ttfOut) throws IOException { SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedDirTabs = sortDirTabMap(dirTabs); byte[] file = fontFile.getAllBytes(); TTFTableOutputStream tableOut = ttfOut.getTableOutputStream(); TTFGlyphOutputStream glyphOut = ttfOut.getGlyphOutputStream(); ttfOut.startFontStream(); for (Map.Entry<OFTableName, OFDirTabEntry> entry : sortedDirTabs) { int offset = (int) entry.getValue().getOffset(); int paddedLength = (int) entry.getValue().getLength(); paddedLength += getPadSize(offset + paddedLength); if (entry.getKey().equals(OFTableName.GLYF)) { streamGlyf(glyphOut, file, offset, paddedLength); } else { tableOut.streamTable(file, offset, paddedLength); } } ttfOut.endFontStream(); } private void streamGlyf(TTFGlyphOutputStream glyphOut, byte[] fontFile, int tableOffset, int tableLength) throws IOException { //Stream all but the last glyph int glyphStart = 0; int glyphEnd = 0; glyphOut.startGlyphStream(); for (int i = 0; i < mtxTab.length - 1; i++) { glyphStart = (int) mtxTab[i].getOffset() + tableOffset; glyphEnd = (int) mtxTab[i + 1].getOffset() + tableOffset; glyphOut.streamGlyph(fontFile, glyphStart, glyphEnd - glyphStart); } glyphOut.streamGlyph(fontFile, glyphEnd, (tableOffset + tableLength) - glyphEnd); glyphOut.endGlyphStream(); }
Returns the order in which the tables in a TrueType font should be written to file.
Params:
  • directoryTabs – the map that is to be sorted.
Returns:TTFTablesNames[] an array of table names sorted in the order they should appear in the TTF file.
/** * Returns the order in which the tables in a TrueType font should be written to file. * @param directoryTabs the map that is to be sorted. * @return TTFTablesNames[] an array of table names sorted in the order they should appear in * the TTF file. */
SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortDirTabMap(Map<OFTableName, OFDirTabEntry> directoryTabs) { SortedSet<Map.Entry<OFTableName, OFDirTabEntry>> sortedSet = new TreeSet<Map.Entry<OFTableName, OFDirTabEntry>>( new Comparator<Map.Entry<OFTableName, OFDirTabEntry>>() { public int compare(Entry<OFTableName, OFDirTabEntry> o1, Entry<OFTableName, OFDirTabEntry> o2) { return (int) (o1.getValue().getOffset() - o2.getValue().getOffset()); } }); // @SuppressFBWarnings("DMI_ENTRY_SETS_MAY_REUSE_ENTRY_OBJECTS") sortedSet.addAll(directoryTabs.entrySet()); return sortedSet; }
Returns this font's character to glyph mapping.
Returns:the font's cmap
/** * Returns this font's character to glyph mapping. * * @return the font's cmap */
public List<CMapSegment> 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.
Params:
  • name – The name to check
Throws:
Returns:True if not collection or font name present, false otherwise
/** * 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 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(String tag, String name) throws IOException { if ("ttcf".equals(tag)) { // This is a TrueType Collection fontFile.skip(4); // Read directory offsets int numDirectories = (int)fontFile.readTTFULong(); // int numDirectories=in.readTTFUShort(); long[] dirOffsets = new long[numDirectories]; for (int i = 0; i < numDirectories; i++) { dirOffsets[i] = fontFile.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++) { fontFile.seekSet(dirOffsets[i]); readDirTabs(); readName(); 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 = ""; } fontFile.seekSet(dirTabOffset); return found; } else { fontFile.seekSet(0); return true; } }
Return TTC font names
Params:
  • in – FontFileReader to read from
Throws:
Returns:True if not collection or font name present, false otherwise
/** * 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<String> getTTCnames(FontFileReader in) throws IOException { this.fontFile = in; List<String> fontNames = new ArrayList<String>(); 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(); } log.info("This is a TrueType collection file with " + numDirectories + " fonts"); log.info("Containing the following fonts: "); for (int i = 0; (i < numDirectories); i++) { in.seekSet(dirOffsets[i]); readDirTabs(); readName(); log.info(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<Integer> ret = new ArrayList<Integer>(); for (int i = 32; i < Glyphs.WINANSI_ENCODING.length; i++) { if (unicode == Glyphs.WINANSI_ENCODING[i]) { ret.add(i); } } return ret.toArray(new Integer[ret.size()]); }
Dumps a few informational values to System.out.
/** * 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: " + convertTTFUnit2PDFUnit(xHeight)); System.out.println("capheight: " + 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: [" + convertTTFUnit2PDFUnit(fontBBox1) + " " + convertTTFUnit2PDFUnit(fontBBox2) + " " + convertTTFUnit2PDFUnit(fontBBox3) + " " + convertTTFUnit2PDFUnit(fontBBox4) + "]"); } private String formatUnitsForDebug(int units) { return units + " -> " + convertTTFUnit2PDFUnit(units) + " internal units"; }
Map a glyph index to the corresponding unicode code point
Params:
  • glyphIndex –
Returns:unicode code point
/** * Map a glyph index to the corresponding unicode code point * * @param glyphIndex * @return unicode code point */
private Integer glyphToUnicode(int glyphIndex) { return glyphToUnicodeMap.get(glyphIndex); }
Map a unicode code point to the corresponding glyph index
Params:
  • unicodeIndex – unicode code point
Returns:glyph index
/** * Map a unicode code point to the corresponding glyph index * * @param unicodeIndex unicode code point * @return glyph index */
private Integer unicodeToGlyph(int unicodeIndex) throws IOException { final Integer result = unicodeToGlyphMap.get(unicodeIndex); if (result == null) { throw new IOException( "Glyph index not found for unicode value " + unicodeIndex); } return result; } String getGlyphName(int glyphIndex) { return mtxTab[glyphIndex].getName(); }
Determine if advanced (typographic) table is present.
Returns:true if advanced (typographic) table is present
/** * Determine if advanced (typographic) table is present. * @return true if advanced (typographic) table is present */
public boolean hasAdvancedTable() { if (advancedTableReader != null) { return advancedTableReader.hasAdvancedTable(); } else { return false; } }
Returns the GDEF table or null if none present.
Returns:the GDEF table
/** * Returns the GDEF table or null if none present. * @return the GDEF table */
public GlyphDefinitionTable getGDEF() { if (advancedTableReader != null) { return advancedTableReader.getGDEF(); } else { return null; } }
Returns the GSUB table or null if none present.
Returns:the GSUB table
/** * Returns the GSUB table or null if none present. * @return the GSUB table */
public GlyphSubstitutionTable getGSUB() { if (advancedTableReader != null) { return advancedTableReader.getGSUB(); } else { return null; } }
Returns the GPOS table or null if none present.
Returns:the GPOS table
/** * Returns the GPOS table or null if none present. * @return the GPOS table */
public GlyphPositioningTable getGPOS() { if (advancedTableReader != null) { return advancedTableReader.getGPOS(); } else { return null; } }
Static main method to get info about a TrueType font.
Params:
  • args – The command line arguments
/** * Static main method to get info about a TrueType font. * @param args The command line arguments */
public static void main(String[] args) { InputStream stream = null; try { boolean useKerning = true; boolean useAdvanced = true; stream = new FileInputStream(args[0]); FontFileReader reader = new FontFileReader(stream); String name = null; if (args.length >= 2) { name = args[1]; } String header = OFFontLoader.readHeader(reader); boolean isCFF = header.equals("OTTO"); OpenFont otfFile = (isCFF) ? new OTFFile() : new TTFFile(useKerning, useAdvanced); otfFile.readFont(reader, header, name); otfFile.printStuff(); } catch (IOException ioe) { System.err.println("Problem reading font: " + ioe.toString()); ioe.printStackTrace(System.err); } finally { IOUtils.closeQuietly(stream); } } public String getEmbedFontName() { return embedFontName; } public String getCopyrightNotice() { return notice; } }