/*
 * 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.complexscripts.fonts;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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

import org.apache.fop.complexscripts.scripts.ScriptProcessor;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFDirTabEntry;
import org.apache.fop.fonts.truetype.OFTableName;
import org.apache.fop.fonts.truetype.OpenFont;

// CSOFF: LineLengthCheck

OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile} to read advanced typographic tables (GDEF, GSUB, GPOS).

This work was originally authored by Glenn Adams (gadams@apache.org).

/** * <p>OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile} * to read advanced typographic tables (GDEF, GSUB, GPOS).</p> * * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> */
public final class OTFAdvancedTypographicTableReader { // logging state private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class); // instance state private OpenFont otf; // parent font file reader private FontFileReader in; // input reader private GlyphDefinitionTable gdef; // glyph definition table private GlyphSubstitutionTable gsub; // glyph substitution table private GlyphPositioningTable gpos; // glyph positioning table // transient parsing state private transient Map<String, Object> seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages } private transient Map<String, Object> seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>") private transient Map<String, Object> seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") } private transient GlyphMappingTable seMapping; // subtable entry mappings private transient List seEntries; // subtable entry entries private transient List seSubtables; // subtable entry subtables private Map<String, ScriptProcessor> processors = new HashMap<String, ScriptProcessor>();
Construct an OTFAdvancedTypographicTableReader instance.
Params:
  • otf – parent font file reader (must be non-null)
  • in – font file reader (must be non-null)
/** * Construct an <code>OTFAdvancedTypographicTableReader</code> instance. * @param otf parent font file reader (must be non-null) * @param in font file reader (must be non-null) */
public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) { assert otf != null; assert in != null; this.otf = otf; this.in = in; }
Read all advanced typographic tables.
Throws:
  • AdvancedTypographicTableFormatException – if ATT table has invalid format
/** * Read all advanced typographic tables. * @throws AdvancedTypographicTableFormatException if ATT table has invalid format */
public void readAll() throws AdvancedTypographicTableFormatException { try { readGDEF(); readGSUB(); readGPOS(); } catch (AdvancedTypographicTableFormatException e) { resetATStateAll(); throw e; } catch (IOException e) { resetATStateAll(); throw new AdvancedTypographicTableFormatException(e.getMessage(), e); } finally { resetATState(); } }
Determine if advanced (typographic) table is present.
Returns:true if advanced (typographic) table is present
/** * Determine if advanced (typographic) table is present. * @return true if advanced (typographic) table is present */
public boolean hasAdvancedTable() { return (gdef != null) || (gsub != null) || (gpos != null); }
Returns the GDEF table or null if none present.
Returns:the GDEF table
/** * Returns the GDEF table or null if none present. * @return the GDEF table */
public GlyphDefinitionTable getGDEF() { return gdef; }
Returns the GSUB table or null if none present.
Returns:the GSUB table
/** * Returns the GSUB table or null if none present. * @return the GSUB table */
public GlyphSubstitutionTable getGSUB() { return gsub; }
Returns the GPOS table or null if none present.
Returns:the GPOS table
/** * Returns the GPOS table or null if none present. * @return the GPOS table */
public GlyphPositioningTable getGPOS() { return gpos; } private void readLangSysTable(OFTableName 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 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); } 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(OFTableName 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(tableTag, scriptTable + loa [ i ], lta [ i ]); } } // read default language system table (if specified) if (dl > 0) { readLangSysTable(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(OFTableName 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(tableTag, scriptList + soa [ i ], sta [ i ]); } } } private void readFeatureTable(OFTableName 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 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); } lul.add("lu" + li); } seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul }); } private void readFeatureList(OFTableName 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(tableTag, featureList + foa [ i ], fta [ i ], i); } } } 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(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(g); } // dump info if debugging if (log.isDebugEnabled()) { log.debug(label + " glyphs: " + toString(ga)); } return GlyphCoverageTable.createCoverageTable(entries); } private GlyphCoverageTable readCoverageTableFormat2(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(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(label, tableOffset, cf); } else if (cf == 2) { gct = readCoverageTableFormat2(label, tableOffset, cf); } else { throw new AdvancedTypographicTableFormatException("unsupported coverage table format: " + cf); } in.seekSet(cp); return gct; } private GlyphClassTable readClassDefTableFormat1(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(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(gc); } // dump info if debugging if (log.isDebugEnabled()) { log.debug(label + " glyph classes: " + toString(ca)); } return GlyphClassTable.createClassTable(entries); } private GlyphClassTable readClassDefTableFormat2(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(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(label, tableOffset, cf); } else if (cf == 2) { gct = readClassDefTableFormat2(label, tableOffset, cf); } else { throw new AdvancedTypographicTableFormatException("unsupported class definition table format: " + cf); } in.seekSet(cp); return gct; } private void readSingleSubTableFormat1(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(tableTag + " single substitution coverage", subtableOffset + co); seEntries.add(dg); } private void readSingleSubTableFormat2(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(tableTag + " single substitution coverage", subtableOffset + co); // read glyph substitutions for (int i = 0, n = ng; i < n; i++) { int gs = in.readTTFUShort(); if (log.isDebugEnabled()) { log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs); } seEntries.add(gs); } } private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readSingleSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported single substitution subtable format: " + sf); } return sf; } private void readMultipleSubTableFormat1(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readMultipleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported multiple substitution subtable format: " + sf); } return sf; } private void readAlternateSubTableFormat1(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readAlternateSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported alternate substitution subtable format: " + sf); } return sf; } private void readLigatureSubTableFormat1(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readLigatureSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported ligature substitution subtable format: " + sf); } return sf; } private GlyphTable.RuleLookup[] readRuleLookups(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(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(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(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(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(tableTag + " contextual substitution coverage", subtableOffset + co); } else { ct = null; } // read class definition table GlyphClassTable cdt; if (cdo > 0) { cdt = readClassDefTable(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(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(ngc); seEntries.add(rsa); } private void readContextualSubTableFormat3(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 3) { readContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported contextual substitution subtable format: " + sf); } return sf; } private void readChainedContextualSubTableFormat1(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(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(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(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(tableTag + " chained contextual substitution coverage", subtableOffset + co); } else { ct = null; } // read backtrack class definition table GlyphClassTable bcdt; if (bcdo > 0) { bcdt = readClassDefTable(tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo); } else { bcdt = null; } // read input class definition table GlyphClassTable icdt; if (icdo > 0) { icdt = readClassDefTable(tableTag + " contextual substitution input class definition", subtableOffset + icdo); } else { icdt = null; } // read lookahead class definition table GlyphClassTable lcdt; if (lcdo > 0) { lcdt = readClassDefTable(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(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(ngc); seEntries.add(rsa); } private void readChainedContextualSubTableFormat3(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(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(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readChainedContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readChainedContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 3) { readChainedContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported chained contextual substitution subtable format: " + sf); } return sf; } private void readExtensionSubTableFormat1(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(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); } private int readExtensionSubTable(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(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported extension substitution subtable format: " + sf); } return sf; } private void readReverseChainedSingleSubTableFormat1(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(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read substitution subtable format int sf = in.readTTFUShort(); if (sf == 1) { readReverseChainedSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported reverse chained single substitution subtable format: " + sf); } return sf; } private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { initATSubState(); int subtableFormat = -1; switch (lookupType) { case GSUBLookupType.SINGLE: subtableFormat = readSingleSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.MULTIPLE: subtableFormat = readMultipleSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.ALTERNATE: subtableFormat = readAlternateSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.LIGATURE: subtableFormat = readLigatureSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.CONTEXTUAL: subtableFormat = readContextualSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.CHAINED_CONTEXTUAL: subtableFormat = readChainedContextualSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.REVERSE_CHAINED_SINGLE: subtableFormat = readReverseChainedSingleSubTable(lookupType, lookupFlags, subtableOffset); break; case GSUBLookupType.EXTENSION: subtableFormat = readExtensionSubTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); break; default: break; } extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); resetATSubState(); } private GlyphPositioningTable.DeviceTable readPosDeviceTable(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; int m1; int dm; int dd; int 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(long subtableOffset, int valueFormat) throws IOException { // XPlacement int xp; if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) { xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xp = 0; } // YPlacement int yp; if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) { yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { yp = 0; } // XAdvance int xa; if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) { xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { xa = 0; } // YAdvance int ya; if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) { ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); } else { ya = 0; } // XPlaDevice GlyphPositioningTable.DeviceTable xpd; if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE) != 0) { int xpdo = in.readTTFUShort(); xpd = readPosDeviceTable(subtableOffset, xpdo); } else { xpd = null; } // YPlaDevice GlyphPositioningTable.DeviceTable ypd; if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE) != 0) { int ypdo = in.readTTFUShort(); ypd = readPosDeviceTable(subtableOffset, ypdo); } else { ypd = null; } // XAdvDevice GlyphPositioningTable.DeviceTable xad; if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE) != 0) { int xado = in.readTTFUShort(); xad = readPosDeviceTable(subtableOffset, xado); } else { xad = null; } // YAdvDevice GlyphPositioningTable.DeviceTable yad; if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE) != 0) { int yado = in.readTTFUShort(); yad = readPosDeviceTable(subtableOffset, yado); } else { yad = null; } return new GlyphPositioningTable.Value(xp, yp, xa, ya, xpd, ypd, xad, yad); } private void readSinglePosTableFormat1(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(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(tableTag + " single positioning coverage", subtableOffset + co); // store results seMapping = ct; seEntries.add(v); } private void readSinglePosTableFormat2(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positionining subtable format int sf = in.readTTFUShort(); if (sf == 1) { readSinglePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readSinglePosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported single positioning subtable format: " + sf); } return sf; } private GlyphPositioningTable.PairValues readPosPairValues(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(subtableOffset, vf1); } else { v1 = null; } // read second value (if present) GlyphPositioningTable.Value v2; if (vf2 != 0) { v2 = readPosValue(subtableOffset, vf2); } else { v2 = null; } return new GlyphPositioningTable.PairValues(glyph, v1, v2); } private GlyphPositioningTable.PairValues[] readPosPairSetTable(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(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(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(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(subtableOffset, pso, vf1, vf2); } // store results seMapping = ct; seEntries.add(pvm); } private void readPairPosTableFormat2(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(tableTag + " pair positioning coverage", subtableOffset + co); // read class definition table #1 GlyphClassTable cdt1 = readClassDefTable(tableTag + " pair positioning class definition #1", subtableOffset + cd1o); // read class definition table #2 GlyphClassTable cdt2 = readClassDefTable(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(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(nc1); seEntries.add(nc2); seEntries.add(pvm); } private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readPairPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readPairPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported pair positioning subtable format: " + sf); } return sf; } private GlyphPositioningTable.Anchor readPosAnchor(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 = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); a = new GlyphPositioningTable.Anchor(x, y); } else if (af == 2) { // read x coordinate int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate int y = otf.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 = otf.convertTTFUnit2PDFUnit(in.readTTFShort()); // read y coordinate int y = otf.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(cp, xdo); } else { xd = null; } // read y device table (if present) GlyphPositioningTable.DeviceTable yd; if (ydo != 0) { yd = readPosDeviceTable(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(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(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(subtableOffset + eno); } else { ena = null; } // read exit anchor GlyphPositioningTable.Anchor exa; if (exo > 0) { exa = readPosAnchor(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readCursivePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported cursive positioning subtable format: " + sf); } return sf; } private void readMarkToBasePosTableFormat1(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(tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco); // read base coverage table GlyphCoverageTable bct = readCoverageTable(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(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(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(nmc); seEntries.add(maa); seEntries.add(bam); } private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readMarkToBasePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported mark-to-base positioning subtable format: " + sf); } return sf; } private void readMarkToLigaturePosTableFormat1(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(tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco); // read ligature coverage table GlyphCoverageTable lct = readCoverageTable(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(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(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(nmc); seEntries.add(mxc); seEntries.add(maa); seEntries.add(lam); } private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readMarkToLigaturePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported mark-to-ligature positioning subtable format: " + sf); } return sf; } private void readMarkToMarkPosTableFormat1(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(tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co); // read mark #2 coverage table GlyphCoverageTable mct2 = readCoverageTable(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(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(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(nmc); seEntries.add(maa); seEntries.add(mam); } private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readMarkToMarkPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported mark-to-mark positioning subtable format: " + sf); } return sf; } private void readContextualPosTableFormat1(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(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(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(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(tableTag + " contextual positioning coverage", subtableOffset + co); } else { ct = null; } // read class definition table GlyphClassTable cdt; if (cdo > 0) { cdt = readClassDefTable(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(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(ngc); seEntries.add(rsa); } private void readContextualPosTableFormat3(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 3) { readContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported contextual positioning subtable format: " + sf); } return sf; } private void readChainedContextualPosTableFormat1(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(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(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(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(tableTag + " chained contextual positioning coverage", subtableOffset + co); } else { ct = null; } // read backtrack class definition table GlyphClassTable bcdt; if (bcdo > 0) { bcdt = readClassDefTable(tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo); } else { bcdt = null; } // read input class definition table GlyphClassTable icdt; if (icdo > 0) { icdt = readClassDefTable(tableTag + " contextual positioning input class definition", subtableOffset + icdo); } else { icdt = null; } // read lookahead class definition table GlyphClassTable lcdt; if (lcdo > 0) { lcdt = readClassDefTable(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(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(ngc); seEntries.add(rsa); } private void readChainedContextualPosTableFormat3(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(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(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(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(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(int lookupType, int lookupFlags, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read positioning subtable format int sf = in.readTTFUShort(); if (sf == 1) { readChainedContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 2) { readChainedContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf); } else if (sf == 3) { readChainedContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported chained contextual positioning subtable format: " + sf); } return sf; } private void readExtensionPosTableFormat1(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(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo); } private int readExtensionPosTable(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(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported extension positioning subtable format: " + sf); } return sf; } private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException { initATSubState(); int subtableFormat = -1; switch (lookupType) { case GPOSLookupType.SINGLE: subtableFormat = readSinglePosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.PAIR: subtableFormat = readPairPosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.CURSIVE: subtableFormat = readCursivePosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.MARK_TO_BASE: subtableFormat = readMarkToBasePosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.MARK_TO_LIGATURE: subtableFormat = readMarkToLigaturePosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.MARK_TO_MARK: subtableFormat = readMarkToMarkPosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.CONTEXTUAL: subtableFormat = readContextualPosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.CHAINED_CONTEXTUAL: subtableFormat = readChainedContextualPosTable(lookupType, lookupFlags, subtableOffset); break; case GPOSLookupType.EXTENSION: subtableFormat = readExtensionPosTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset); break; default: break; } extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat); resetATSubState(); } private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException { boolean isGSUB = tableTag.equals(OFTableName.GSUB); boolean isGPOS = tableTag.equals(OFTableName.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(lt, lf, lookupSequence, i, lookupTable + so); } else if (isGPOS) { readGPOSSubtable(lt, lf, lookupSequence, i, lookupTable + so); } } } private void readLookupList(OFTableName 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(tableTag, i, lookupList + loa [ i ]); } } }
Read the common layout tables (used by GSUB and GPOS).
Params:
  • tableTag – tag of table being read
  • scriptList – offset to script list from beginning of font file
  • featureList – offset to feature list from beginning of font file
  • lookupList – offset to lookup list from beginning of font file
Throws:
/** * Read the common layout tables (used by GSUB and GPOS). * @param tableTag tag of table being read * @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(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException { if (scriptList > 0) { readScriptList(tableTag, scriptList); } if (featureList > 0) { readFeatureList(tableTag, featureList); } if (lookupList > 0) { readLookupList(tableTag, lookupList); } } private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table GlyphClassTable ct = readClassDefTable(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(OFTableName 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(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(OFTableName 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(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(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { initATSubState(); in.seekSet(subtableOffset); // subtable is a bare class definition table GlyphClassTable ct = readClassDefTable(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(OFTableName 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(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(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException { in.seekSet(subtableOffset); // read mark set subtable format int sf = in.readTTFUShort(); if (sf == 1) { readGDEFMarkGlyphsTableFormat1(tableTag, lookupSequence, subtableOffset, sf); } else { throw new AdvancedTypographicTableFormatException("unsupported mark glyph sets subtable format: " + sf); } }
Read the GDEF table.
Throws:
  • IOException – In case of a I/O problem
/** * Read the GDEF table. * @throws IOException In case of a I/O problem */
private void readGDEF() throws IOException { OFTableName tableTag = OFTableName.GDEF; // Initialize temporary state initATState(); // Read glyph definition (GDEF) table OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gdef != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { otf.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(tableTag, seqno++, to + cdo); } // (optionally) read glyph attachment point subtable if (apo != 0) { readGDEFAttachmentTable(tableTag, seqno++, to + apo); } // (optionally) read ligature caret subtable if (lco != 0) { readGDEFLigatureCaretTable(tableTag, seqno++, to + lco); } // (optionally) read mark attachment class subtable if (mao != 0) { readGDEFMarkAttachmentTable(tableTag, seqno++, to + mao); } // (optionally) read mark glyph sets subtable if (mgo != 0) { readGDEFMarkGlyphsTable(tableTag, seqno++, to + mgo); } GlyphDefinitionTable gdef; if ((gdef = constructGDEF()) != null) { this.gdef = gdef; } } }
Read the GSUB table.
Throws:
  • IOException – In case of a I/O problem
/** * Read the GSUB table. * @throws IOException In case of a I/O problem */
private void readGSUB() throws IOException { OFTableName tableTag = OFTableName.GSUB; // Initialize temporary state initATState(); // Read glyph substitution (GSUB) table OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { otf.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(tableTag, to + slo, to + flo, to + llo); GlyphSubstitutionTable gsub; if ((gsub = constructGSUB()) != null) { this.gsub = gsub; } } }
Read the GPOS table.
Throws:
  • IOException – In case of a I/O problem
/** * Read the GPOS table. * @throws IOException In case of a I/O problem */
private void readGPOS() throws IOException { OFTableName tableTag = OFTableName.GPOS; // Initialize temporary state initATState(); // Read glyph positioning (GPOS) table OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag); if (gpos != null) { if (log.isDebugEnabled()) { log.debug(tableTag + ": ignoring duplicate table"); } } else if (dirTab != null) { otf.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(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.
@returnsglyph definition table or null if insufficient or invalid state
/** * 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, processors); } } resetATState(); return gdef; }
Construct the (internal representation of the) GSUB table based on previously parsed state.
@returnsglyph substitution table or null if insufficient or invalid state
/** * 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, processors); } } } resetATState(); return gsub; }
Construct the (internal representation of the) GPOS table based on previously parsed state.
@returnsglyph positioning table or null if insufficient or invalid state
/** * 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, processors); } } } 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 (Object fid1 : fids) { String fid = (String) fid1; constructLookupsFeature(lookups, st, lt, fid); } } private void constructLookupsLanguage(Map lookups, String st, String lt, Map<String, Object> 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> languages) { for (Object aLl : ll) { String lt = (String) aLl; constructLookupsLanguage(lookups, st, lt, languages); } } private Map constructLookups() { Map<GlyphTable.LookupSpec, List<String>> lookups = new java.util.LinkedHashMap(); for (Object o : seScripts.keySet()) { String st = (String) o; Object[] sp = (Object[]) seScripts.get(st); if (sp != null) { assert sp.length == 3; Map<String, Object> 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<GlyphSubtable> subtables = new java.util.ArrayList(); if (seSubtables != null) { for (Object seSubtable : seSubtables) { Object[] stp = (Object[]) seSubtable; 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 == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) { int type = GDEFLookupType.getSubtableType(lt); String lid = "lu" + ln; int sequence = sn; int flags = lf; int format = sf; 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 (Object seSubtable : seSubtables) { Object[] stp = (Object[]) seSubtable; 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 == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) { int type = GSUBLookupType.getSubtableType(lt); String lid = "lu" + ln; int sequence = sn; int flags = lf; int format = sf; 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 (Object seSubtable : seSubtables) { Object[] stp = (Object[]) seSubtable; 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 == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) { int type = GSUBLookupType.getSubtableType(lt); String lid = "lu" + ln; int sequence = sn; int flags = lf; int format = sf; 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 = tableType; Integer lt = lookupType; Integer ln = lookupSequence; Integer lf = lookupFlags; Integer sn = subtableSequence; Integer sf = 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; }
helper method for formatting an integer array for output
/** 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 anIa : ia) { if (!first) { sb.append(' '); } else { first = false; } sb.append(anIa); } } return sb.toString(); } }