| /* |
| * 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.Arrays; |
| import java.util.BitSet; |
| 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.AdvancedTypographicTableFormatException; |
| import org.apache.fop.fonts.FontUtil; |
| import org.apache.fop.fonts.GlyphClassTable; |
| import org.apache.fop.fonts.GlyphCoverageTable; |
| import org.apache.fop.fonts.GlyphDefinitionSubtable; |
| import org.apache.fop.fonts.GlyphDefinitionTable; |
| import org.apache.fop.fonts.GlyphMappingTable; |
| import org.apache.fop.fonts.GlyphPositioningSubtable; |
| import org.apache.fop.fonts.GlyphPositioningTable; |
| import org.apache.fop.fonts.GlyphSubstitutionSubtable; |
| import org.apache.fop.fonts.GlyphSubstitutionTable; |
| import org.apache.fop.fonts.GlyphSubtable; |
| import org.apache.fop.fonts.GlyphTable; |
| |
| // CSOFF: AvoidNestedBlocksCheck |
| // CSOFF: NoWhitespaceAfterCheck |
| // CSOFF: InnerAssignmentCheck |
| // CSOFF: SimplifyBooleanReturnCheck |
| // CSOFF: LineLengthCheck |
| |
| /** |
| * 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; |
| |
| private final String encoding = "WinAnsiEncoding"; // Default encoding |
| |
| private final short firstChar = 0; |
| |
| private boolean useKerning = false; |
| private boolean useAdvanced = false; |
| |
| private boolean isEmbeddable = true; |
| private boolean hasSerifs = true; |
| /** |
| * Table directory |
| */ |
| protected Map dirTabs; |
| private Map<Integer, Map<Integer, Integer>> kerningTab; // for CIDs |
| private Map<Integer, Map<Integer, Integer>> ansiKerningTab; // For winAnsiEncoding |
| private List cmaps; |
| private Set unicodeMappings; |
| |
| 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 final int[] mtxEncoded = null; |
| |
| private String postScriptName = ""; |
| private String fullName = ""; |
| private String notice = ""; |
| private final 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 final Map glyphToUnicodeMap = new java.util.HashMap(); |
| private final Map unicodeToGlyphMap = new java.util.HashMap(); |
| |
| private TTFDirTabEntry currentDirTab; |
| |
| private boolean isCFF; |
| |
| /* advanced typographic support */ |
| private Map/*<String,Object[3]>*/ seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages } |
| private Map/*<String,Object[2]>*/ seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>") |
| private Map/*<String,List<String>>*/ seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") } |
| private GlyphMappingTable seMapping; |
| private List seEntries; |
| private List seSubtables; |
| private GlyphDefinitionTable gdef; |
| private GlyphSubstitutionTable gsub; |
| private GlyphPositioningTable gpos; |
| |
| /** |
| * logging instance |
| */ |
| protected Log log = LogFactory.getLog(TTFFile.class); |
| |
| /** |
| * Constructor |
| * @param useKerning true if kerning data should be loaded |
| * @param useAdvanced true if advanced typographic tables should be loaded |
| */ |
| public TTFFile ( boolean useKerning, boolean useAdvanced ) { |
| this.useKerning = useKerning; |
| this.useAdvanced = useAdvanced; |
| } |
| |
| /** |
| * Key-value helper class |
| */ |
| class UnicodeMapping implements Comparable { |
| |
| private final int unicodeIndex; |
| private final 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; |
| } |
| |
| |
| /** {@inheritDoc} */ |
| public int hashCode() { |
| int hc = unicodeIndex; |
| hc = 19 * hc + ( hc ^ glyphIndex ); |
| return hc; |
| } |
| |
| /** {@inheritDoc} */ |
| public boolean equals ( Object o ) { |
| if ( o instanceof UnicodeMapping ) { |
| UnicodeMapping m = (UnicodeMapping) o; |
| if ( unicodeIndex != m.unicodeIndex ) { |
| return false; |
| } else if ( glyphIndex != m.glyphIndex ) { |
| return false; |
| } else { |
| return true; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| /** {@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; |
| } |
| } |
| } |
| |
| /** |
| * 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 { |
| |
| unicodeMappings = new java.util.TreeSet(); |
| |
| seekTab(in, "cmap", 2); |
| int numCMap = in.readTTFUShort(); // Number of cmap subtables |
| long cmapUniOffset = 0; |
| long symbolMapOffset = 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 (cmapPID == 3 && cmapEID == 0) { |
| symbolMapOffset = cmapOffset; |
| } |
| } |
| |
| if (cmapUniOffset > 0) { |
| return readUnicodeCmap(in, cmapUniOffset, 1); |
| } else if (symbolMapOffset > 0) { |
| return readUnicodeCmap(in, symbolMapOffset, 0); |
| } else { |
| log.fatal("Unsupported TrueType font: No Unicode or Symbol cmap table" |
| + " not present. Aborting"); |
| return false; |
| } |
| } |
| |
| private boolean readUnicodeCmap // CSOK: MethodLength |
| (FontFileReader in, long cmapUniOffset, int encodingID) |
| throws IOException { |
| //Read CMAP table and correct mtxTab.index |
| int mtxPtr = 0; |
| |
| // 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(); |
| |
| 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; |
| in.seekSet(glyphOffset); |
| glyphIdx = (in.readTTFUShort() + cmapDeltas[i]) |
| & 0xffff; |
| |
| unicodeMappings.add(new UnicodeMapping(glyphIdx, j)); |
| mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(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(glyphIdx, mapped)); |
| mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped)); |
| } |
| } |
| |
| // 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); |
| } |
| |
| unicodeMappings.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++; |
| } |
| } |
| } |
| } |
| } |
| } 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); |
| } |
| |
| /** |
| * 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(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(); |
| |
| if ( useKerning ) { |
| readKerning(in); |
| } |
| |
| // Read advanced typographic tables. If any format exception, |
| // reset (thus ignoring) all advanced typographic tables. |
| if ( useAdvanced ) { |
| try { |
| readGDEF(in); |
| readGSUB(in); |
| readGPOS(in); |
| } catch ( AdvancedTypographicTableFormatException e ) { |
| resetATStateAll(); |
| log.warn ( "Encountered format constraint violation in advanced (typographic) table (AT) " |
| + "in font '" + getFullName() + "', ignoring AT data: " |
| + e.getMessage() ); |
| } |
| } |
| |
| guessVerticalMetricsFromGlyphBBox(); |
| return true; |
| } |
| |
| private void createCMaps() { |
| cmaps = new java.util.ArrayList(); |
| TTFCmapEntry tce = new TTFCmapEntry(); |
| |
| Iterator e = unicodeMappings.iterator(); |
| 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 convertTTFUnit2PDFUnit(capHeight); |
| } |
| |
| /** |
| * Returns the XHeight attribute of the font. |
| * @return int The XHeight |
| */ |
| public int getXHeight() { |
| return 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] = convertTTFUnit2PDFUnit(fontBBox1); |
| fbb[1] = convertTTFUnit2PDFUnit(fontBBox2); |
| fbb[2] = convertTTFUnit2PDFUnit(fontBBox3); |
| fbb[3] = convertTTFUnit2PDFUnit(fontBBox4); |
| |
| return fbb; |
| } |
| |
| /** |
| * Returns the LowerCaseAscent attribute of the font. |
| * @return int The LowerCaseAscent |
| */ |
| public int getLowerCaseAscent() { |
| return convertTTFUnit2PDFUnit(ascender); |
| } |
| |
| /** |
| * 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. |
| * @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] = 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 convertTTFUnit2PDFUnit(ansiWidth[idx]); |
| } |
| |
| /** |
| * Returns the kerning table. |
| * @return Map The kerning table |
| */ |
| public Map<Integer, Map<Integer, Integer>> getKerning() { |
| return kerningTab; |
| } |
| |
| /** |
| * Returns the ANSI kerning table. |
| * @return Map The ANSI kerning table |
| */ |
| public Map<Integer, Map<Integer, Integer>> 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); |
| int flags = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug("flags: " + flags + " - " + Integer.toString(flags, 2)); |
| } |
| upem = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug("unit per em: " + upem); |
| } |
| |
| in.skip(16); |
| |
| fontBBox1 = in.readTTFShort(); |
| fontBBox2 = in.readTTFShort(); |
| fontBBox3 = in.readTTFShort(); |
| fontBBox4 = in.readTTFShort(); |
| if (log.isDebugEnabled()) { |
| log.debug("font bbox: xMin=" + fontBBox1 |
| + " yMin=" + fontBBox2 |
| + " xMax=" + fontBBox3 |
| + " yMax=" + fontBBox4); |
| } |
| |
| 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(); |
| hheaDescender = in.readTTFShort(); |
| |
| in.skip(2 + 2 + 3 * 2 + 8 * 2); |
| nhmtx = in.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 |
| * @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 (log.isTraceEnabled()) { |
| log.trace("*** 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 (log.isTraceEnabled()) { |
| log.trace(" 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 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 void readOS2(FontFileReader in) throws IOException { |
| // Check if font is embeddable |
| TTFDirTabEntry os2Entry = (TTFDirTabEntry)dirTabs.get("OS/2"); |
| if (os2Entry != null) { |
| seekTab(in, "OS/2", 0); |
| int version = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug("OS/2 table: version=" + version |
| + ", offset=" + os2Entry.getOffset() + ", len=" + os2Entry.getLength()); |
| } |
| in.skip(2); //xAvgCharWidth |
| 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 |
| os2Descender = in.readTTFShort(); //sTypoDescender |
| if (log.isDebugEnabled()) { |
| log.debug("sTypoAscender: " + os2Ascender |
| + " -> internal " + convertTTFUnit2PDFUnit(os2Ascender)); |
| log.debug("sTypoDescender: " + os2Descender |
| + " -> internal " + convertTTFUnit2PDFUnit(os2Descender)); |
| } |
| v = in.readTTFShort(); //sTypoLineGap |
| if (log.isDebugEnabled()) { |
| log.debug("sTypoLineGap: " + v); |
| } |
| v = in.readTTFUShort(); //usWinAscent |
| if (log.isDebugEnabled()) { |
| log.debug("usWinAscent: " + formatUnitsForDebug(v)); |
| } |
| v = in.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)) { |
| in.skip(2 * 4); |
| this.os2xHeight = in.readTTFShort(); //sxHeight |
| this.os2CapHeight = in.readTTFShort(); //sCapHeight |
| if (log.isDebugEnabled()) { |
| log.debug("sxHeight: " + this.os2xHeight); |
| 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 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 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 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: " + formatUnitsForDebug(xHeight)); |
| in.skip(2 * 2); |
| capHeight = in.readTTFUShort(); |
| log.debug("capHeight from PCLT: " + formatUnitsForDebug(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; |
| } |
| } |
| |
| 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."); |
| } |
| } |
| } |
| |
| 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 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 (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]; |
| } |
| } |
| } |
| } |
| 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. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private 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 = kerningTab.get(iObj); |
| if (adjTab == null) { |
| adjTab = new java.util.HashMap(); |
| } |
| adjTab.put(u2, new Integer(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<Integer, Integer> akpx = new java.util.HashMap(); |
| Map ckpx = 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); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** helper method for formatting an integer array for output */ |
| private String toString ( int[] ia ) { |
| StringBuffer sb = new StringBuffer(); |
| if ( ( ia == null ) || ( ia.length == 0 ) ) { |
| sb.append ( '-' ); |
| } else { |
| boolean first = true; |
| for ( int i = 0; i < ia.length; i++ ) { |
| if ( ! first ) { |
| sb.append ( ' ' ); |
| } else { |
| first = false; |
| } |
| sb.append ( ia[i] ); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| private void readLangSysTable(FontFileReader in, String tableTag, long langSysTable, String langSysTag) throws IOException { |
| in.seekSet(langSysTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table: " + langSysTag ); |
| } |
| // read lookup order (reorder) table offset |
| int lo = in.readTTFUShort(); |
| // read required feature index |
| int rf = in.readTTFUShort(); |
| String rfi; |
| if ( rf != 65535 ) { |
| rfi = "f" + rf; |
| } else { |
| rfi = null; |
| } |
| // read (non-required) feature count |
| int nf = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table reorder table: " + lo ); |
| log.debug(tableTag + " lang sys table required feature index: " + rf ); |
| log.debug(tableTag + " lang sys table non-required feature count: " + nf ); |
| } |
| // read (non-required) feature indices |
| int[] fia = new int[nf]; |
| List fl = new java.util.ArrayList(); |
| for ( int i = 0; i < nf; i++ ) { |
| int fi = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys table non-required feature index: " + fi ); |
| } |
| fia[i] = fi; |
| fl.add ( "f" + fi ); |
| } |
| if ( seLanguages == null ) { |
| seLanguages = new java.util.LinkedHashMap(); |
| } |
| seLanguages.put ( langSysTag, new Object[] { rfi, fl } ); |
| } |
| |
| private static String defaultTag = "dflt"; |
| |
| private void readScriptTable(FontFileReader in, String tableTag, long scriptTable, String scriptTag) throws IOException { |
| in.seekSet(scriptTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script table: " + scriptTag ); |
| } |
| // read default language system table offset |
| int dl = in.readTTFUShort(); |
| String dt = defaultTag; |
| if ( dl > 0 ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " default lang sys tag: " + dt ); |
| log.debug(tableTag + " default lang sys table offset: " + dl ); |
| } |
| } |
| // read language system record count |
| int nl = in.readTTFUShort(); |
| List ll = new java.util.ArrayList(); |
| if ( nl > 0 ) { |
| String[] lta = new String[nl]; |
| int[] loa = new int[nl]; |
| // read language system records |
| for ( int i = 0, n = nl; i < n; i++ ) { |
| String lt = in.readTTFString(4); |
| int lo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys tag: " + lt ); |
| log.debug(tableTag + " lang sys table offset: " + lo ); |
| } |
| lta[i] = lt; |
| loa[i] = lo; |
| if ( dl == lo ) { |
| dl = 0; |
| dt = lt; |
| } |
| ll.add ( lt ); |
| } |
| // read non-default language system tables |
| for ( int i = 0, n = nl; i < n; i++ ) { |
| readLangSysTable ( in, tableTag, scriptTable + loa [ i ], lta [ i ] ); |
| } |
| } |
| // read default language system table (if specified) |
| if ( dl > 0 ) { |
| readLangSysTable ( in, tableTag, scriptTable + dl, dt ); |
| } else if ( dt != null ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lang sys default: " + dt ); |
| } |
| } |
| seScripts.put ( scriptTag, new Object[] { dt, ll, seLanguages } ); |
| seLanguages = null; |
| } |
| |
| private void readScriptList(FontFileReader in, String tableTag, long scriptList) throws IOException { |
| in.seekSet(scriptList); |
| // read script record count |
| int ns = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list record count: " + ns ); |
| } |
| if ( ns > 0 ) { |
| String[] sta = new String[ns]; |
| int[] soa = new int[ns]; |
| // read script records |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| String st = in.readTTFString(4); |
| int so = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script tag: " + st ); |
| log.debug(tableTag + " script table offset: " + so ); |
| } |
| sta[i] = st; |
| soa[i] = so; |
| } |
| // read script tables |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| seLanguages = null; |
| readScriptTable ( in, tableTag, scriptList + soa [ i ], sta [ i ] ); |
| } |
| } |
| } |
| |
| private void readFeatureTable(FontFileReader in, String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException { |
| in.seekSet(featureTable); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table: " + featureTag ); |
| } |
| // read feature params offset |
| int po = in.readTTFUShort(); |
| // read lookup list indices count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table parameters offset: " + po ); |
| log.debug(tableTag + " feature table lookup list index count: " + nl ); |
| } |
| // read lookup table indices |
| int[] lia = new int[nl]; |
| List lul = new java.util.ArrayList(); |
| for ( int i = 0; i < nl; i++ ) { |
| int li = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature table lookup index: " + li ); |
| } |
| lia[i] = li; |
| lul.add ( "lu" + li ); |
| } |
| seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } ); |
| } |
| |
| private void readFeatureList(FontFileReader in, String tableTag, long featureList) throws IOException { |
| in.seekSet(featureList); |
| // read feature record count |
| int nf = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature list record count: " + nf ); |
| } |
| if ( nf > 0 ) { |
| String[] fta = new String[nf]; |
| int[] foa = new int[nf]; |
| // read feature records |
| for ( int i = 0, n = nf; i < n; i++ ) { |
| String ft = in.readTTFString(4); |
| int fo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature tag: " + ft ); |
| log.debug(tableTag + " feature table offset: " + fo ); |
| } |
| fta[i] = ft; |
| foa[i] = fo; |
| } |
| // read feature tables |
| for ( int i = 0, n = nf; i < n; i++ ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " feature index: " + i ); |
| } |
| readFeatureTable ( in, tableTag, featureList + foa [ i ], fta [ i ], i ); |
| } |
| } |
| } |
| |
| /** |
| * Determine if advanced (typographic) table is present. |
| * @return true if advanced (typographic) table is present |
| */ |
| public boolean hasAdvancedTable() { |
| return ( gdef != null ) || ( gsub != null ) || ( gpos != null ); |
| } |
| |
| /** |
| * Returns the GDEF table or null if none present. |
| * @return the GDEF table |
| */ |
| public GlyphDefinitionTable getGDEF() { |
| return gdef; |
| } |
| |
| /** |
| * Returns the GSUB table or null if none present. |
| * @return the GSUB table |
| */ |
| public GlyphSubstitutionTable getGSUB() { |
| return gsub; |
| } |
| |
| /** |
| * Returns the GPOS table or null if none present. |
| * @return the GPOS table |
| */ |
| public GlyphPositioningTable getGPOS() { |
| return gpos; |
| } |
| |
| static final class GDEFLookupType { |
| static final int GLYPH_CLASS = 1; |
| static final int ATTACHMENT_POINT = 2; |
| static final int LIGATURE_CARET = 3; |
| static final int MARK_ATTACHMENT = 4; |
| private GDEFLookupType() { |
| } |
| public static int getSubtableType ( int lt ) { |
| int st; |
| switch ( lt ) { |
| case GDEFLookupType.GLYPH_CLASS: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS; |
| break; |
| case GDEFLookupType.ATTACHMENT_POINT: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT; |
| break; |
| case GDEFLookupType.LIGATURE_CARET: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET; |
| break; |
| case GDEFLookupType.MARK_ATTACHMENT: |
| st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT; |
| break; |
| default: |
| st = -1; |
| break; |
| } |
| return st; |
| } |
| public static String toString(int type) { |
| String s; |
| switch ( type ) { |
| case GLYPH_CLASS: |
| s = "GlyphClass"; |
| break; |
| case ATTACHMENT_POINT: |
| s = "AttachmentPoint"; |
| break; |
| case LIGATURE_CARET: |
| s = "LigatureCaret"; |
| break; |
| case MARK_ATTACHMENT: |
| s = "MarkAttachment"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class GSUBLookupType { |
| static final int SINGLE = 1; |
| static final int MULTIPLE = 2; |
| static final int ALTERNATE = 3; |
| static final int LIGATURE = 4; |
| static final int CONTEXTUAL = 5; |
| static final int CHAINED_CONTEXTUAL = 6; |
| static final int EXTENSION = 7; |
| static final int REVERSE_CHAINED_SINGLE = 8; |
| private GSUBLookupType() { |
| } |
| public static int getSubtableType ( int lt ) { |
| int st; |
| switch ( lt ) { |
| case GSUBLookupType.SINGLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE; |
| break; |
| case GSUBLookupType.MULTIPLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE; |
| break; |
| case GSUBLookupType.ALTERNATE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE; |
| break; |
| case GSUBLookupType.LIGATURE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE; |
| break; |
| case GSUBLookupType.CONTEXTUAL: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL; |
| break; |
| case GSUBLookupType.CHAINED_CONTEXTUAL: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL; |
| break; |
| case GSUBLookupType.EXTENSION: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION; |
| break; |
| case GSUBLookupType.REVERSE_CHAINED_SINGLE: |
| st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE; |
| break; |
| default: |
| st = -1; |
| break; |
| } |
| return st; |
| } |
| public static String toString(int type) { |
| String s; |
| switch ( type ) { |
| case SINGLE: |
| s = "Single"; |
| break; |
| case MULTIPLE: |
| s = "Multiple"; |
| break; |
| case ALTERNATE: |
| s = "Alternate"; |
| break; |
| case LIGATURE: |
| s = "Ligature"; |
| break; |
| case CONTEXTUAL: |
| s = "Contextual"; |
| break; |
| case CHAINED_CONTEXTUAL: |
| s = "ChainedContextual"; |
| break; |
| case EXTENSION: |
| s = "Extension"; |
| break; |
| case REVERSE_CHAINED_SINGLE: |
| s = "ReverseChainedSingle"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class GPOSLookupType { |
| static final int SINGLE = 1; |
| static final int PAIR = 2; |
| static final int CURSIVE = 3; |
| static final int MARK_TO_BASE = 4; |
| static final int MARK_TO_LIGATURE = 5; |
| static final int MARK_TO_MARK = 6; |
| static final int CONTEXTUAL = 7; |
| static final int CHAINED_CONTEXTUAL = 8; |
| static final int EXTENSION = 9; |
| private GPOSLookupType() { |
| } |
| public static String toString(int type) { |
| String s; |
| switch ( type ) { |
| case SINGLE: |
| s = "Single"; |
| break; |
| case PAIR: |
| s = "Pair"; |
| break; |
| case CURSIVE: |
| s = "Cursive"; |
| break; |
| case MARK_TO_BASE: |
| s = "MarkToBase"; |
| break; |
| case MARK_TO_LIGATURE: |
| s = "MarkToLigature"; |
| break; |
| case MARK_TO_MARK: |
| s = "MarkToMark"; |
| break; |
| case CONTEXTUAL: |
| s = "Contextual"; |
| break; |
| case CHAINED_CONTEXTUAL: |
| s = "ChainedContextual"; |
| break; |
| case EXTENSION: |
| s = "Extension"; |
| break; |
| default: |
| s = "?"; |
| break; |
| } |
| return s; |
| } |
| } |
| |
| static final class LookupFlag { |
| static final int RIGHT_TO_LEFT = 0x0001; |
| static final int IGNORE_BASE_GLYPHS = 0x0002; |
| static final int IGNORE_LIGATURE = 0x0004; |
| static final int IGNORE_MARKS = 0x0008; |
| static final int USE_MARK_FILTERING_SET = 0x0010; |
| static final int MARK_ATTACHMENT_TYPE = 0xFF00; |
| private LookupFlag() { |
| } |
| public static String toString(int flags) { |
| StringBuffer sb = new StringBuffer(); |
| boolean first = true; |
| if ( ( flags & RIGHT_TO_LEFT ) != 0 ) { |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append ( '|' ); |
| } |
| sb.append ( "RightToLeft" ); |
| } |
| if ( ( flags & IGNORE_BASE_GLYPHS ) != 0 ) { |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append ( '|' ); |
| } |
| sb.append ( "IgnoreBaseGlyphs" ); |
| } |
| if ( ( flags & IGNORE_LIGATURE ) != 0 ) { |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append ( '|' ); |
| } |
| sb.append ( "IgnoreLigature" ); |
| } |
| if ( ( flags & IGNORE_MARKS ) != 0 ) { |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append ( '|' ); |
| } |
| sb.append ( "IgnoreMarks" ); |
| } |
| if ( ( flags & USE_MARK_FILTERING_SET ) != 0 ) { |
| if ( first ) { |
| first = false; |
| } else { |
| sb.append ( '|' ); |
| } |
| sb.append ( "UseMarkFilteringSet" ); |
| } |
| if ( sb.length() == 0 ) { |
| sb.append ( '-' ); |
| } |
| return sb.toString(); |
| } |
| } |
| |
| private GlyphCoverageTable readCoverageTableFormat1(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| int[] ga = new int[ng]; |
| for ( int i = 0, n = ng; i < n; i++ ) { |
| int g = in.readTTFUShort(); |
| ga[i] = g; |
| entries.add ( Integer.valueOf(g) ); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " glyphs: " + toString(ga) ); |
| } |
| return GlyphCoverageTable.createCoverageTable ( entries ); |
| } |
| |
| private GlyphCoverageTable readCoverageTableFormat2(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read range record count |
| int nr = in.readTTFUShort(); |
| for ( int i = 0, n = nr; i < n; i++ ) { |
| // read range start |
| int s = in.readTTFUShort(); |
| // read range end |
| int e = in.readTTFUShort(); |
| // read range coverage (mapping) index |
| int m = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m ); |
| } |
| entries.add ( new GlyphCoverageTable.MappingRange ( s, e, m ) ); |
| } |
| return GlyphCoverageTable.createCoverageTable ( entries ); |
| } |
| |
| private GlyphCoverageTable readCoverageTable(FontFileReader in, String label, long tableOffset) throws IOException { |
| GlyphCoverageTable gct; |
| long cp = in.getCurrentPos(); |
| in.seekSet(tableOffset); |
| // read coverage table format |
| int cf = in.readTTFUShort(); |
| if ( cf == 1 ) { |
| gct = readCoverageTableFormat1 ( in, label, tableOffset, cf ); |
| } else if ( cf == 2 ) { |
| gct = readCoverageTableFormat2 ( in, label, tableOffset, cf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported coverage table format: " + cf ); |
| } |
| in.seekSet ( cp ); |
| return gct; |
| } |
| |
| private GlyphClassTable readClassDefTableFormat1(FontFileReader in, String label, long tableOffset, int classFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read start glyph |
| int sg = in.readTTFUShort(); |
| entries.add ( Integer.valueOf(sg) ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read glyph classes |
| int[] ca = new int[ng]; |
| for ( int i = 0, n = ng; i < n; i++ ) { |
| int gc = in.readTTFUShort(); |
| ca[i] = gc; |
| entries.add ( Integer.valueOf(gc) ); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " glyph classes: " + toString(ca) ); |
| } |
| return GlyphClassTable.createClassTable ( entries ); |
| } |
| |
| private GlyphClassTable readClassDefTableFormat2(FontFileReader in, String label, long tableOffset, int classFormat) throws IOException { |
| List entries = new java.util.ArrayList(); |
| in.seekSet(tableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read range record count |
| int nr = in.readTTFUShort(); |
| for ( int i = 0, n = nr; i < n; i++ ) { |
| // read range start |
| int s = in.readTTFUShort(); |
| // read range end |
| int e = in.readTTFUShort(); |
| // read range glyph class (mapping) index |
| int m = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m ); |
| } |
| entries.add ( new GlyphClassTable.MappingRange ( s, e, m ) ); |
| } |
| return GlyphClassTable.createClassTable ( entries ); |
| } |
| |
| private GlyphClassTable readClassDefTable(FontFileReader in, String label, long tableOffset) throws IOException { |
| GlyphClassTable gct; |
| long cp = in.getCurrentPos(); |
| in.seekSet(tableOffset); |
| // read class table format |
| int cf = in.readTTFUShort(); |
| if ( cf == 1 ) { |
| gct = readClassDefTableFormat1 ( in, label, tableOffset, cf ); |
| } else if ( cf == 2 ) { |
| gct = readClassDefTableFormat2 ( in, label, tableOffset, cf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported class definition table format: " + cf ); |
| } |
| in.seekSet ( cp ); |
| return gct; |
| } |
| |
| private void readSingleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read delta glyph |
| int dg = in.readTTFShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)" ); |
| log.debug(tableTag + " single substitution coverage table offset: " + co ); |
| log.debug(tableTag + " single substitution delta: " + dg ); |
| } |
| // read coverage table |
| seMapping = readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co ); |
| seEntries.add ( Integer.valueOf ( dg ) ); |
| } |
| |
| private void readSingleSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)" ); |
| log.debug(tableTag + " single substitution coverage table offset: " + co ); |
| log.debug(tableTag + " single substitution glyph count: " + ng ); |
| } |
| // read coverage table |
| seMapping = readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co ); |
| // read glyph substitutions |
| int[] gsa = new int[ng]; |
| for ( int i = 0, n = ng; i < n; i++ ) { |
| int gs = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs ); |
| } |
| gsa[i] = gs; |
| seEntries.add ( Integer.valueOf ( gs ) ); |
| } |
| } |
| |
| private int readSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readSingleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readSingleSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported single substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readMultipleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read sequence count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)" ); |
| log.debug(tableTag + " multiple substitution coverage table offset: " + co ); |
| log.debug(tableTag + " multiple substitution sequence count: " + ns ); |
| } |
| // read coverage table |
| seMapping = readCoverageTable ( in, tableTag + " multiple substitution coverage", subtableOffset + co ); |
| // read sequence table offsets |
| int[] soa = new int[ns]; |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read sequence tables |
| int[][] gsa = new int [ ns ] []; |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| int so = soa[i]; |
| int[] ga; |
| if ( so > 0 ) { |
| in.seekSet(subtableOffset + so); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| ga = new int[ng]; |
| for ( int j = 0; j < ng; j++ ) { |
| ga[j] = in.readTTFUShort(); |
| } |
| } else { |
| ga = null; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString ( ga ) ); |
| } |
| gsa [ i ] = ga; |
| } |
| seEntries.add ( gsa ); |
| } |
| |
| private int readMultipleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readMultipleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported multiple substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readAlternateSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read alternate set count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)" ); |
| log.debug(tableTag + " alternate substitution coverage table offset: " + co ); |
| log.debug(tableTag + " alternate substitution alternate set count: " + ns ); |
| } |
| // read coverage table |
| seMapping = readCoverageTable ( in, tableTag + " alternate substitution coverage", subtableOffset + co ); |
| // read alternate set table offsets |
| int[] soa = new int[ns]; |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read alternate set tables |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| int so = soa[i]; |
| in.seekSet(subtableOffset + so); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| int[] ga = new int[ng]; |
| for ( int j = 0; j < ng; j++ ) { |
| int gs = in.readTTFUShort(); |
| ga[j] = gs; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString ( ga ) ); |
| } |
| seEntries.add ( ga ); |
| } |
| } |
| |
| private int readAlternateSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readAlternateSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported alternate substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readLigatureSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read ligature set count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)" ); |
| log.debug(tableTag + " ligature substitution coverage table offset: " + co ); |
| log.debug(tableTag + " ligature substitution ligature set count: " + ns ); |
| } |
| // read coverage table |
| seMapping = readCoverageTable ( in, tableTag + " ligature substitution coverage", subtableOffset + co ); |
| // read ligature set table offsets |
| int[] soa = new int[ns]; |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| soa[i] = in.readTTFUShort(); |
| } |
| // read ligature set tables |
| for ( int i = 0, n = ns; i < n; i++ ) { |
| int so = soa[i]; |
| in.seekSet(subtableOffset + so); |
| // read ligature table count |
| int nl = in.readTTFUShort(); |
| int[] loa = new int[nl]; |
| for ( int j = 0; j < nl; j++ ) { |
| loa[j] = in.readTTFUShort(); |
| } |
| List ligs = new java.util.ArrayList(); |
| for ( int j = 0; j < nl; j++ ) { |
| int lo = loa[j]; |
| in.seekSet(subtableOffset + so + lo); |
| // read ligature glyph id |
| int lg = in.readTTFUShort(); |
| // read ligature (input) component count |
| int nc = in.readTTFUShort(); |
| int[] ca = new int [ nc - 1 ]; |
| // read ligature (input) component glyph ids |
| for ( int k = 0; k < nc - 1; k++ ) { |
| ca[k] = in.readTTFUShort(); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString ( ca ) ); |
| } |
| ligs.add ( new GlyphSubstitutionTable.Ligature ( lg, ca ) ); |
| } |
| seEntries.add ( new GlyphSubstitutionTable.LigatureSet ( ligs ) ); |
| } |
| } |
| |
| private int readLigatureSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readLigatureSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported ligature substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private GlyphTable.RuleLookup[] readRuleLookups(FontFileReader in, int numLookups, String header) throws IOException { |
| GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ]; |
| for ( int i = 0, n = numLookups; i < n; i++ ) { |
| int sequenceIndex = in.readTTFUShort(); |
| int lookupIndex = in.readTTFUShort(); |
| la [ i ] = new GlyphTable.RuleLookup ( sequenceIndex, lookupIndex ); |
| // dump info if debugging and header is non-null |
| if ( log.isDebugEnabled() && ( header != null ) ) { |
| log.debug(header + "lookup[" + i + "]: " + la[i]); |
| } |
| } |
| return la; |
| } |
| |
| private void readContextualSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for ( int i = 0; i < nrs; i++ ) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)" ); |
| log.debug(tableTag + " contextual substitution coverage table offset: " + co ); |
| log.debug(tableTag + " contextual substitution rule set count: " + nrs ); |
| for ( int i = 0; i < nrs; i++ ) { |
| log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " contextual substitution coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for ( int i = 0; i < nrs; i++ ) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if ( rso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + rso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| GlyphTable.GlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + rso + ro ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ ng - 1 ]; |
| for ( int k = 0, nk = glyphs.length; k < nk; k++ ) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( rsa ); |
| } |
| |
| private void readContextualSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read class def table offset |
| int cdo = in.readTTFUShort(); |
| // read class rule set count |
| int ngc = in.readTTFUShort(); |
| // read class rule set offsets |
| int[] csoa = new int [ ngc ]; |
| for ( int i = 0; i < ngc; i++ ) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)" ); |
| log.debug(tableTag + " contextual substitution coverage table offset: " + co ); |
| log.debug(tableTag + " contextual substitution class set count: " + ngc ); |
| for ( int i = 0; i < ngc; i++ ) { |
| log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " contextual substitution coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read class definition table |
| GlyphClassTable cdt; |
| if ( cdo > 0 ) { |
| cdt = readClassDefTable ( in, tableTag + " contextual substitution class definition", subtableOffset + cdo ); |
| } else { |
| cdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for ( int i = 0; i < ngc; i++ ) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if ( cso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + cso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| int ro = roa [ j ]; |
| GlyphTable.ClassSequenceRule r; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + cso + ro ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read classes |
| int[] classes = new int [ ng - 1 ]; |
| for ( int k = 0, nk = classes.length; k < nk; k++ ) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes ); |
| } else { |
| assert ro > 0 : "unexpected null subclass rule offset"; |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( cdt ); |
| seEntries.add ( Integer.valueOf ( ngc ) ); |
| seEntries.add ( rsa ); |
| } |
| |
| private void readContextualSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read glyph (input sequence length) count |
| int ng = in.readTTFUShort(); |
| // read substitution lookup count |
| int nl = in.readTTFUShort(); |
| // read glyph coverage offsets, one per glyph input sequence length count |
| int[] gcoa = new int [ ng ]; |
| for ( int i = 0; i < ng; i++ ) { |
| gcoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)" ); |
| log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng ); |
| log.debug(tableTag + " contextual substitution lookup count: " + nl ); |
| for ( int i = 0; i < ng; i++ ) { |
| log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i] ); |
| } |
| } |
| // read coverage tables |
| GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; |
| for ( int i = 0; i < ng; i++ ) { |
| int gco = gcoa [ i ]; |
| GlyphCoverageTable gct; |
| if ( gco > 0 ) { |
| gct = readCoverageTable ( in, tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco ); |
| } else { |
| gct = null; |
| } |
| gca [ i ] = gct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca ); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} ); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert ( gca != null ) && ( gca.length > 0 ); |
| seMapping = gca[0]; |
| seEntries.add ( rsa ); |
| } |
| |
| private int readContextualSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readContextualSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readContextualSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 3 ) { |
| readContextualSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported contextual substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readChainedContextualSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for ( int i = 0; i < nrs; i++ ) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)" ); |
| log.debug(tableTag + " chained contextual substitution coverage table offset: " + co ); |
| log.debug(tableTag + " chained contextual substitution rule set count: " + nrs ); |
| for ( int i = 0; i < nrs; i++ ) { |
| log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " chained contextual substitution coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for ( int i = 0; i < nrs; i++ ) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if ( rso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + rso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| GlyphTable.ChainedGlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + rso + ro ); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyphs |
| int[] backtrackGlyphs = new int [ nbg ]; |
| for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) { |
| backtrackGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ nig - 1 ]; |
| for ( int k = 0, nk = glyphs.length; k < nk; k++ ) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyphs |
| int[] lookaheadGlyphs = new int [ nlg ]; |
| for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) { |
| lookaheadGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( rsa ); |
| } |
| |
| private void readChainedContextualSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack class def table offset |
| int bcdo = in.readTTFUShort(); |
| // read input class def table offset |
| int icdo = in.readTTFUShort(); |
| // read lookahead class def table offset |
| int lcdo = in.readTTFUShort(); |
| // read class set count |
| int ngc = in.readTTFUShort(); |
| // read class set offsets |
| int[] csoa = new int [ ngc ]; |
| for ( int i = 0; i < ngc; i++ ) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)" ); |
| log.debug(tableTag + " chained contextual substitution coverage table offset: " + co ); |
| log.debug(tableTag + " chained contextual substitution class set count: " + ngc ); |
| for ( int i = 0; i < ngc; i++ ) { |
| log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " chained contextual substitution coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read backtrack class definition table |
| GlyphClassTable bcdt; |
| if ( bcdo > 0 ) { |
| bcdt = readClassDefTable ( in, tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo ); |
| } else { |
| bcdt = null; |
| } |
| // read input class definition table |
| GlyphClassTable icdt; |
| if ( icdo > 0 ) { |
| icdt = readClassDefTable ( in, tableTag + " contextual substitution input class definition", subtableOffset + icdo ); |
| } else { |
| icdt = null; |
| } |
| // read lookahead class definition table |
| GlyphClassTable lcdt; |
| if ( lcdo > 0 ) { |
| lcdt = readClassDefTable ( in, tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo ); |
| } else { |
| lcdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for ( int i = 0; i < ngc; i++ ) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if ( cso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + cso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| int ro = roa [ j ]; |
| GlyphTable.ChainedClassSequenceRule r; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + cso + ro ); |
| // read backtrack glyph class count |
| int nbc = in.readTTFUShort(); |
| // read backtrack glyph classes |
| int[] backtrackClasses = new int [ nbc ]; |
| for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) { |
| backtrackClasses [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph class count |
| int nic = in.readTTFUShort(); |
| // read input glyph classes |
| int[] classes = new int [ nic - 1 ]; |
| for ( int k = 0, nk = classes.length; k < nk; k++ ) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph class count |
| int nlc = in.readTTFUShort(); |
| // read lookahead glyph classes |
| int[] lookaheadClasses = new int [ nlc ]; |
| for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) { |
| lookaheadClasses [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( icdt ); |
| seEntries.add ( bcdt ); |
| seEntries.add ( lcdt ); |
| seEntries.add ( Integer.valueOf ( ngc ) ); |
| seEntries.add ( rsa ); |
| } |
| |
| private void readChainedContextualSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for ( int i = 0; i < nbg; i++ ) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read input glyph coverage offsets |
| int[] igcoa = new int [ nig ]; |
| for ( int i = 0; i < nig; i++ ) { |
| igcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for ( int i = 0; i < nlg; i++ ) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read substitution lookup count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)" ); |
| log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg ); |
| for ( int i = 0; i < nbg; i++ ) { |
| log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual substitution input glyph count: " + nig ); |
| for ( int i = 0; i < nig; i++ ) { |
| log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg ); |
| for ( int i = 0; i < nlg; i++ ) { |
| log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual substitution lookup count: " + nl ); |
| } |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for ( int i = 0; i < nbg; i++ ) { |
| int bgco = bgcoa [ i ]; |
| GlyphCoverageTable bgct; |
| if ( bgco > 0 ) { |
| bgct = readCoverageTable ( in, tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco ); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read input coverage tables |
| GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; |
| for ( int i = 0; i < nig; i++ ) { |
| int igco = igcoa [ i ]; |
| GlyphCoverageTable igct; |
| if ( igco > 0 ) { |
| igct = readCoverageTable ( in, tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco ); |
| } else { |
| igct = null; |
| } |
| igca[i] = igct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for ( int i = 0; i < nlg; i++ ) { |
| int lgco = lgcoa [ i ]; |
| GlyphCoverageTable lgct; |
| if ( lgco > 0 ) { |
| lgct = readCoverageTable ( in, tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco ); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " chained contextual substitution lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca ); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} ); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert ( igca != null ) && ( igca.length > 0 ); |
| seMapping = igca[0]; |
| seEntries.add ( rsa ); |
| } |
| |
| private int readChainedContextualSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readChainedContextualSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readChainedContextualSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 3 ) { |
| readChainedContextualSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported chained contextual substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readExtensionSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read extension lookup type |
| int lt = in.readTTFUShort(); |
| // read extension offset |
| long eo = in.readTTFULong(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " extension substitution subtable format: " + subtableFormat ); |
| log.debug(tableTag + " extension substitution lookup type: " + lt ); |
| log.debug(tableTag + " extension substitution lookup table offset: " + eo ); |
| } |
| // read referenced subtable from extended offset |
| readGSUBSubtable ( in, lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo ); |
| } |
| |
| private int readExtensionSubTable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readExtensionSubTableFormat1 ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported extension substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readReverseChainedSingleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GSUB"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for ( int i = 0; i < nbg; i++ ) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for ( int i = 0; i < nlg; i++ ) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read substitution (output) glyph count |
| int ng = in.readTTFUShort(); |
| // read substitution (output) glyphs |
| int[] glyphs = new int [ ng ]; |
| for ( int i = 0, n = ng; i < n; i++ ) { |
| glyphs [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat ); |
| log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co ); |
| log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg ); |
| for ( int i = 0; i < nbg; i++ ) { |
| log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] ); |
| } |
| log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg ); |
| for ( int i = 0; i < nlg; i++ ) { |
| log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] ); |
| } |
| log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs) ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution coverage", subtableOffset + co ); |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for ( int i = 0; i < nbg; i++ ) { |
| int bgco = bgcoa[i]; |
| GlyphCoverageTable bgct; |
| if ( bgco > 0 ) { |
| bgct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco ); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for ( int i = 0; i < nlg; i++ ) { |
| int lgco = lgcoa[i]; |
| GlyphCoverageTable lgct; |
| if ( lgco > 0 ) { |
| lgct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco ); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( bgca ); |
| seEntries.add ( lgca ); |
| seEntries.add ( glyphs ); |
| } |
| |
| private int readReverseChainedSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read substitution subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readReverseChainedSingleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported reverse chained single substitution subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readGSUBSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| int subtableFormat = -1; |
| switch ( lookupType ) { |
| case GSUBLookupType.SINGLE: |
| subtableFormat = readSingleSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.MULTIPLE: |
| subtableFormat = readMultipleSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.ALTERNATE: |
| subtableFormat = readAlternateSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.LIGATURE: |
| subtableFormat = readLigatureSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.CONTEXTUAL: |
| subtableFormat = readContextualSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.CHAINED_CONTEXTUAL: |
| subtableFormat = readChainedContextualSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.REVERSE_CHAINED_SINGLE: |
| subtableFormat = readReverseChainedSingleSubTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GSUBLookupType.EXTENSION: |
| subtableFormat = readExtensionSubTable ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset ); |
| break; |
| default: |
| break; |
| } |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat ); |
| resetATSubState(); |
| } |
| |
| private GlyphPositioningTable.DeviceTable readPosDeviceTable(FontFileReader in, long subtableOffset, long deviceTableOffset) throws IOException { |
| long cp = in.getCurrentPos(); |
| in.seekSet(subtableOffset + deviceTableOffset); |
| // read start size |
| int ss = in.readTTFUShort(); |
| // read end size |
| int es = in.readTTFUShort(); |
| // read delta format |
| int df = in.readTTFUShort(); |
| int s1, m1, dm, dd, s2; |
| if ( df == 1 ) { |
| s1 = 14; m1 = 0x3; dm = 1; dd = 4; s2 = 2; |
| } else if ( df == 2 ) { |
| s1 = 12; m1 = 0xF; dm = 7; dd = 16; s2 = 4; |
| } else if ( df == 3 ) { |
| s1 = 8; m1 = 0xFF; dm = 127; dd = 256; s2 = 8; |
| } else { |
| log.debug ( "unsupported device table delta format: " + df + ", ignoring device table" ); |
| return null; |
| } |
| // read deltas |
| int n = ( es - ss ) + 1; |
| if ( n < 0 ) { |
| log.debug ( "invalid device table delta count: " + n + ", ignoring device table" ); |
| return null; |
| } |
| int[] da = new int [ n ]; |
| for ( int i = 0; ( i < n ) && ( s2 > 0 );) { |
| int p = in.readTTFUShort(); |
| for ( int j = 0, k = 16 / s2; j < k; j++ ) { |
| int d = ( p >> s1 ) & m1; |
| if ( d > dm ) { |
| d -= dd; |
| } |
| if ( i < n ) { |
| da [ i++ ] = d; |
| } else { |
| break; |
| } |
| p <<= s2; |
| } |
| } |
| in.seekSet(cp); |
| return new GlyphPositioningTable.DeviceTable ( ss, es, da ); |
| } |
| |
| private GlyphPositioningTable.Value readPosValue(FontFileReader in, long subtableOffset, int valueFormat) throws IOException { |
| // XPlacement |
| int xp; |
| if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) { |
| xp = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| } else { |
| xp = 0; |
| } |
| // YPlacement |
| int yp; |
| if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT ) != 0 ) { |
| yp = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| } else { |
| yp = 0; |
| } |
| // XAdvance |
| int xa; |
| if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE ) != 0 ) { |
| xa = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| } else { |
| xa = 0; |
| } |
| // YAdvance |
| int ya; |
| if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE ) != 0 ) { |
| ya = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| } else { |
| ya = 0; |
| } |
| // XPlaDevice |
| GlyphPositioningTable.DeviceTable xpd; |
| if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE ) != 0 ) { |
| int xpdo = in.readTTFUShort(); |
| xpd = readPosDeviceTable ( in, subtableOffset, xpdo ); |
| } else { |
| xpd = null; |
| } |
| // YPlaDevice |
| GlyphPositioningTable.DeviceTable ypd; |
| if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE ) != 0 ) { |
| int ypdo = in.readTTFUShort(); |
| ypd = readPosDeviceTable ( in, subtableOffset, ypdo ); |
| } else { |
| ypd = null; |
| } |
| // XAdvDevice |
| GlyphPositioningTable.DeviceTable xad; |
| if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE ) != 0 ) { |
| int xado = in.readTTFUShort(); |
| xad = readPosDeviceTable ( in, subtableOffset, xado ); |
| } else { |
| xad = null; |
| } |
| // YAdvDevice |
| GlyphPositioningTable.DeviceTable yad; |
| if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE ) != 0 ) { |
| int yado = in.readTTFUShort(); |
| yad = readPosDeviceTable ( in, subtableOffset, yado ); |
| } else { |
| yad = null; |
| } |
| return new GlyphPositioningTable.Value ( xp, yp, xa, ya, xpd, ypd, xad, yad ); |
| } |
| |
| private void readSinglePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format |
| int vf = in.readTTFUShort(); |
| // read value |
| GlyphPositioningTable.Value v = readPosValue ( in, subtableOffset, vf ); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)" ); |
| log.debug(tableTag + " single positioning coverage table offset: " + co ); |
| log.debug(tableTag + " single positioning value: " + v ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " single positioning coverage", subtableOffset + co ); |
| // store results |
| seMapping = ct; |
| seEntries.add ( v ); |
| } |
| |
| private void readSinglePosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format |
| int vf = in.readTTFUShort(); |
| // read value count |
| int nv = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)" ); |
| log.debug(tableTag + " single positioning coverage table offset: " + co ); |
| log.debug(tableTag + " single positioning value count: " + nv ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " single positioning coverage", subtableOffset + co ); |
| // read positioning values |
| GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv]; |
| for ( int i = 0, n = nv; i < n; i++ ) { |
| GlyphPositioningTable.Value pv = readPosValue ( in, subtableOffset, vf ); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " single positioning value[" + i + "]: " + pv ); |
| } |
| pva[i] = pv; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( pva ); |
| } |
| |
| private int readSinglePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positionining subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readSinglePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readSinglePosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported single positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private GlyphPositioningTable.PairValues readPosPairValues(FontFileReader in, long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException { |
| // read glyph (if present) |
| int glyph; |
| if ( hasGlyph ) { |
| glyph = in.readTTFUShort(); |
| } else { |
| glyph = 0; |
| } |
| // read first value (if present) |
| GlyphPositioningTable.Value v1; |
| if ( vf1 != 0 ) { |
| v1 = readPosValue ( in, subtableOffset, vf1 ); |
| } else { |
| v1 = null; |
| } |
| // read second value (if present) |
| GlyphPositioningTable.Value v2; |
| if ( vf2 != 0 ) { |
| v2 = readPosValue ( in, subtableOffset, vf2 ); |
| } else { |
| v2 = null; |
| } |
| return new GlyphPositioningTable.PairValues ( glyph, v1, v2 ); |
| } |
| |
| private GlyphPositioningTable.PairValues[] readPosPairSetTable(FontFileReader in, long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException { |
| String tableTag = "GPOS"; |
| long cp = in.getCurrentPos(); |
| in.seekSet(subtableOffset + pairSetTableOffset); |
| // read pair values count |
| int npv = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table offset: " + pairSetTableOffset ); |
| log.debug(tableTag + " pair set table values count: " + npv ); |
| } |
| // read pair values |
| GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ]; |
| for ( int i = 0, n = npv; i < n; i++ ) { |
| GlyphPositioningTable.PairValues pv = readPosPairValues ( in, subtableOffset, true, vf1, vf2 ); |
| pva [ i ] = pv; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table value[" + i + "]: " + pv); |
| } |
| } |
| in.seekSet(cp); |
| return pva; |
| } |
| |
| private void readPairPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format for first glyph |
| int vf1 = in.readTTFUShort(); |
| // read value format for second glyph |
| int vf2 = in.readTTFUShort(); |
| // read number (count) of pair sets |
| int nps = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)" ); |
| log.debug(tableTag + " pair positioning coverage table offset: " + co ); |
| log.debug(tableTag + " pair positioning value format #1: " + vf1 ); |
| log.debug(tableTag + " pair positioning value format #2: " + vf2 ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " pair positioning coverage", subtableOffset + co ); |
| // read pair value matrix |
| GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][]; |
| for ( int i = 0, n = nps; i < n; i++ ) { |
| // read pair set offset |
| int pso = in.readTTFUShort(); |
| // read pair set table at offset |
| pvm [ i ] = readPosPairSetTable ( in, subtableOffset, pso, vf1, vf2 ); |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( pvm ); |
| } |
| |
| private void readPairPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read value format for first glyph |
| int vf1 = in.readTTFUShort(); |
| // read value format for second glyph |
| int vf2 = in.readTTFUShort(); |
| // read class def 1 offset |
| int cd1o = in.readTTFUShort(); |
| // read class def 2 offset |
| int cd2o = in.readTTFUShort(); |
| // read number (count) of classes in class def 1 table |
| int nc1 = in.readTTFUShort(); |
| // read number (count) of classes in class def 2 table |
| int nc2 = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)" ); |
| log.debug(tableTag + " pair positioning coverage table offset: " + co ); |
| log.debug(tableTag + " pair positioning value format #1: " + vf1 ); |
| log.debug(tableTag + " pair positioning value format #2: " + vf2 ); |
| log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o ); |
| log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o ); |
| log.debug(tableTag + " pair positioning class #1 count: " + nc1 ); |
| log.debug(tableTag + " pair positioning class #2 count: " + nc2 ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " pair positioning coverage", subtableOffset + co ); |
| // read class definition table #1 |
| GlyphClassTable cdt1 = readClassDefTable ( in, tableTag + " pair positioning class definition #1", subtableOffset + cd1o ); |
| // read class definition table #2 |
| GlyphClassTable cdt2 = readClassDefTable ( in, tableTag + " pair positioning class definition #2", subtableOffset + cd2o ); |
| // read pair value matrix |
| GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ]; |
| for ( int i = 0; i < nc1; i++ ) { |
| for ( int j = 0; j < nc2; j++ ) { |
| GlyphPositioningTable.PairValues pv = readPosPairValues ( in, subtableOffset, false, vf1, vf2 ); |
| pvm [ i ] [ j ] = pv; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv); |
| } |
| } |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( cdt1 ); |
| seEntries.add ( cdt2 ); |
| seEntries.add ( Integer.valueOf ( nc1 ) ); |
| seEntries.add ( Integer.valueOf ( nc2 ) ); |
| seEntries.add ( pvm ); |
| } |
| |
| private int readPairPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readPairPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readPairPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported pair positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private GlyphPositioningTable.Anchor readPosAnchor(FontFileReader in, long anchorTableOffset) throws IOException { |
| GlyphPositioningTable.Anchor a; |
| long cp = in.getCurrentPos(); |
| in.seekSet(anchorTableOffset); |
| // read anchor table format |
| int af = in.readTTFUShort(); |
| if ( af == 1 ) { |
| // read x coordinate |
| int x = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| // read y coordinate |
| int y = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| a = new GlyphPositioningTable.Anchor ( x, y ); |
| } else if ( af == 2 ) { |
| // read x coordinate |
| int x = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| // read y coordinate |
| int y = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| // read anchor point index |
| int ap = in.readTTFUShort(); |
| a = new GlyphPositioningTable.Anchor ( x, y, ap ); |
| } else if ( af == 3 ) { |
| // read x coordinate |
| int x = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| // read y coordinate |
| int y = convertTTFUnit2PDFUnit ( in.readTTFShort() ); |
| // read x device table offset |
| int xdo = in.readTTFUShort(); |
| // read y device table offset |
| int ydo = in.readTTFUShort(); |
| // read x device table (if present) |
| GlyphPositioningTable.DeviceTable xd; |
| if ( xdo != 0 ) { |
| xd = readPosDeviceTable ( in, cp, xdo ); |
| } else { |
| xd = null; |
| } |
| // read y device table (if present) |
| GlyphPositioningTable.DeviceTable yd; |
| if ( ydo != 0 ) { |
| yd = readPosDeviceTable ( in, cp, ydo ); |
| } else { |
| yd = null; |
| } |
| a = new GlyphPositioningTable.Anchor ( x, y, xd, yd ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported positioning anchor format: " + af ); |
| } |
| in.seekSet(cp); |
| return a; |
| } |
| |
| private void readCursivePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read entry/exit count |
| int ec = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat ); |
| log.debug(tableTag + " cursive positioning coverage table offset: " + co ); |
| log.debug(tableTag + " cursive positioning entry/exit count: " + ec ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " cursive positioning coverage", subtableOffset + co ); |
| // read entry/exit records |
| GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ]; |
| for ( int i = 0, n = ec; i < n; i++ ) { |
| // read entry anchor offset |
| int eno = in.readTTFUShort(); |
| // read exit anchor offset |
| int exo = in.readTTFUShort(); |
| // read entry anchor |
| GlyphPositioningTable.Anchor ena; |
| if ( eno > 0 ) { |
| ena = readPosAnchor ( in, subtableOffset + eno ); |
| } else { |
| ena = null; |
| } |
| // read exit anchor |
| GlyphPositioningTable.Anchor exa; |
| if ( exo > 0 ) { |
| exa = readPosAnchor ( in, subtableOffset + exo ); |
| } else { |
| exa = null; |
| } |
| aa [ ( i * 2 ) + 0 ] = ena; |
| aa [ ( i * 2 ) + 1 ] = exa; |
| if (log.isDebugEnabled()) { |
| if ( ena != null ) { |
| log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena ); |
| } |
| if ( exa != null ) { |
| log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa ); |
| } |
| } |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( aa ); |
| } |
| |
| private int readCursivePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readCursivePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported cursive positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readMarkToBasePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read mark coverage offset |
| int mco = in.readTTFUShort(); |
| // read base coverage offset |
| int bco = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark array offset |
| int mao = in.readTTFUShort(); |
| // read base array offset |
| int bao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat ); |
| log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco ); |
| log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco ); |
| log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc ); |
| log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao ); |
| log.debug(tableTag + " mark-to-base positioning base array offset: " + bao ); |
| } |
| // read mark coverage table |
| GlyphCoverageTable mct = readCoverageTable ( in, tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco ); |
| // read base coverage table |
| GlyphCoverageTable bct = readCoverageTable ( in, tableTag + " mark-to-base positioning base coverage", subtableOffset + bco ); |
| // read mark anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + mao); |
| // read mark count |
| int nm = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning mark count: " + nm ); |
| } |
| // read mark anchor array, where i:{0...markCount} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; |
| for ( int i = 0; i < nm; i++ ) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + mao + ao ); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if ( a != null ) { |
| ma = new GlyphPositioningTable.MarkAnchor ( mc, a ); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma); |
| } |
| |
| } |
| // read base anchor matrix |
| // seek to base array |
| in.seekSet(subtableOffset + bao); |
| // read base count |
| int nb = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning base count: " + nb ); |
| } |
| // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ]; |
| for ( int i = 0; i < nb; i++ ) { |
| for ( int j = 0; j < nmc; j++ ) { |
| // read base anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + bao + ao ); |
| } else { |
| a = null; |
| } |
| bam [ i ] [ j ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a); |
| } |
| } |
| } |
| // store results |
| seMapping = mct; |
| seEntries.add ( bct ); |
| seEntries.add ( Integer.valueOf ( nmc ) ); |
| seEntries.add ( maa ); |
| seEntries.add ( bam ); |
| } |
| |
| private int readMarkToBasePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readMarkToBasePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-base positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readMarkToLigaturePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read mark coverage offset |
| int mco = in.readTTFUShort(); |
| // read ligature coverage offset |
| int lco = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark array offset |
| int mao = in.readTTFUShort(); |
| // read ligature array offset |
| int lao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat ); |
| log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco ); |
| log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco ); |
| log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc ); |
| log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao ); |
| log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao ); |
| } |
| // read mark coverage table |
| GlyphCoverageTable mct = readCoverageTable ( in, tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco ); |
| // read ligature coverage table |
| GlyphCoverageTable lct = readCoverageTable ( in, tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco ); |
| // read mark anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + mao); |
| // read mark count |
| int nm = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm ); |
| } |
| // read mark anchor array, where i:{0...markCount} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ]; |
| for ( int i = 0; i < nm; i++ ) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + mao + ao ); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if ( a != null ) { |
| ma = new GlyphPositioningTable.MarkAnchor ( mc, a ); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma); |
| } |
| } |
| // read ligature anchor matrix |
| // seek to ligature array |
| in.seekSet(subtableOffset + lao); |
| // read ligature count |
| int nl = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl ); |
| } |
| // read ligature attach table offsets |
| int[] laoa = new int [ nl ]; |
| for ( int i = 0; i < nl; i++ ) { |
| laoa [ i ] = in.readTTFUShort(); |
| } |
| // iterate over ligature attach tables, recording maximum component count |
| int mxc = 0; |
| for ( int i = 0; i < nl; i++ ) { |
| int lato = laoa [ i ]; |
| in.seekSet ( subtableOffset + lao + lato ); |
| // read component count |
| int cc = in.readTTFUShort(); |
| if ( cc > mxc ) { |
| mxc = cc; |
| } |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc ); |
| } |
| // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][]; |
| for ( int i = 0; i < nl; i++ ) { |
| int lato = laoa [ i ]; |
| // seek to ligature attach table for ligature[i] |
| in.seekSet ( subtableOffset + lao + lato ); |
| // read component count |
| int cc = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ]; |
| for ( int j = 0; j < cc; j++ ) { |
| for ( int k = 0; k < nmc; k++ ) { |
| // read ligature anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + lao + lato + ao ); |
| } else { |
| a = null; |
| } |
| lcm [ j ] [ k ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a); |
| } |
| } |
| } |
| lam [ i ] = lcm; |
| } |
| // store results |
| seMapping = mct; |
| seEntries.add ( lct ); |
| seEntries.add ( Integer.valueOf ( nmc ) ); |
| seEntries.add ( Integer.valueOf ( mxc ) ); |
| seEntries.add ( maa ); |
| seEntries.add ( lam ); |
| } |
| |
| private int readMarkToLigaturePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readMarkToLigaturePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-ligature positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readMarkToMarkPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read mark #1 coverage offset |
| int m1co = in.readTTFUShort(); |
| // read mark #2 coverage offset |
| int m2co = in.readTTFUShort(); |
| // read mark class count |
| int nmc = in.readTTFUShort(); |
| // read mark #1 array offset |
| int m1ao = in.readTTFUShort(); |
| // read mark #2 array offset |
| int m2ao = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat ); |
| log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co ); |
| log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co ); |
| log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc ); |
| log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao ); |
| log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao ); |
| } |
| // read mark #1 coverage table |
| GlyphCoverageTable mct1 = readCoverageTable ( in, tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co ); |
| // read mark #2 coverage table |
| GlyphCoverageTable mct2 = readCoverageTable ( in, tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co ); |
| // read mark #1 anchor array |
| // seek to mark array |
| in.seekSet(subtableOffset + m1ao); |
| // read mark count |
| int nm1 = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1 ); |
| } |
| // read mark anchor array, where i:{0...mark1Count} |
| GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ]; |
| for ( int i = 0; i < nm1; i++ ) { |
| // read mark class |
| int mc = in.readTTFUShort(); |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + m1ao + ao ); |
| } else { |
| a = null; |
| } |
| GlyphPositioningTable.MarkAnchor ma; |
| if ( a != null ) { |
| ma = new GlyphPositioningTable.MarkAnchor ( mc, a ); |
| } else { |
| ma = null; |
| } |
| maa [ i ] = ma; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma); |
| } |
| } |
| // read mark #2 anchor matrix |
| // seek to mark #2 array |
| in.seekSet(subtableOffset + m2ao); |
| // read mark #2 count |
| int nm2 = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2 ); |
| } |
| // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1} |
| GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ]; |
| for ( int i = 0; i < nm2; i++ ) { |
| for ( int j = 0; j < nmc; j++ ) { |
| // read mark anchor offset |
| int ao = in.readTTFUShort(); |
| GlyphPositioningTable.Anchor a; |
| if ( ao > 0 ) { |
| a = readPosAnchor ( in, subtableOffset + m2ao + ao ); |
| } else { |
| a = null; |
| } |
| mam [ i ] [ j ] = a; |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a); |
| } |
| } |
| } |
| // store results |
| seMapping = mct1; |
| seEntries.add ( mct2 ); |
| seEntries.add ( Integer.valueOf ( nmc ) ); |
| seEntries.add ( maa ); |
| seEntries.add ( mam ); |
| } |
| |
| private int readMarkToMarkPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readMarkToMarkPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-mark positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readContextualPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for ( int i = 0; i < nrs; i++ ) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)" ); |
| log.debug(tableTag + " contextual positioning coverage table offset: " + co ); |
| log.debug(tableTag + " contextual positioning rule set count: " + nrs ); |
| for ( int i = 0; i < nrs; i++ ) { |
| log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " contextual positioning coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for ( int i = 0; i < nrs; i++ ) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if ( rso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + rso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| GlyphTable.GlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + rso + ro ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ ng - 1 ]; |
| for ( int k = 0, nk = glyphs.length; k < nk; k++ ) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( rsa ); |
| } |
| |
| private void readContextualPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read class def table offset |
| int cdo = in.readTTFUShort(); |
| // read class rule set count |
| int ngc = in.readTTFUShort(); |
| // read class rule set offsets |
| int[] csoa = new int [ ngc ]; |
| for ( int i = 0; i < ngc; i++ ) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)" ); |
| log.debug(tableTag + " contextual positioning coverage table offset: " + co ); |
| log.debug(tableTag + " contextual positioning class set count: " + ngc ); |
| for ( int i = 0; i < ngc; i++ ) { |
| log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " contextual positioning coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read class definition table |
| GlyphClassTable cdt; |
| if ( cdo > 0 ) { |
| cdt = readClassDefTable ( in, tableTag + " contextual positioning class definition", subtableOffset + cdo ); |
| } else { |
| cdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for ( int i = 0; i < ngc; i++ ) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if ( cso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + cso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| int ro = roa [ j ]; |
| GlyphTable.ClassSequenceRule r; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + cso + ro ); |
| // read glyph count |
| int ng = in.readTTFUShort(); |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read classes |
| int[] classes = new int [ ng - 1 ]; |
| for ( int k = 0, nk = classes.length; k < nk; k++ ) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( cdt ); |
| seEntries.add ( Integer.valueOf ( ngc ) ); |
| seEntries.add ( rsa ); |
| } |
| |
| private void readContextualPosTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read glyph (input sequence length) count |
| int ng = in.readTTFUShort(); |
| // read positioning lookup count |
| int nl = in.readTTFUShort(); |
| // read glyph coverage offsets, one per glyph input sequence length count |
| int[] gcoa = new int [ ng ]; |
| for ( int i = 0; i < ng; i++ ) { |
| gcoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)" ); |
| log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng ); |
| log.debug(tableTag + " contextual positioning lookup count: " + nl ); |
| for ( int i = 0; i < ng; i++ ) { |
| log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i] ); |
| } |
| } |
| // read coverage tables |
| GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ]; |
| for ( int i = 0; i < ng; i++ ) { |
| int gco = gcoa [ i ]; |
| GlyphCoverageTable gct; |
| if ( gco > 0 ) { |
| gct = readCoverageTable ( in, tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i] ); |
| } else { |
| gct = null; |
| } |
| gca [ i ] = gct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca ); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} ); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert ( gca != null ) && ( gca.length > 0 ); |
| seMapping = gca[0]; |
| seEntries.add ( rsa ); |
| } |
| |
| private int readContextualPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readContextualPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readContextualPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 3 ) { |
| readContextualPosTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported contextual positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readChainedContextualPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read rule set count |
| int nrs = in.readTTFUShort(); |
| // read rule set offsets |
| int[] rsoa = new int [ nrs ]; |
| for ( int i = 0; i < nrs; i++ ) { |
| rsoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)" ); |
| log.debug(tableTag + " chained contextual positioning coverage table offset: " + co ); |
| log.debug(tableTag + " chained contextual positioning rule set count: " + nrs ); |
| for ( int i = 0; i < nrs; i++ ) { |
| log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " chained contextual positioning coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ]; |
| String header = null; |
| for ( int i = 0; i < nrs; i++ ) { |
| GlyphTable.RuleSet rs; |
| int rso = rsoa [ i ]; |
| if ( rso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + rso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| GlyphTable.ChainedGlyphSequenceRule r; |
| int ro = roa [ j ]; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + rso + ro ); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyphs |
| int[] backtrackGlyphs = new int [ nbg ]; |
| for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) { |
| backtrackGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read glyphs |
| int[] glyphs = new int [ nig - 1 ]; |
| for ( int k = 0, nk = glyphs.length; k < nk; k++ ) { |
| glyphs [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read lookahead glyphs |
| int[] lookaheadGlyphs = new int [ nlg ]; |
| for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) { |
| lookaheadGlyphs [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( rsa ); |
| } |
| |
| private void readChainedContextualPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read backtrack class def table offset |
| int bcdo = in.readTTFUShort(); |
| // read input class def table offset |
| int icdo = in.readTTFUShort(); |
| // read lookahead class def table offset |
| int lcdo = in.readTTFUShort(); |
| // read class set count |
| int ngc = in.readTTFUShort(); |
| // read class set offsets |
| int[] csoa = new int [ ngc ]; |
| for ( int i = 0; i < ngc; i++ ) { |
| csoa [ i ] = in.readTTFUShort(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)" ); |
| log.debug(tableTag + " chained contextual positioning coverage table offset: " + co ); |
| log.debug(tableTag + " chained contextual positioning class set count: " + ngc ); |
| for ( int i = 0; i < ngc; i++ ) { |
| log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct; |
| if ( co > 0 ) { |
| ct = readCoverageTable ( in, tableTag + " chained contextual positioning coverage", subtableOffset + co ); |
| } else { |
| ct = null; |
| } |
| // read backtrack class definition table |
| GlyphClassTable bcdt; |
| if ( bcdo > 0 ) { |
| bcdt = readClassDefTable ( in, tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo ); |
| } else { |
| bcdt = null; |
| } |
| // read input class definition table |
| GlyphClassTable icdt; |
| if ( icdo > 0 ) { |
| icdt = readClassDefTable ( in, tableTag + " contextual positioning input class definition", subtableOffset + icdo ); |
| } else { |
| icdt = null; |
| } |
| // read lookahead class definition table |
| GlyphClassTable lcdt; |
| if ( lcdo > 0 ) { |
| lcdt = readClassDefTable ( in, tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo ); |
| } else { |
| lcdt = null; |
| } |
| // read rule sets |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ]; |
| String header = null; |
| for ( int i = 0; i < ngc; i++ ) { |
| int cso = csoa [ i ]; |
| GlyphTable.RuleSet rs; |
| if ( cso > 0 ) { |
| // seek to rule set [ i ] |
| in.seekSet ( subtableOffset + cso ); |
| // read rule count |
| int nr = in.readTTFUShort(); |
| // read rule offsets |
| int[] roa = new int [ nr ]; |
| GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ]; |
| for ( int j = 0; j < nr; j++ ) { |
| roa [ j ] = in.readTTFUShort(); |
| } |
| // read glyph sequence rules |
| for ( int j = 0; j < nr; j++ ) { |
| GlyphTable.ChainedClassSequenceRule r; |
| int ro = roa [ j ]; |
| if ( ro > 0 ) { |
| // seek to rule [ j ] |
| in.seekSet ( subtableOffset + cso + ro ); |
| // read backtrack glyph class count |
| int nbc = in.readTTFUShort(); |
| // read backtrack glyph classes |
| int[] backtrackClasses = new int [ nbc ]; |
| for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) { |
| backtrackClasses [ k ] = in.readTTFUShort(); |
| } |
| // read input glyph class count |
| int nic = in.readTTFUShort(); |
| // read input glyph classes |
| int[] classes = new int [ nic - 1 ]; |
| for ( int k = 0, nk = classes.length; k < nk; k++ ) { |
| classes [ k ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph class count |
| int nlc = in.readTTFUShort(); |
| // read lookahead glyph classes |
| int[] lookaheadClasses = new int [ nlc ]; |
| for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) { |
| lookaheadClasses [ k ] = in.readTTFUShort(); |
| } |
| // read rule lookup count |
| int nl = in.readTTFUShort(); |
| // read rule lookups |
| if (log.isDebugEnabled()) { |
| header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses ); |
| } else { |
| r = null; |
| } |
| ra [ j ] = r; |
| } |
| rs = new GlyphTable.HomogeneousRuleSet ( ra ); |
| } else { |
| rs = null; |
| } |
| rsa [ i ] = rs; |
| } |
| // store results |
| seMapping = ct; |
| seEntries.add ( icdt ); |
| seEntries.add ( bcdt ); |
| seEntries.add ( lcdt ); |
| seEntries.add ( Integer.valueOf ( ngc ) ); |
| seEntries.add ( rsa ); |
| } |
| |
| private void readChainedContextualPosTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read backtrack glyph count |
| int nbg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] bgcoa = new int [ nbg ]; |
| for ( int i = 0; i < nbg; i++ ) { |
| bgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read input glyph count |
| int nig = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] igcoa = new int [ nig ]; |
| for ( int i = 0; i < nig; i++ ) { |
| igcoa [ i ] = in.readTTFUShort(); |
| } |
| // read lookahead glyph count |
| int nlg = in.readTTFUShort(); |
| // read backtrack glyph coverage offsets |
| int[] lgcoa = new int [ nlg ]; |
| for ( int i = 0; i < nlg; i++ ) { |
| lgcoa [ i ] = in.readTTFUShort(); |
| } |
| // read positioning lookup count |
| int nl = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)" ); |
| log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg ); |
| for ( int i = 0; i < nbg; i++ ) { |
| log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual positioning input glyph count: " + nig ); |
| for ( int i = 0; i < nig; i++ ) { |
| log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg ); |
| for ( int i = 0; i < nlg; i++ ) { |
| log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i] ); |
| } |
| log.debug(tableTag + " chained contextual positioning lookup count: " + nl ); |
| } |
| // read backtrack coverage tables |
| GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg]; |
| for ( int i = 0; i < nbg; i++ ) { |
| int bgco = bgcoa [ i ]; |
| GlyphCoverageTable bgct; |
| if ( bgco > 0 ) { |
| bgct = readCoverageTable ( in, tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco ); |
| } else { |
| bgct = null; |
| } |
| bgca[i] = bgct; |
| } |
| // read input coverage tables |
| GlyphCoverageTable[] igca = new GlyphCoverageTable[nig]; |
| for ( int i = 0; i < nig; i++ ) { |
| int igco = igcoa [ i ]; |
| GlyphCoverageTable igct; |
| if ( igco > 0 ) { |
| igct = readCoverageTable ( in, tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco ); |
| } else { |
| igct = null; |
| } |
| igca[i] = igct; |
| } |
| // read lookahead coverage tables |
| GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg]; |
| for ( int i = 0; i < nlg; i++ ) { |
| int lgco = lgcoa [ i ]; |
| GlyphCoverageTable lgct; |
| if ( lgco > 0 ) { |
| lgct = readCoverageTable ( in, tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco ); |
| } else { |
| lgct = null; |
| } |
| lgca[i] = lgct; |
| } |
| // read rule lookups |
| String header = null; |
| if (log.isDebugEnabled()) { |
| header = tableTag + " chained contextual positioning lookups: "; |
| } |
| GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header ); |
| // construct rule, rule set, and rule set array |
| GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca ); |
| GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} ); |
| GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs}; |
| // store results |
| assert ( igca != null ) && ( igca.length > 0 ); |
| seMapping = igca[0]; |
| seEntries.add ( rsa ); |
| } |
| |
| private int readChainedContextualPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readChainedContextualPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 2 ) { |
| readChainedContextualPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else if ( sf == 3 ) { |
| readChainedContextualPosTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported chained contextual positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readExtensionPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException { |
| String tableTag = "GPOS"; |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read extension lookup type |
| int lt = in.readTTFUShort(); |
| // read extension offset |
| long eo = in.readTTFULong(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " extension positioning subtable format: " + subtableFormat ); |
| log.debug(tableTag + " extension positioning lookup type: " + lt ); |
| log.debug(tableTag + " extension positioning lookup table offset: " + eo ); |
| } |
| // read referenced subtable from extended offset |
| readGPOSSubtable ( in, lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo ); |
| } |
| |
| private int readExtensionPosTable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read positioning subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readExtensionPosTableFormat1 ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported extension positioning subtable format: " + sf ); |
| } |
| return sf; |
| } |
| |
| private void readGPOSSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| int subtableFormat = -1; |
| switch ( lookupType ) { |
| case GPOSLookupType.SINGLE: |
| subtableFormat = readSinglePosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.PAIR: |
| subtableFormat = readPairPosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.CURSIVE: |
| subtableFormat = readCursivePosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.MARK_TO_BASE: |
| subtableFormat = readMarkToBasePosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.MARK_TO_LIGATURE: |
| subtableFormat = readMarkToLigaturePosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.MARK_TO_MARK: |
| subtableFormat = readMarkToMarkPosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.CONTEXTUAL: |
| subtableFormat = readContextualPosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.CHAINED_CONTEXTUAL: |
| subtableFormat = readChainedContextualPosTable ( in, lookupType, lookupFlags, subtableOffset ); |
| break; |
| case GPOSLookupType.EXTENSION: |
| subtableFormat = readExtensionPosTable ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset ); |
| break; |
| default: |
| break; |
| } |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat ); |
| resetATSubState(); |
| } |
| |
| private void readLookupTable(FontFileReader in, String tableTag, int lookupSequence, long lookupTable) throws IOException { |
| boolean isGSUB = tableTag.equals ( "GSUB" ); |
| boolean isGPOS = tableTag.equals ( "GPOS" ); |
| in.seekSet(lookupTable); |
| // read lookup type |
| int lt = in.readTTFUShort(); |
| // read lookup flags |
| int lf = in.readTTFUShort(); |
| // read sub-table count |
| int ns = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| String lts; |
| if ( isGSUB ) { |
| lts = GSUBLookupType.toString ( lt ); |
| } else if ( isGPOS ) { |
| lts = GPOSLookupType.toString ( lt ); |
| } else { |
| lts = "?"; |
| } |
| log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")" ); |
| log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString ( lf ) + ")" ); |
| log.debug(tableTag + " lookup table subtable count: " + ns ); |
| } |
| // read subtable offsets |
| int[] soa = new int[ns]; |
| for ( int i = 0; i < ns; i++ ) { |
| int so = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table subtable offset: " + so ); |
| } |
| soa[i] = so; |
| } |
| // read mark filtering set |
| if ( ( lf & LookupFlag.USE_MARK_FILTERING_SET ) != 0 ) { |
| // read mark filtering set |
| int fs = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table mark filter set: " + fs ); |
| } |
| } |
| // read subtables |
| for ( int i = 0; i < ns; i++ ) { |
| int so = soa[i]; |
| if ( isGSUB ) { |
| readGSUBSubtable ( in, lt, lf, lookupSequence, i, lookupTable + so ); |
| } else if ( isGPOS ) { |
| readGPOSSubtable ( in, lt, lf, lookupSequence, i, lookupTable + so ); |
| } |
| } |
| } |
| |
| private void readLookupList(FontFileReader in, String tableTag, long lookupList) throws IOException { |
| in.seekSet(lookupList); |
| // read lookup record count |
| int nl = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup list record count: " + nl ); |
| } |
| if ( nl > 0 ) { |
| int[] loa = new int[nl]; |
| // read lookup records |
| for ( int i = 0, n = nl; i < n; i++ ) { |
| int lo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup table offset: " + lo ); |
| } |
| loa[i] = lo; |
| } |
| // read lookup tables |
| for ( int i = 0, n = nl; i < n; i++ ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " lookup index: " + i ); |
| } |
| readLookupTable ( in, tableTag, i, lookupList + loa [ i ] ); |
| } |
| } |
| } |
| |
| /** |
| * Read the common layout tables (used by GSUB and GPOS). |
| * @param in FontFileReader to read from |
| * @param scriptList offset to script list from beginning of font file |
| * @param featureList offset to feature list from beginning of font file |
| * @param lookupList offset to lookup list from beginning of font file |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readCommonLayoutTables(FontFileReader in, String tableTag, long scriptList, long featureList, long lookupList) throws IOException { |
| if ( scriptList > 0 ) { |
| readScriptList ( in, tableTag, scriptList ); |
| } |
| if ( featureList > 0 ) { |
| readFeatureList ( in, tableTag, featureList ); |
| } |
| if ( lookupList > 0 ) { |
| readLookupList ( in, tableTag, lookupList ); |
| } |
| } |
| |
| private void readGDEFClassDefTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // subtable is a bare class definition table |
| GlyphClassTable ct = readClassDefTable ( in, tableTag + " glyph class definition table", subtableOffset ); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1 ); |
| resetATSubState(); |
| } |
| |
| private void readGDEFAttachmentTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " attachment point coverage table offset: " + co ); |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " attachment point coverage", subtableOffset + co ); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1 ); |
| resetATSubState(); |
| } |
| |
| private void readGDEFLigatureCaretTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // read coverage offset |
| int co = in.readTTFUShort(); |
| // read ligature glyph count |
| int nl = in.readTTFUShort(); |
| // read ligature glyph table offsets |
| int[] lgto = new int [ nl ]; |
| for ( int i = 0; i < nl; i++ ) { |
| lgto [ i ] = in.readTTFUShort(); |
| } |
| |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " ligature caret coverage table offset: " + co ); |
| log.debug(tableTag + " ligature caret ligature glyph count: " + nl ); |
| for ( int i = 0; i < nl; i++ ) { |
| log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i] ); |
| } |
| } |
| // read coverage table |
| GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " ligature caret coverage", subtableOffset + co ); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1 ); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkAttachmentTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // subtable is a bare class definition table |
| GlyphClassTable ct = readClassDefTable ( in, tableTag + " glyph class definition table", subtableOffset ); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 ); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkGlyphsTableFormat1(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException { |
| initATSubState(); |
| in.seekSet(subtableOffset); |
| // skip over format (already known) |
| in.skip ( 2 ); |
| // read mark set class count |
| int nmc = in.readTTFUShort(); |
| long[] mso = new long [ nmc ]; |
| // read mark set coverage offsets |
| for ( int i = 0; i < nmc; i++ ) { |
| mso [ i ] = in.readTTFULong(); |
| } |
| // dump info if debugging |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)" ); |
| log.debug(tableTag + " mark set class count: " + nmc ); |
| for ( int i = 0; i < nmc; i++ ) { |
| log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i] ); |
| } |
| } |
| // read mark set coverage tables, one per class |
| GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc]; |
| for ( int i = 0; i < nmc; i++ ) { |
| msca[i] = readCoverageTable ( in, tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i] ); |
| } |
| // create combined class table from per-class coverage tables |
| GlyphClassTable ct = GlyphClassTable.createClassTable ( Arrays.asList ( msca ) ); |
| // store results |
| seMapping = ct; |
| // extract subtable |
| extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 ); |
| resetATSubState(); |
| } |
| |
| private void readGDEFMarkGlyphsTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException { |
| in.seekSet(subtableOffset); |
| // read mark set subtable format |
| int sf = in.readTTFUShort(); |
| if ( sf == 1 ) { |
| readGDEFMarkGlyphsTableFormat1 ( in, tableTag, lookupSequence, subtableOffset, sf ); |
| } else { |
| throw new AdvancedTypographicTableFormatException ( "unsupported mark glyph sets subtable format: " + sf ); |
| } |
| } |
| |
| /** |
| * Read the GDEF table. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGDEF(FontFileReader in) throws IOException { |
| String tableTag = "GDEF"; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph definition (GDEF) table |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag); |
| if ( gdef != null ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| seekTab(in, tableTag, 0); |
| long version = in.readTTFULong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 )); |
| } |
| // glyph class definition table offset (may be null) |
| int cdo = in.readTTFUShort(); |
| // attach point list offset (may be null) |
| int apo = in.readTTFUShort(); |
| // ligature caret list offset (may be null) |
| int lco = in.readTTFUShort(); |
| // mark attach class definition table offset (may be null) |
| int mao = in.readTTFUShort(); |
| // mark glyph sets definition table offset (may be null) |
| int mgo; |
| if ( version >= 0x00010002 ) { |
| mgo = in.readTTFUShort(); |
| } else { |
| mgo = 0; |
| } |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " glyph class definition table offset: " + cdo ); |
| log.debug(tableTag + " attachment point list offset: " + apo ); |
| log.debug(tableTag + " ligature caret list offset: " + lco ); |
| log.debug(tableTag + " mark attachment class definition table offset: " + mao ); |
| log.debug(tableTag + " mark glyph set definitions table offset: " + mgo ); |
| } |
| // initialize subtable sequence number |
| int seqno = 0; |
| // obtain offset to start of gdef table |
| long to = dirTab.getOffset(); |
| // (optionally) read glyph class definition subtable |
| if ( cdo != 0 ) { |
| readGDEFClassDefTable ( in, tableTag, seqno++, to + cdo ); |
| } |
| // (optionally) read glyph attachment point subtable |
| if ( apo != 0 ) { |
| readGDEFAttachmentTable ( in, tableTag, seqno++, to + apo ); |
| } |
| // (optionally) read ligature caret subtable |
| if ( lco != 0 ) { |
| readGDEFLigatureCaretTable ( in, tableTag, seqno++, to + lco ); |
| } |
| // (optionally) read mark attachment class subtable |
| if ( mao != 0 ) { |
| readGDEFMarkAttachmentTable ( in, tableTag, seqno++, to + mao ); |
| } |
| // (optionally) read mark glyph sets subtable |
| if ( mgo != 0 ) { |
| readGDEFMarkGlyphsTable ( in, tableTag, seqno++, to + mgo ); |
| } |
| GlyphDefinitionTable gdef; |
| if ( ( gdef = constructGDEF() ) != null ) { |
| this.gdef = gdef; |
| } |
| } |
| } |
| |
| /** |
| * Read the GSUB table. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGSUB(FontFileReader in) throws IOException { |
| String tableTag = "GSUB"; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph substitution (GSUB) table |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag); |
| if ( gpos != null ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| seekTab(in, tableTag, 0); |
| int version = in.readTTFLong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 )); |
| } |
| int slo = in.readTTFUShort(); |
| int flo = in.readTTFUShort(); |
| int llo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list offset: " + slo ); |
| log.debug(tableTag + " feature list offset: " + flo ); |
| log.debug(tableTag + " lookup list offset: " + llo ); |
| } |
| long to = dirTab.getOffset(); |
| readCommonLayoutTables ( in, tableTag, to + slo, to + flo, to + llo ); |
| GlyphSubstitutionTable gsub; |
| if ( ( gsub = constructGSUB() ) != null ) { |
| this.gsub = gsub; |
| } |
| } |
| } |
| |
| /** |
| * Read the GPOS table. |
| * @param in FontFileReader to read from |
| * @throws IOException In case of a I/O problem |
| */ |
| private void readGPOS(FontFileReader in) throws IOException { |
| String tableTag = "GPOS"; |
| // Initialize temporary state |
| initATState(); |
| // Read glyph positioning (GPOS) table |
| TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag); |
| if ( gpos != null ) { |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + ": ignoring duplicate table"); |
| } |
| } else if (dirTab != null) { |
| seekTab(in, tableTag, 0); |
| int version = in.readTTFLong(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 )); |
| } |
| int slo = in.readTTFUShort(); |
| int flo = in.readTTFUShort(); |
| int llo = in.readTTFUShort(); |
| if (log.isDebugEnabled()) { |
| log.debug(tableTag + " script list offset: " + slo ); |
| log.debug(tableTag + " feature list offset: " + flo ); |
| log.debug(tableTag + " lookup list offset: " + llo ); |
| } |
| long to = dirTab.getOffset(); |
| readCommonLayoutTables ( in, tableTag, to + slo, to + flo, to + llo ); |
| GlyphPositioningTable gpos; |
| if ( ( gpos = constructGPOS() ) != null ) { |
| this.gpos = gpos; |
| } |
| } |
| } |
| |
| /** |
| * Construct the (internal representation of the) GDEF table based on previously |
| * parsed state. |
| * @returns glyph definition table or null if insufficient or invalid state |
| */ |
| private GlyphDefinitionTable constructGDEF() { |
| GlyphDefinitionTable gdef = null; |
| List subtables; |
| if ( ( subtables = constructGDEFSubtables() ) != null ) { |
| if ( subtables.size() > 0 ) { |
| gdef = new GlyphDefinitionTable ( subtables ); |
| } |
| } |
| resetATState(); |
| return gdef; |
| } |
| |
| /** |
| * Construct the (internal representation of the) GSUB table based on previously |
| * parsed state. |
| * @returns glyph substitution table or null if insufficient or invalid state |
| */ |
| private GlyphSubstitutionTable constructGSUB() { |
| GlyphSubstitutionTable gsub = null; |
| Map lookups; |
| if ( ( lookups = constructLookups() ) != null ) { |
| List subtables; |
| if ( ( subtables = constructGSUBSubtables() ) != null ) { |
| if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) { |
| gsub = new GlyphSubstitutionTable ( gdef, lookups, subtables ); |
| } |
| } |
| } |
| resetATState(); |
| return gsub; |
| } |
| |
| /** |
| * Construct the (internal representation of the) GPOS table based on previously |
| * parsed state. |
| * @returns glyph positioning table or null if insufficient or invalid state |
| */ |
| private GlyphPositioningTable constructGPOS() { |
| GlyphPositioningTable gpos = null; |
| Map lookups; |
| if ( ( lookups = constructLookups() ) != null ) { |
| List subtables; |
| if ( ( subtables = constructGPOSSubtables() ) != null ) { |
| if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) { |
| gpos = new GlyphPositioningTable ( gdef, lookups, subtables ); |
| } |
| } |
| } |
| resetATState(); |
| return gpos; |
| } |
| |
| private void constructLookupsFeature ( Map lookups, String st, String lt, String fid ) { |
| Object[] fp = (Object[]) seFeatures.get ( fid ); |
| if ( fp != null ) { |
| assert fp.length == 2; |
| String ft = (String) fp[0]; // feature tag |
| List/*<String>*/ lul = (List) fp[1]; // list of lookup table ids |
| if ( ( ft != null ) && ( lul != null ) && ( lul.size() > 0 ) ) { |
| GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec ( st, lt, ft ); |
| lookups.put ( ls, lul ); |
| } |
| } |
| } |
| |
| private void constructLookupsFeatures ( Map lookups, String st, String lt, List/*<String>*/ fids ) { |
| for ( Iterator fit = fids.iterator(); fit.hasNext();) { |
| String fid = (String) fit.next(); |
| constructLookupsFeature ( lookups, st, lt, fid ); |
| } |
| } |
| |
| private void constructLookupsLanguage ( Map lookups, String st, String lt, Map/*<String,Object[2]>*/ languages ) { |
| Object[] lp = (Object[]) languages.get ( lt ); |
| if ( lp != null ) { |
| assert lp.length == 2; |
| if ( lp[0] != null ) { // required feature id |
| constructLookupsFeature ( lookups, st, lt, (String) lp[0] ); |
| } |
| if ( lp[1] != null ) { // non-required features ids |
| constructLookupsFeatures ( lookups, st, lt, (List) lp[1] ); |
| } |
| } |
| } |
| |
| private void constructLookupsLanguages ( Map lookups, String st, List/*<String>*/ ll, Map/*<String,Object[2]>*/ languages ) { |
| for ( Iterator lit = ll.iterator(); lit.hasNext();) { |
| String lt = (String) lit.next(); |
| constructLookupsLanguage ( lookups, st, lt, languages ); |
| } |
| } |
| |
| private Map constructLookups() { |
| Map/*<GlyphTable.LookupSpec,List<String>>*/ lookups = new java.util.LinkedHashMap(); |
| for ( Iterator sit = seScripts.keySet().iterator(); sit.hasNext();) { |
| String st = (String) sit.next(); |
| Object[] sp = (Object[]) seScripts.get ( st ); |
| if ( sp != null ) { |
| assert sp.length == 3; |
| Map/*<String,Object[2]>*/ languages = (Map) sp[2]; |
| if ( sp[0] != null ) { // default language |
| constructLookupsLanguage ( lookups, st, (String) sp[0], languages ); |
| } |
| if ( sp[1] != null ) { // non-default languages |
| constructLookupsLanguages ( lookups, st, (List) sp[1], languages ); |
| } |
| } |
| } |
| return lookups; |
| } |
| |
| private List constructGDEFSubtables() { |
| List/*<GlyphDefinitionSubtable>*/ subtables = new java.util.ArrayList(); |
| if ( seSubtables != null ) { |
| for ( Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ( ( st = constructGDEFSubtable ( stp ) ) != null ) { |
| subtables.add ( st ); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGDEFSubtable ( Object[] stp ) { |
| GlyphSubtable st = null; |
| assert ( stp != null ) && ( stp.length == 8 ); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphMappingTable mapping = (GlyphMappingTable) stp[6]; |
| List entries = (List) stp[7]; |
| if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) { |
| int type = GDEFLookupType.getSubtableType ( lt.intValue() ); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphDefinitionTable.createSubtable ( type, lid, sequence, flags, format, mapping, entries ); |
| } |
| return st; |
| } |
| |
| private List constructGSUBSubtables() { |
| List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList(); |
| if ( seSubtables != null ) { |
| for ( Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ( ( st = constructGSUBSubtable ( stp ) ) != null ) { |
| subtables.add ( st ); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGSUBSubtable ( Object[] stp ) { |
| GlyphSubtable st = null; |
| assert ( stp != null ) && ( stp.length == 8 ); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; |
| List entries = (List) stp[7]; |
| if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) { |
| int type = GSUBLookupType.getSubtableType ( lt.intValue() ); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphSubstitutionTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries ); |
| } |
| return st; |
| } |
| |
| private List constructGPOSSubtables() { |
| List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList(); |
| if ( seSubtables != null ) { |
| for ( Iterator it = seSubtables.iterator(); it.hasNext();) { |
| Object[] stp = (Object[]) it.next(); |
| GlyphSubtable st; |
| if ( ( st = constructGPOSSubtable ( stp ) ) != null ) { |
| subtables.add ( st ); |
| } |
| } |
| } |
| return subtables; |
| } |
| |
| private GlyphSubtable constructGPOSSubtable ( Object[] stp ) { |
| GlyphSubtable st = null; |
| assert ( stp != null ) && ( stp.length == 8 ); |
| Integer tt = (Integer) stp[0]; // table type |
| Integer lt = (Integer) stp[1]; // lookup type |
| Integer ln = (Integer) stp[2]; // lookup sequence number |
| Integer lf = (Integer) stp[3]; // lookup flags |
| Integer sn = (Integer) stp[4]; // subtable sequence number |
| Integer sf = (Integer) stp[5]; // subtable format |
| GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6]; |
| List entries = (List) stp[7]; |
| if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) { |
| int type = GSUBLookupType.getSubtableType ( lt.intValue() ); |
| String lid = "lu" + ln.intValue(); |
| int sequence = sn.intValue(); |
| int flags = lf.intValue(); |
| int format = sf.intValue(); |
| st = GlyphPositioningTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries ); |
| } |
| return st; |
| } |
| |
| private void initATState() { |
| seScripts = new java.util.LinkedHashMap(); |
| seLanguages = new java.util.LinkedHashMap(); |
| seFeatures = new java.util.LinkedHashMap(); |
| seSubtables = new java.util.ArrayList(); |
| resetATSubState(); |
| } |
| |
| private void resetATState() { |
| seScripts = null; |
| seLanguages = null; |
| seFeatures = null; |
| seSubtables = null; |
| resetATSubState(); |
| } |
| |
| private void initATSubState() { |
| seMapping = null; |
| seEntries = new java.util.ArrayList(); |
| } |
| |
| private void extractSESubState ( int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat ) { |
| if ( seEntries != null ) { |
| if ( ( tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) || ( seEntries.size() > 0 ) ) { |
| if ( seSubtables != null ) { |
| Integer tt = Integer.valueOf ( tableType ); |
| Integer lt = Integer.valueOf ( lookupType ); |
| Integer ln = Integer.valueOf ( lookupSequence ); |
| Integer lf = Integer.valueOf ( lookupFlags ); |
| Integer sn = Integer.valueOf ( subtableSequence ); |
| Integer sf = Integer.valueOf ( subtableFormat ); |
| seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries } ); |
| } |
| } |
| } |
| } |
| |
| private void resetATSubState() { |
| seMapping = null; |
| seEntries = null; |
| } |
| |
| private void resetATStateAll() { |
| resetATState(); |
| gdef = null; gsub = null; gpos = null; |
| } |
| |
| /** |
| * 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<String> getTTCnames(FontFileReader in) throws IOException { |
| List<String> fontNames = new java.util.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(in); |
| |
| readName(in); |
| |
| 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 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: " + 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 |
| * |
| * @param glyphIndex |
| * @return unicode code point |
| */ |
| private Integer glyphToUnicode(int glyphIndex) { |
| 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 |
| */ |
| 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 { |
| boolean useKerning = true; |
| boolean useAdvanced = true; |
| TTFFile ttfFile = new TTFFile(useKerning, useAdvanced); |
| |
| 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); |
| } |
| } |
| } |