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