You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-commits@xmlgraphics.apache.org by ga...@apache.org on 2012/02/26 03:29:29 UTC

svn commit: r1293736 [11/38] - in /xmlgraphics/fop/trunk: ./ src/codegen/java/org/apache/fop/tools/ src/codegen/unicode/java/org/apache/fop/complexscripts/ src/codegen/unicode/java/org/apache/fop/complexscripts/bidi/ src/documentation/content/xdocs/tru...

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/OTFAdvancedTypographicTableReader.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,3797 @@
+/*
+ * 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.Iterator;
+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.fonts.AdvancedTypographicTableFormatException;
+import org.apache.fop.complexscripts.fonts.GlyphClassTable;
+import org.apache.fop.complexscripts.fonts.GlyphCoverageTable;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphDefinitionTable;
+import org.apache.fop.complexscripts.fonts.GlyphMappingTable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphPositioningTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable;
+import org.apache.fop.complexscripts.fonts.GlyphSubtable;
+import org.apache.fop.complexscripts.fonts.GlyphTable;
+import org.apache.fop.fonts.truetype.FontFileReader;
+import org.apache.fop.fonts.truetype.TTFDirTabEntry;
+import org.apache.fop.fonts.truetype.TTFFile;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: SimplifyBooleanReturnCheck
+// 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).
+ *
+ * @author Glenn Adams
+ */
+public final class OTFAdvancedTypographicTableReader {
+
+    // logging state
+    private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class);
+    // instance state
+    private TTFFile ttf;                                        // 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[3]>*/ seScripts;      // script-tag         => Object[3] : { default-language-tag, List(language-tag), seLanguages }
+    private transient Map/*<String,Object[2]>*/ seLanguages;    // language-tag       => Object[2] : { "f<required-feature-index>", List("f<feature-index>")
+    private transient Map/*<String,List<String>>*/ 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
+
+    /**
+     * Construct an <code>OTFAdvancedTypographicTableReader</code> instance.
+     * @param ttf parent font file reader (must be non-null)
+     * @param in font file reader (must be non-null)
+     */
+    public OTFAdvancedTypographicTableReader ( TTFFile ttf, FontFileReader in ) {
+        assert ttf != null;
+        assert in != null;
+        this.ttf = ttf;
+        this.in = in;
+    }
+
+    /**
+     * 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.
+     * @return true if advanced (typographic) table is present
+     */
+    public boolean hasAdvancedTable() {
+        return ( gdef != null ) || ( gsub != null ) || ( gpos != null );
+    }
+
+    /**
+     * Returns the GDEF table or null if none present.
+     * @return the GDEF table
+     */
+    public GlyphDefinitionTable getGDEF() {
+        return gdef;
+    }
+
+    /**
+     * Returns the GSUB table or null if none present.
+     * @return the GSUB table
+     */
+    public GlyphSubstitutionTable getGSUB() {
+        return gsub;
+    }
+
+    /**
+     * Returns the GPOS table or null if none present.
+     * @return the GPOS table
+     */
+    public GlyphPositioningTable getGPOS() {
+        return gpos;
+    }
+
+    private void readLangSysTable(String tableTag, long langSysTable, String langSysTag) throws IOException {
+        in.seekSet(langSysTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " lang sys table: " + langSysTag );
+        }
+        // read lookup order (reorder) table offset
+        int lo = in.readTTFUShort();
+        // read required feature index
+        int rf = in.readTTFUShort();
+        String rfi;
+        if ( rf != 65535 ) {
+            rfi = "f" + rf;
+        } else {
+            rfi = null;
+        }
+        // read (non-required) feature count
+        int nf = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " lang sys table reorder table: " + lo );
+            log.debug(tableTag + " lang sys table required feature index: " + rf );
+            log.debug(tableTag + " lang sys table non-required feature count: " + nf );
+        }
+        // read (non-required) feature indices
+        int[] fia = new int[nf];
+        List fl = new java.util.ArrayList();
+        for ( int i = 0; i < nf; i++ ) {
+            int fi = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " lang sys table non-required feature index: " + fi );
+            }
+            fia[i] = fi;
+            fl.add ( "f" + fi );
+        }
+        if ( seLanguages == null ) {
+            seLanguages = new java.util.LinkedHashMap();
+        }
+        seLanguages.put ( langSysTag, new Object[] { rfi, fl } );
+    }
+
+    private static String defaultTag = "dflt";
+
+    private void readScriptTable(String tableTag, long scriptTable, String scriptTag) throws IOException {
+        in.seekSet(scriptTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " script table: " + scriptTag );
+        }
+        // read default language system table offset
+        int dl = in.readTTFUShort();
+        String dt = defaultTag;
+        if ( dl > 0 ) {
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " default lang sys tag: " + dt );
+                log.debug(tableTag + " default lang sys table offset: " + dl );
+            }
+        }
+        // read language system record count
+        int nl = in.readTTFUShort();
+        List ll = new java.util.ArrayList();
+        if ( nl > 0 ) {
+            String[] lta = new String[nl];
+            int[] loa = new int[nl];
+            // read language system records
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                String lt = in.readTTFString(4);
+                int lo = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " lang sys tag: " + lt );
+                    log.debug(tableTag + " lang sys table offset: " + lo );
+                }
+                lta[i] = lt;
+                loa[i] = lo;
+                if ( dl == lo ) {
+                    dl = 0;
+                    dt = lt;
+                }
+                ll.add ( lt );
+            }
+            // read non-default language system tables
+            for ( int i = 0, n = nl; i < n; i++ ) {
+                readLangSysTable ( 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(String tableTag, long scriptList) throws IOException {
+        in.seekSet(scriptList);
+        // read script record count
+        int ns = in.readTTFUShort();
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " script list record count: " + ns );
+        }
+        if ( ns > 0 ) {
+            String[] sta = new String[ns];
+            int[] soa = new int[ns];
+            // read script records
+            for ( int i = 0, n = ns; i < n; i++ ) {
+                String st = in.readTTFString(4);
+                int so = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " script tag: " + st );
+                    log.debug(tableTag + " script table offset: " + so );
+                }
+                sta[i] = st;
+                soa[i] = so;
+            }
+            // read script tables
+            for ( int i = 0, n = ns; i < n; i++ ) {
+                seLanguages = null;
+                readScriptTable ( tableTag, scriptList + soa [ i ], sta [ i ] );
+            }
+        }
+    }
+
+    private void readFeatureTable(String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
+        in.seekSet(featureTable);
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature table: " + featureTag );
+        }
+        // read feature params offset
+        int po = in.readTTFUShort();
+        // read lookup list indices count
+        int nl = in.readTTFUShort();
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature table parameters offset: " + po );
+            log.debug(tableTag + " feature table lookup list index count: " + nl );
+        }
+        // read lookup table indices
+        int[] lia = new int[nl];
+        List lul = new java.util.ArrayList();
+        for ( int i = 0; i < nl; i++ ) {
+            int li = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " feature table lookup index: " + li );
+            }
+            lia[i] = li;
+            lul.add ( "lu" + li );
+        }
+        seFeatures.put ( "f" + featureIndex, new Object[] { featureTag, lul } );
+    }
+
+    private void readFeatureList(String tableTag, long featureList) throws IOException {
+        in.seekSet(featureList);
+        // read feature record count
+        int nf = in.readTTFUShort();
+        if (log.isDebugEnabled()) {
+            log.debug(tableTag + " feature list record count: " + nf );
+        }
+        if ( nf > 0 ) {
+            String[] fta = new String[nf];
+            int[] foa = new int[nf];
+            // read feature records
+            for ( int i = 0, n = nf; i < n; i++ ) {
+                String ft = in.readTTFString(4);
+                int fo = in.readTTFUShort();
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " feature tag: " + ft );
+                    log.debug(tableTag + " feature table offset: " + fo );
+                }
+                fta[i] = ft;
+                foa[i] = fo;
+            }
+            // read feature tables
+            for ( int i = 0, n = nf; i < n; i++ ) {
+                if (log.isDebugEnabled()) {
+                    log.debug(tableTag + " feature index: " + i );
+                }
+                readFeatureTable ( 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 ( Integer.valueOf(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 ( Integer.valueOf(sg) );
+        // read glyph count
+        int ng = in.readTTFUShort();
+        // read glyph classes
+        int[] ca = new int[ng];
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int gc = in.readTTFUShort();
+            ca[i] = gc;
+            entries.add ( Integer.valueOf(gc) );
+        }
+        // dump info if debugging
+        if (log.isDebugEnabled()) {
+            log.debug(label + " glyph classes: " + toString(ca) );
+        }
+        return GlyphClassTable.createClassTable ( entries );
+    }
+
+    private GlyphClassTable readClassDefTableFormat2(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 ( Integer.valueOf ( 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
+        int[] gsa = new int[ng];
+        for ( int i = 0, n = ng; i < n; i++ ) {
+            int gs = in.readTTFUShort();
+            if (log.isDebugEnabled()) {
+                log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs );
+            }
+            gsa[i] = gs;
+            seEntries.add ( Integer.valueOf ( gs ) );
+        }
+    }
+
+    private int readSingleSubTable(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 ( Integer.valueOf ( 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 ( Integer.valueOf ( 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, m1, dm, dd, s2;
+        if ( df == 1 ) {
+            s1 = 14; m1 = 0x3; dm = 1; dd = 4; s2 = 2;
+        } else if ( df == 2 ) {
+            s1 = 12; m1 = 0xF; dm = 7; dd = 16; s2 = 4;
+        } else if ( df == 3 ) {
+            s1 = 8; m1 = 0xFF; dm = 127; dd = 256; s2 = 8;
+        } else {
+            log.debug ( "unsupported device table delta format: " + df + ", ignoring device table" );
+            return null;
+        }
+        // read deltas
+        int n = ( es - ss ) + 1;
+        if ( n < 0 ) {
+            log.debug ( "invalid device table delta count: " + n + ", ignoring device table" );
+            return null;
+        }
+        int[] da = new int [ n ];
+        for ( int i = 0; ( i < n ) && ( s2 > 0 );) {
+            int p = in.readTTFUShort();
+            for ( int j = 0, k = 16 / s2; j < k; j++ ) {
+                int d = ( p >> s1 ) & m1;
+                if ( d > dm ) {
+                    d -= dd;
+                }
+                if ( i < n ) {
+                    da [ i++ ] = d;
+                } else {
+                    break;
+                }
+                p <<= s2;
+            }
+        }
+        in.seekSet(cp);
+        return new GlyphPositioningTable.DeviceTable ( ss, es, da );
+    }
+
+    private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException {
+        // XPlacement
+        int xp;
+        if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) {
+            xp = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+        } else {
+            xp = 0;
+        }
+        // YPlacement
+        int yp;
+        if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT ) != 0 ) {
+            yp = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+        } else {
+            yp = 0;
+        }
+        // XAdvance
+        int xa;
+        if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE ) != 0 ) {
+            xa = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+        } else {
+            xa = 0;
+        }
+        // YAdvance
+        int ya;
+        if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE ) != 0 ) {
+            ya = ttf.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 ( Integer.valueOf ( nc1 ) );
+        seEntries.add ( Integer.valueOf ( 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 = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+            // read y coordinate
+            int y = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+            a = new GlyphPositioningTable.Anchor ( x, y );
+        } else if ( af == 2 ) {
+            // read x coordinate
+            int x = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+            // read y coordinate
+            int y = ttf.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 = ttf.convertTTFUnit2PDFUnit ( in.readTTFShort() );
+            // read y coordinate
+            int y = ttf.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 );

[... 1747 lines stripped ...]


---------------------------------------------------------------------
To unsubscribe, e-mail: fop-commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-commits-help@xmlgraphics.apache.org