/*
* 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.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFDirTabEntry;
import org.apache.fop.fonts.truetype.TTFFile;
import org.apache.fop.fonts.truetype.TTFTableName;
// CSOFF: AvoidNestedBlocksCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: InnerAssignmentCheck
// CSOFF: SimplifyBooleanReturnCheck
// CSOFF: LineLengthCheck
/**
* <p>OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile}
* to read advanced typographic tables (GDEF, GSUB, GPOS).</p>
*
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
*/
public final class OTFAdvancedTypographicTableReader {
// logging state
private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class);
// instance state
private 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(TTFTableName 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(TTFTableName 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(TTFTableName 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(TTFTableName 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(TTFTableName 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;
int m1;
int dm;
int dd;
int s2;
if ( df == 1 ) {
s1 = 14;
m1 = 0x3;
dm = 1;
dd = 4;
s2 = 2;
} else if ( df == 2 ) {
s1 = 12;
m1 = 0xF;
dm = 7;
dd = 16;
s2 = 4;
} else if ( df == 3 ) {
s1 = 8;
m1 = 0xFF;
dm = 127;
dd = 256;
s2 = 8;
} else {
log.debug ( "unsupported device table delta format: " + df + ", ignoring device table" );
return null;
}
// read deltas
int n = ( es - ss ) + 1;
if ( n < 0 ) {
log.debug ( "invalid device table delta count: " + n + ", ignoring device table" );
return null;
}
int[] da = new int [ n ];
for ( int i = 0; ( i < n ) && ( s2 > 0 );) {
int p = in.readTTFUShort();
for ( int j = 0, k = 16 / s2; j < k; j++ ) {
int d = ( p >> s1 ) & m1;
if ( d > dm ) {
d -= dd;
}
if ( i < n ) {
da [ i++ ] = d;
} else {
break;
}
p <<= s2;
}
}
in.seekSet(cp);
return new GlyphPositioningTable.DeviceTable ( ss, es, da );
}
private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException {
// XPlacement
int xp;
if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) {
xp = 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 );
} else {
xd = null;
}
// read y device table (if present)
GlyphPositioningTable.DeviceTable yd;
if ( ydo != 0 ) {
yd = readPosDeviceTable ( cp, ydo );
} else {
yd = null;
}
a = new GlyphPositioningTable.Anchor ( x, y, xd, yd );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported positioning anchor format: " + af );
}
in.seekSet(cp);
return a;
}
private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
// read entry/exit count
int ec = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat );
log.debug(tableTag + " cursive positioning coverage table offset: " + co );
log.debug(tableTag + " cursive positioning entry/exit count: " + ec );
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable ( tableTag + " cursive positioning coverage", subtableOffset + co );
// read entry/exit records
GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ];
for ( int i = 0, n = ec; i < n; i++ ) {
// read entry anchor offset
int eno = in.readTTFUShort();
// read exit anchor offset
int exo = in.readTTFUShort();
// read entry anchor
GlyphPositioningTable.Anchor ena;
if ( eno > 0 ) {
ena = readPosAnchor ( subtableOffset + eno );
} else {
ena = null;
}
// read exit anchor
GlyphPositioningTable.Anchor exa;
if ( exo > 0 ) {
exa = readPosAnchor ( subtableOffset + exo );
} else {
exa = null;
}
aa [ ( i * 2 ) + 0 ] = ena;
aa [ ( i * 2 ) + 1 ] = exa;
if (log.isDebugEnabled()) {
if ( ena != null ) {
log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena );
}
if ( exa != null ) {
log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa );
}
}
}
// store results
seMapping = ct;
seEntries.add ( aa );
}
private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readCursivePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported cursive positioning subtable format: " + sf );
}
return sf;
}
private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read mark coverage offset
int mco = in.readTTFUShort();
// read base coverage offset
int bco = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark array offset
int mao = in.readTTFUShort();
// read base array offset
int bao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat );
log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco );
log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco );
log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc );
log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao );
log.debug(tableTag + " mark-to-base positioning base array offset: " + bao );
}
// read mark coverage table
GlyphCoverageTable mct = readCoverageTable ( tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco );
// read base coverage table
GlyphCoverageTable bct = readCoverageTable ( tableTag + " mark-to-base positioning base coverage", subtableOffset + bco );
// read mark anchor array
// seek to mark array
in.seekSet(subtableOffset + mao);
// read mark count
int nm = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning mark count: " + nm );
}
// read mark anchor array, where i:{0...markCount}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
for ( int i = 0; i < nm; i++ ) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + mao + ao );
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if ( a != null ) {
ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma);
}
}
// read base anchor matrix
// seek to base array
in.seekSet(subtableOffset + bao);
// read base count
int nb = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning base count: " + nb );
}
// read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ];
for ( int i = 0; i < nb; i++ ) {
for ( int j = 0; j < nmc; j++ ) {
// read base anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + bao + ao );
} else {
a = null;
}
bam [ i ] [ j ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a);
}
}
}
// store results
seMapping = mct;
seEntries.add ( bct );
seEntries.add ( Integer.valueOf ( nmc ) );
seEntries.add ( maa );
seEntries.add ( bam );
}
private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readMarkToBasePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-base positioning subtable format: " + sf );
}
return sf;
}
private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read mark coverage offset
int mco = in.readTTFUShort();
// read ligature coverage offset
int lco = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark array offset
int mao = in.readTTFUShort();
// read ligature array offset
int lao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat );
log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco );
log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco );
log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc );
log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao );
log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao );
}
// read mark coverage table
GlyphCoverageTable mct = readCoverageTable ( tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco );
// read ligature coverage table
GlyphCoverageTable lct = readCoverageTable ( tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco );
// read mark anchor array
// seek to mark array
in.seekSet(subtableOffset + mao);
// read mark count
int nm = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm );
}
// read mark anchor array, where i:{0...markCount}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
for ( int i = 0; i < nm; i++ ) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + mao + ao );
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if ( a != null ) {
ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma);
}
}
// read ligature anchor matrix
// seek to ligature array
in.seekSet(subtableOffset + lao);
// read ligature count
int nl = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl );
}
// read ligature attach table offsets
int[] laoa = new int [ nl ];
for ( int i = 0; i < nl; i++ ) {
laoa [ i ] = in.readTTFUShort();
}
// iterate over ligature attach tables, recording maximum component count
int mxc = 0;
for ( int i = 0; i < nl; i++ ) {
int lato = laoa [ i ];
in.seekSet ( subtableOffset + lao + lato );
// read component count
int cc = in.readTTFUShort();
if ( cc > mxc ) {
mxc = cc;
}
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc );
}
// read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][];
for ( int i = 0; i < nl; i++ ) {
int lato = laoa [ i ];
// seek to ligature attach table for ligature[i]
in.seekSet ( subtableOffset + lao + lato );
// read component count
int cc = in.readTTFUShort();
GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ];
for ( int j = 0; j < cc; j++ ) {
for ( int k = 0; k < nmc; k++ ) {
// read ligature anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + lao + lato + ao );
} else {
a = null;
}
lcm [ j ] [ k ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a);
}
}
}
lam [ i ] = lcm;
}
// store results
seMapping = mct;
seEntries.add ( lct );
seEntries.add ( Integer.valueOf ( nmc ) );
seEntries.add ( Integer.valueOf ( mxc ) );
seEntries.add ( maa );
seEntries.add ( lam );
}
private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readMarkToLigaturePosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-ligature positioning subtable format: " + sf );
}
return sf;
}
private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read mark #1 coverage offset
int m1co = in.readTTFUShort();
// read mark #2 coverage offset
int m2co = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark #1 array offset
int m1ao = in.readTTFUShort();
// read mark #2 array offset
int m2ao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat );
log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co );
log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co );
log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc );
log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao );
log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao );
}
// read mark #1 coverage table
GlyphCoverageTable mct1 = readCoverageTable ( tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co );
// read mark #2 coverage table
GlyphCoverageTable mct2 = readCoverageTable ( tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co );
// read mark #1 anchor array
// seek to mark array
in.seekSet(subtableOffset + m1ao);
// read mark count
int nm1 = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1 );
}
// read mark anchor array, where i:{0...mark1Count}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ];
for ( int i = 0; i < nm1; i++ ) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + m1ao + ao );
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if ( a != null ) {
ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma);
}
}
// read mark #2 anchor matrix
// seek to mark #2 array
in.seekSet(subtableOffset + m2ao);
// read mark #2 count
int nm2 = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2 );
}
// read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ];
for ( int i = 0; i < nm2; i++ ) {
for ( int j = 0; j < nmc; j++ ) {
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if ( ao > 0 ) {
a = readPosAnchor ( subtableOffset + m2ao + ao );
} else {
a = null;
}
mam [ i ] [ j ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a);
}
}
}
// store results
seMapping = mct1;
seEntries.add ( mct2 );
seEntries.add ( Integer.valueOf ( nmc ) );
seEntries.add ( maa );
seEntries.add ( mam );
}
private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readMarkToMarkPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported mark-to-mark positioning subtable format: " + sf );
}
return sf;
}
private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for ( int i = 0; i < nrs; i++ ) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
log.debug(tableTag + " contextual positioning coverage table offset: " + co );
log.debug(tableTag + " contextual positioning rule set count: " + nrs );
for ( int i = 0; i < nrs; i++ ) {
log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
}
}
// read coverage table
GlyphCoverageTable ct;
if ( co > 0 ) {
ct = readCoverageTable ( tableTag + " contextual positioning coverage", subtableOffset + co );
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for ( int i = 0; i < nrs; i++ ) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if ( rso > 0 ) {
// seek to rule set [ i ]
in.seekSet ( subtableOffset + rso );
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for ( int j = 0; j < nr; j++ ) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for ( int j = 0; j < nr; j++ ) {
GlyphTable.GlyphSequenceRule r;
int ro = roa [ j ];
if ( ro > 0 ) {
// seek to rule [ j ]
in.seekSet ( subtableOffset + rso + ro );
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ ng - 1 ];
for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
glyphs [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs );
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet ( ra );
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add ( rsa );
}
private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
// read class def table offset
int cdo = in.readTTFUShort();
// read class rule set count
int ngc = in.readTTFUShort();
// read class rule set offsets
int[] csoa = new int [ ngc ];
for ( int i = 0; i < ngc; i++ ) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
log.debug(tableTag + " contextual positioning coverage table offset: " + co );
log.debug(tableTag + " contextual positioning class set count: " + ngc );
for ( int i = 0; i < ngc; i++ ) {
log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i] );
}
}
// read coverage table
GlyphCoverageTable ct;
if ( co > 0 ) {
ct = readCoverageTable ( tableTag + " contextual positioning coverage", subtableOffset + co );
} else {
ct = null;
}
// read class definition table
GlyphClassTable cdt;
if ( cdo > 0 ) {
cdt = readClassDefTable ( tableTag + " contextual positioning class definition", subtableOffset + cdo );
} else {
cdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for ( int i = 0; i < ngc; i++ ) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if ( cso > 0 ) {
// seek to rule set [ i ]
in.seekSet ( subtableOffset + cso );
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for ( int j = 0; j < nr; j++ ) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for ( int j = 0; j < nr; j++ ) {
int ro = roa [ j ];
GlyphTable.ClassSequenceRule r;
if ( ro > 0 ) {
// seek to rule [ j ]
in.seekSet ( subtableOffset + cso + ro );
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read classes
int[] classes = new int [ ng - 1 ];
for ( int k = 0, nk = classes.length; k < nk; k++ ) {
classes [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes );
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet ( ra );
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add ( cdt );
seEntries.add ( Integer.valueOf ( ngc ) );
seEntries.add ( rsa );
}
private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read glyph (input sequence length) count
int ng = in.readTTFUShort();
// read positioning lookup count
int nl = in.readTTFUShort();
// read glyph coverage offsets, one per glyph input sequence length count
int[] gcoa = new int [ ng ];
for ( int i = 0; i < ng; i++ ) {
gcoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng );
log.debug(tableTag + " contextual positioning lookup count: " + nl );
for ( int i = 0; i < ng; i++ ) {
log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i] );
}
}
// read coverage tables
GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
for ( int i = 0; i < ng; i++ ) {
int gco = gcoa [ i ];
GlyphCoverageTable gct;
if ( gco > 0 ) {
gct = readCoverageTable ( tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i] );
} else {
gct = null;
}
gca [ i ] = gct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca );
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert ( gca != null ) && ( gca.length > 0 );
seMapping = gca[0];
seEntries.add ( rsa );
}
private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readContextualPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 2 ) {
readContextualPosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 3 ) {
readContextualPosTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported contextual positioning subtable format: " + sf );
}
return sf;
}
private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for ( int i = 0; i < nrs; i++ ) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
log.debug(tableTag + " chained contextual positioning rule set count: " + nrs );
for ( int i = 0; i < nrs; i++ ) {
log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
}
}
// read coverage table
GlyphCoverageTable ct;
if ( co > 0 ) {
ct = readCoverageTable ( tableTag + " chained contextual positioning coverage", subtableOffset + co );
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for ( int i = 0; i < nrs; i++ ) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if ( rso > 0 ) {
// seek to rule set [ i ]
in.seekSet ( subtableOffset + rso );
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for ( int j = 0; j < nr; j++ ) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for ( int j = 0; j < nr; j++ ) {
GlyphTable.ChainedGlyphSequenceRule r;
int ro = roa [ j ];
if ( ro > 0 ) {
// seek to rule [ j ]
in.seekSet ( subtableOffset + rso + ro );
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyphs
int[] backtrackGlyphs = new int [ nbg ];
for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) {
backtrackGlyphs [ k ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ nig - 1 ];
for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
glyphs [ k ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read lookahead glyphs
int[] lookaheadGlyphs = new int [ nlg ];
for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) {
lookaheadGlyphs [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs );
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet ( ra );
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add ( rsa );
}
private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
// read backtrack class def table offset
int bcdo = in.readTTFUShort();
// read input class def table offset
int icdo = in.readTTFUShort();
// read lookahead class def table offset
int lcdo = in.readTTFUShort();
// read class set count
int ngc = in.readTTFUShort();
// read class set offsets
int[] csoa = new int [ ngc ];
for ( int i = 0; i < ngc; i++ ) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
log.debug(tableTag + " chained contextual positioning class set count: " + ngc );
for ( int i = 0; i < ngc; i++ ) {
log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i] );
}
}
// read coverage table
GlyphCoverageTable ct;
if ( co > 0 ) {
ct = readCoverageTable ( tableTag + " chained contextual positioning coverage", subtableOffset + co );
} else {
ct = null;
}
// read backtrack class definition table
GlyphClassTable bcdt;
if ( bcdo > 0 ) {
bcdt = readClassDefTable ( tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo );
} else {
bcdt = null;
}
// read input class definition table
GlyphClassTable icdt;
if ( icdo > 0 ) {
icdt = readClassDefTable ( tableTag + " contextual positioning input class definition", subtableOffset + icdo );
} else {
icdt = null;
}
// read lookahead class definition table
GlyphClassTable lcdt;
if ( lcdo > 0 ) {
lcdt = readClassDefTable ( tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo );
} else {
lcdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for ( int i = 0; i < ngc; i++ ) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if ( cso > 0 ) {
// seek to rule set [ i ]
in.seekSet ( subtableOffset + cso );
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for ( int j = 0; j < nr; j++ ) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for ( int j = 0; j < nr; j++ ) {
GlyphTable.ChainedClassSequenceRule r;
int ro = roa [ j ];
if ( ro > 0 ) {
// seek to rule [ j ]
in.seekSet ( subtableOffset + cso + ro );
// read backtrack glyph class count
int nbc = in.readTTFUShort();
// read backtrack glyph classes
int[] backtrackClasses = new int [ nbc ];
for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) {
backtrackClasses [ k ] = in.readTTFUShort();
}
// read input glyph class count
int nic = in.readTTFUShort();
// read input glyph classes
int[] classes = new int [ nic - 1 ];
for ( int k = 0, nk = classes.length; k < nk; k++ ) {
classes [ k ] = in.readTTFUShort();
}
// read lookahead glyph class count
int nlc = in.readTTFUShort();
// read lookahead glyph classes
int[] lookaheadClasses = new int [ nlc ];
for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) {
lookaheadClasses [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses );
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet ( ra );
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add ( icdt );
seEntries.add ( bcdt );
seEntries.add ( lcdt );
seEntries.add ( Integer.valueOf ( ngc ) );
seEntries.add ( rsa );
}
private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] bgcoa = new int [ nbg ];
for ( int i = 0; i < nbg; i++ ) {
bgcoa [ i ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] igcoa = new int [ nig ];
for ( int i = 0; i < nig; i++ ) {
igcoa [ i ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] lgcoa = new int [ nlg ];
for ( int i = 0; i < nlg; i++ ) {
lgcoa [ i ] = in.readTTFUShort();
}
// read positioning lookup count
int nl = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg );
for ( int i = 0; i < nbg; i++ ) {
log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
}
log.debug(tableTag + " chained contextual positioning input glyph count: " + nig );
for ( int i = 0; i < nig; i++ ) {
log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i] );
}
log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg );
for ( int i = 0; i < nlg; i++ ) {
log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
}
log.debug(tableTag + " chained contextual positioning lookup count: " + nl );
}
// read backtrack coverage tables
GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
for ( int i = 0; i < nbg; i++ ) {
int bgco = bgcoa [ i ];
GlyphCoverageTable bgct;
if ( bgco > 0 ) {
bgct = readCoverageTable ( tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco );
} else {
bgct = null;
}
bgca[i] = bgct;
}
// read input coverage tables
GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
for ( int i = 0; i < nig; i++ ) {
int igco = igcoa [ i ];
GlyphCoverageTable igct;
if ( igco > 0 ) {
igct = readCoverageTable ( tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco );
} else {
igct = null;
}
igca[i] = igct;
}
// read lookahead coverage tables
GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
for ( int i = 0; i < nlg; i++ ) {
int lgco = lgcoa [ i ];
GlyphCoverageTable lgct;
if ( lgco > 0 ) {
lgct = readCoverageTable ( tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco );
} else {
lgct = null;
}
lgca[i] = lgct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " chained contextual positioning lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups ( nl, header );
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca );
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert ( igca != null ) && ( igca.length > 0 );
seMapping = igca[0];
seEntries.add ( rsa );
}
private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readChainedContextualPosTableFormat1 ( lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 2 ) {
readChainedContextualPosTableFormat2 ( lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 3 ) {
readChainedContextualPosTableFormat3 ( lookupType, lookupFlags, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported chained contextual positioning subtable format: " + sf );
}
return sf;
}
private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read extension lookup type
int lt = in.readTTFUShort();
// read extension offset
long eo = in.readTTFULong();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " extension positioning subtable format: " + subtableFormat );
log.debug(tableTag + " extension positioning lookup type: " + lt );
log.debug(tableTag + " extension positioning lookup table offset: " + eo );
}
// read referenced subtable from extended offset
readGPOSSubtable ( lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo );
}
private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readExtensionPosTableFormat1 ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported extension positioning subtable format: " + sf );
}
return sf;
}
private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
initATSubState();
int subtableFormat = -1;
switch ( lookupType ) {
case GPOSLookupType.SINGLE:
subtableFormat = readSinglePosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.PAIR:
subtableFormat = readPairPosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.CURSIVE:
subtableFormat = readCursivePosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.MARK_TO_BASE:
subtableFormat = readMarkToBasePosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.MARK_TO_LIGATURE:
subtableFormat = readMarkToLigaturePosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.MARK_TO_MARK:
subtableFormat = readMarkToMarkPosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.CONTEXTUAL:
subtableFormat = readContextualPosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.CHAINED_CONTEXTUAL:
subtableFormat = readChainedContextualPosTable ( lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.EXTENSION:
subtableFormat = readExtensionPosTable ( lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset );
break;
default:
break;
}
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat );
resetATSubState();
}
private void readLookupTable(TTFTableName tableTag, int lookupSequence, long lookupTable) throws IOException {
boolean isGSUB = tableTag.equals ( TTFTableName.GSUB );
boolean isGPOS = tableTag.equals ( TTFTableName.GPOS );
in.seekSet(lookupTable);
// read lookup type
int lt = in.readTTFUShort();
// read lookup flags
int lf = in.readTTFUShort();
// read sub-table count
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
String lts;
if ( isGSUB ) {
lts = GSUBLookupType.toString ( lt );
} else if ( isGPOS ) {
lts = GPOSLookupType.toString ( lt );
} else {
lts = "?";
}
log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")" );
log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString ( lf ) + ")" );
log.debug(tableTag + " lookup table subtable count: " + ns );
}
// read subtable offsets
int[] soa = new int[ns];
for ( int i = 0; i < ns; i++ ) {
int so = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table subtable offset: " + so );
}
soa[i] = so;
}
// read mark filtering set
if ( ( lf & LookupFlag.USE_MARK_FILTERING_SET ) != 0 ) {
// read mark filtering set
int fs = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table mark filter set: " + fs );
}
}
// read subtables
for ( int i = 0; i < ns; i++ ) {
int so = soa[i];
if ( isGSUB ) {
readGSUBSubtable ( lt, lf, lookupSequence, i, lookupTable + so );
} else if ( isGPOS ) {
readGPOSSubtable ( lt, lf, lookupSequence, i, lookupTable + so );
}
}
}
private void readLookupList(TTFTableName tableTag, long lookupList) throws IOException {
in.seekSet(lookupList);
// read lookup record count
int nl = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup list record count: " + nl );
}
if ( nl > 0 ) {
int[] loa = new int[nl];
// read lookup records
for ( int i = 0, n = nl; i < n; i++ ) {
int lo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table offset: " + lo );
}
loa[i] = lo;
}
// read lookup tables
for ( int i = 0, n = nl; i < n; i++ ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup index: " + i );
}
readLookupTable ( tableTag, i, lookupList + loa [ i ] );
}
}
}
/**
* Read the common layout tables (used by GSUB and GPOS).
* @param tableTag tag of table being read
* @param scriptList offset to script list from beginning of font file
* @param featureList offset to feature list from beginning of font file
* @param lookupList offset to lookup list from beginning of font file
* @throws IOException In case of a I/O problem
*/
private void readCommonLayoutTables(TTFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException {
if ( scriptList > 0 ) {
readScriptList ( tableTag, scriptList );
}
if ( featureList > 0 ) {
readFeatureList ( tableTag, featureList );
}
if ( lookupList > 0 ) {
readLookupList ( tableTag, lookupList );
}
}
private void readGDEFClassDefTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
GlyphClassTable ct = readClassDefTable ( tableTag + " glyph class definition table", subtableOffset );
// store results
seMapping = ct;
// extract subtable
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1 );
resetATSubState();
}
private void readGDEFAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
int co = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " attachment point coverage table offset: " + co );
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable ( tableTag + " attachment point coverage", subtableOffset + co );
// store results
seMapping = ct;
// extract subtable
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1 );
resetATSubState();
}
private void readGDEFLigatureCaretTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
int co = in.readTTFUShort();
// read ligature glyph count
int nl = in.readTTFUShort();
// read ligature glyph table offsets
int[] lgto = new int [ nl ];
for ( int i = 0; i < nl; i++ ) {
lgto [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " ligature caret coverage table offset: " + co );
log.debug(tableTag + " ligature caret ligature glyph count: " + nl );
for ( int i = 0; i < nl; i++ ) {
log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i] );
}
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable ( tableTag + " ligature caret coverage", subtableOffset + co );
// store results
seMapping = ct;
// extract subtable
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1 );
resetATSubState();
}
private void readGDEFMarkAttachmentTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
GlyphClassTable ct = readClassDefTable ( tableTag + " glyph class definition table", subtableOffset );
// store results
seMapping = ct;
// extract subtable
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
resetATSubState();
}
private void readGDEFMarkGlyphsTableFormat1(TTFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read mark set class count
int nmc = in.readTTFUShort();
long[] mso = new long [ nmc ];
// read mark set coverage offsets
for ( int i = 0; i < nmc; i++ ) {
mso [ i ] = in.readTTFULong();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)" );
log.debug(tableTag + " mark set class count: " + nmc );
for ( int i = 0; i < nmc; i++ ) {
log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i] );
}
}
// read mark set coverage tables, one per class
GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc];
for ( int i = 0; i < nmc; i++ ) {
msca[i] = readCoverageTable ( tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i] );
}
// create combined class table from per-class coverage tables
GlyphClassTable ct = GlyphClassTable.createClassTable ( Arrays.asList ( msca ) );
// store results
seMapping = ct;
// extract subtable
extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
resetATSubState();
}
private void readGDEFMarkGlyphsTable(TTFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read mark set subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readGDEFMarkGlyphsTableFormat1 ( tableTag, lookupSequence, subtableOffset, sf );
} else {
throw new AdvancedTypographicTableFormatException ( "unsupported mark glyph sets subtable format: " + sf );
}
}
/**
* Read the GDEF table.
* @throws IOException In case of a I/O problem
*/
private void readGDEF() throws IOException {
TTFTableName tableTag = TTFTableName.GDEF;
// Initialize temporary state
initATState();
// Read glyph definition (GDEF) table
TTFDirTabEntry dirTab = ttf.getDirectoryEntry( tableTag );
if ( gdef != null ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
ttf.seekTab(in, tableTag, 0);
long version = in.readTTFULong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
}
// glyph class definition table offset (may be null)
int cdo = in.readTTFUShort();
// attach point list offset (may be null)
int apo = in.readTTFUShort();
// ligature caret list offset (may be null)
int lco = in.readTTFUShort();
// mark attach class definition table offset (may be null)
int mao = in.readTTFUShort();
// mark glyph sets definition table offset (may be null)
int mgo;
if ( version >= 0x00010002 ) {
mgo = in.readTTFUShort();
} else {
mgo = 0;
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " glyph class definition table offset: " + cdo );
log.debug(tableTag + " attachment point list offset: " + apo );
log.debug(tableTag + " ligature caret list offset: " + lco );
log.debug(tableTag + " mark attachment class definition table offset: " + mao );
log.debug(tableTag + " mark glyph set definitions table offset: " + mgo );
}
// initialize subtable sequence number
int seqno = 0;
// obtain offset to start of gdef table
long to = dirTab.getOffset();
// (optionally) read glyph class definition subtable
if ( cdo != 0 ) {
readGDEFClassDefTable ( tableTag, seqno++, to + cdo );
}
// (optionally) read glyph attachment point subtable
if ( apo != 0 ) {
readGDEFAttachmentTable ( tableTag, seqno++, to + apo );
}
// (optionally) read ligature caret subtable
if ( lco != 0 ) {
readGDEFLigatureCaretTable ( tableTag, seqno++, to + lco );
}
// (optionally) read mark attachment class subtable
if ( mao != 0 ) {
readGDEFMarkAttachmentTable ( tableTag, seqno++, to + mao );
}
// (optionally) read mark glyph sets subtable
if ( mgo != 0 ) {
readGDEFMarkGlyphsTable ( tableTag, seqno++, to + mgo );
}
GlyphDefinitionTable gdef;
if ( ( gdef = constructGDEF() ) != null ) {
this.gdef = gdef;
}
}
}
/**
* Read the GSUB table.
* @throws IOException In case of a I/O problem
*/
private void readGSUB() throws IOException {
TTFTableName tableTag = TTFTableName.GSUB;
// Initialize temporary state
initATState();
// Read glyph substitution (GSUB) table
TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
if ( gpos != null ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
ttf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
}
int slo = in.readTTFUShort();
int flo = in.readTTFUShort();
int llo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script list offset: " + slo );
log.debug(tableTag + " feature list offset: " + flo );
log.debug(tableTag + " lookup list offset: " + llo );
}
long to = dirTab.getOffset();
readCommonLayoutTables ( tableTag, to + slo, to + flo, to + llo );
GlyphSubstitutionTable gsub;
if ( ( gsub = constructGSUB() ) != null ) {
this.gsub = gsub;
}
}
}
/**
* Read the GPOS table.
* @throws IOException In case of a I/O problem
*/
private void readGPOS() throws IOException {
TTFTableName tableTag = TTFTableName.GPOS;
// Initialize temporary state
initATState();
// Read glyph positioning (GPOS) table
TTFDirTabEntry dirTab = ttf.getDirectoryEntry ( tableTag );
if ( gpos != null ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
ttf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
}
int slo = in.readTTFUShort();
int flo = in.readTTFUShort();
int llo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script list offset: " + slo );
log.debug(tableTag + " feature list offset: " + flo );
log.debug(tableTag + " lookup list offset: " + llo );
}
long to = dirTab.getOffset();
readCommonLayoutTables ( tableTag, to + slo, to + flo, to + llo );
GlyphPositioningTable gpos;
if ( ( gpos = constructGPOS() ) != null ) {
this.gpos = gpos;
}
}
}
/**
* Construct the (internal representation of the) GDEF table based on previously
* parsed state.
* @returns glyph definition table or null if insufficient or invalid state
*/
private GlyphDefinitionTable constructGDEF() {
GlyphDefinitionTable gdef = null;
List subtables;
if ( ( subtables = constructGDEFSubtables() ) != null ) {
if ( subtables.size() > 0 ) {
gdef = new GlyphDefinitionTable ( subtables );
}
}
resetATState();
return gdef;
}
/**
* Construct the (internal representation of the) GSUB table based on previously
* parsed state.
* @returns glyph substitution table or null if insufficient or invalid state
*/
private GlyphSubstitutionTable constructGSUB() {
GlyphSubstitutionTable gsub = null;
Map lookups;
if ( ( lookups = constructLookups() ) != null ) {
List subtables;
if ( ( subtables = constructGSUBSubtables() ) != null ) {
if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
gsub = new GlyphSubstitutionTable ( gdef, lookups, subtables );
}
}
}
resetATState();
return gsub;
}
/**
* Construct the (internal representation of the) GPOS table based on previously
* parsed state.
* @returns glyph positioning table or null if insufficient or invalid state
*/
private GlyphPositioningTable constructGPOS() {
GlyphPositioningTable gpos = null;
Map lookups;
if ( ( lookups = constructLookups() ) != null ) {
List subtables;
if ( ( subtables = constructGPOSSubtables() ) != null ) {
if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
gpos = new GlyphPositioningTable ( gdef, lookups, subtables );
}
}
}
resetATState();
return gpos;
}
private void constructLookupsFeature ( Map lookups, String st, String lt, String fid ) {
Object[] fp = (Object[]) seFeatures.get ( fid );
if ( fp != null ) {
assert fp.length == 2;
String ft = (String) fp[0]; // feature tag
List/*<String>*/ lul = (List) fp[1]; // list of lookup table ids
if ( ( ft != null ) && ( lul != null ) && ( lul.size() > 0 ) ) {
GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec ( st, lt, ft );
lookups.put ( ls, lul );
}
}
}
private void constructLookupsFeatures ( Map lookups, String st, String lt, List/*<String>*/ fids ) {
for ( Iterator fit = fids.iterator(); fit.hasNext();) {
String fid = (String) fit.next();
constructLookupsFeature ( lookups, st, lt, fid );
}
}
private void constructLookupsLanguage ( Map lookups, String st, String lt, Map/*<String,Object[2]>*/ languages ) {
Object[] lp = (Object[]) languages.get ( lt );
if ( lp != null ) {
assert lp.length == 2;
if ( lp[0] != null ) { // required feature id
constructLookupsFeature ( lookups, st, lt, (String) lp[0] );
}
if ( lp[1] != null ) { // non-required features ids
constructLookupsFeatures ( lookups, st, lt, (List) lp[1] );
}
}
}
private void constructLookupsLanguages ( Map lookups, String st, List/*<String>*/ ll, Map/*<String,Object[2]>*/ languages ) {
for ( Iterator lit = ll.iterator(); lit.hasNext();) {
String lt = (String) lit.next();
constructLookupsLanguage ( lookups, st, lt, languages );
}
}
private Map constructLookups() {
Map/*<GlyphTable.LookupSpec,List<String>>*/ lookups = new java.util.LinkedHashMap();
for ( Iterator sit = seScripts.keySet().iterator(); sit.hasNext();) {
String st = (String) sit.next();
Object[] sp = (Object[]) seScripts.get ( st );
if ( sp != null ) {
assert sp.length == 3;
Map/*<String,Object[2]>*/ languages = (Map) sp[2];
if ( sp[0] != null ) { // default language
constructLookupsLanguage ( lookups, st, (String) sp[0], languages );
}
if ( sp[1] != null ) { // non-default languages
constructLookupsLanguages ( lookups, st, (List) sp[1], languages );
}
}
}
return lookups;
}
private List constructGDEFSubtables() {
List/*<GlyphDefinitionSubtable>*/ subtables = new java.util.ArrayList();
if ( seSubtables != null ) {
for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
Object[] stp = (Object[]) it.next();
GlyphSubtable st;
if ( ( st = constructGDEFSubtable ( stp ) ) != null ) {
subtables.add ( st );
}
}
}
return subtables;
}
private GlyphSubtable constructGDEFSubtable ( Object[] stp ) {
GlyphSubtable st = null;
assert ( stp != null ) && ( stp.length == 8 );
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphMappingTable mapping = (GlyphMappingTable) stp[6];
List entries = (List) stp[7];
if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) {
int type = GDEFLookupType.getSubtableType ( lt.intValue() );
String lid = "lu" + ln.intValue();
int sequence = sn.intValue();
int flags = lf.intValue();
int format = sf.intValue();
st = GlyphDefinitionTable.createSubtable ( type, lid, sequence, flags, format, mapping, entries );
}
return st;
}
private List constructGSUBSubtables() {
List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
if ( seSubtables != null ) {
for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
Object[] stp = (Object[]) it.next();
GlyphSubtable st;
if ( ( st = constructGSUBSubtable ( stp ) ) != null ) {
subtables.add ( st );
}
}
}
return subtables;
}
private GlyphSubtable constructGSUBSubtable ( Object[] stp ) {
GlyphSubtable st = null;
assert ( stp != null ) && ( stp.length == 8 );
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
List entries = (List) stp[7];
if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
int type = GSUBLookupType.getSubtableType ( lt.intValue() );
String lid = "lu" + ln.intValue();
int sequence = sn.intValue();
int flags = lf.intValue();
int format = sf.intValue();
st = GlyphSubstitutionTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
}
return st;
}
private List constructGPOSSubtables() {
List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
if ( seSubtables != null ) {
for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
Object[] stp = (Object[]) it.next();
GlyphSubtable st;
if ( ( st = constructGPOSSubtable ( stp ) ) != null ) {
subtables.add ( st );
}
}
}
return subtables;
}
private GlyphSubtable constructGPOSSubtable ( Object[] stp ) {
GlyphSubtable st = null;
assert ( stp != null ) && ( stp.length == 8 );
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
List entries = (List) stp[7];
if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
int type = GSUBLookupType.getSubtableType ( lt.intValue() );
String lid = "lu" + ln.intValue();
int sequence = sn.intValue();
int flags = lf.intValue();
int format = sf.intValue();
st = GlyphPositioningTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
}
return st;
}
private void initATState() {
seScripts = new java.util.LinkedHashMap();
seLanguages = new java.util.LinkedHashMap();
seFeatures = new java.util.LinkedHashMap();
seSubtables = new java.util.ArrayList();
resetATSubState();
}
private void resetATState() {
seScripts = null;
seLanguages = null;
seFeatures = null;
seSubtables = null;
resetATSubState();
}
private void initATSubState() {
seMapping = null;
seEntries = new java.util.ArrayList();
}
private void extractSESubState ( int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat ) {
if ( seEntries != null ) {
if ( ( tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) || ( seEntries.size() > 0 ) ) {
if ( seSubtables != null ) {
Integer tt = Integer.valueOf ( tableType );
Integer lt = Integer.valueOf ( lookupType );
Integer ln = Integer.valueOf ( lookupSequence );
Integer lf = Integer.valueOf ( lookupFlags );
Integer sn = Integer.valueOf ( subtableSequence );
Integer sf = Integer.valueOf ( subtableFormat );
seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries } );
}
}
}
}
private void resetATSubState() {
seMapping = null;
seEntries = null;
}
private void resetATStateAll() {
resetATState();
gdef = null;
gsub = null;
gpos = null;
}
/** helper method for formatting an integer array for output */
private String toString ( int[] ia ) {
StringBuffer sb = new StringBuffer();
if ( ( ia == null ) || ( ia.length == 0 ) ) {
sb.append ( '-' );
} else {
boolean first = true;
for ( int i = 0; i < ia.length; i++ ) {
if ( ! first ) {
sb.append ( ' ' );
} else {
first = false;
}
sb.append ( ia[i] );
}
}
return sb.toString();
}
}