blob: 0607f998e7208147f91f6b135712560ac3197a19 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.complexscripts.fonts;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.complexscripts.scripts.ScriptProcessor;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.OFDirTabEntry;
import org.apache.fop.fonts.truetype.OFTableName;
import org.apache.fop.fonts.truetype.OpenFont;
// CSOFF: LineLengthCheck
/**
* <p>OpenType Font (OTF) advanced typographic table reader. Used by @{Link org.apache.fop.fonts.truetype.TTFFile}
* to read advanced typographic tables (GDEF, GSUB, GPOS).</p>
*
* <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p>
*/
public final class OTFAdvancedTypographicTableReader {
// logging state
private static Log log = LogFactory.getLog(OTFAdvancedTypographicTableReader.class);
// instance state
private OpenFont otf; // parent font file reader
private FontFileReader in; // input reader
private GlyphDefinitionTable gdef; // glyph definition table
private GlyphSubstitutionTable gsub; // glyph substitution table
private GlyphPositioningTable gpos; // glyph positioning table
// transient parsing state
private transient Map<String, Object> seScripts; // script-tag => Object[3] : { default-language-tag, List(language-tag), seLanguages }
private transient Map<String, Object> seLanguages; // language-tag => Object[2] : { "f<required-feature-index>", List("f<feature-index>")
private transient Map<String, Object> seFeatures; // "f<feature-index>" => Object[2] : { feature-tag, List("lu<lookup-index>") }
private transient GlyphMappingTable seMapping; // subtable entry mappings
private transient List seEntries; // subtable entry entries
private transient List seSubtables; // subtable entry subtables
private Map<String, ScriptProcessor> processors = new HashMap<String, ScriptProcessor>();
/**
* Construct an <code>OTFAdvancedTypographicTableReader</code> instance.
* @param otf parent font file reader (must be non-null)
* @param in font file reader (must be non-null)
*/
public OTFAdvancedTypographicTableReader(OpenFont otf, FontFileReader in) {
assert otf != null;
assert in != null;
this.otf = otf;
this.in = in;
}
/**
* Read all advanced typographic tables.
* @throws AdvancedTypographicTableFormatException if ATT table has invalid format
*/
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(OFTableName tableTag, long langSysTable, String langSysTag)
throws IOException {
in.seekSet(langSysTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys table: " + langSysTag);
}
// read lookup order (reorder) table offset
int lo = in.readTTFUShort();
// read required feature index
int rf = in.readTTFUShort();
String rfi;
if (rf != 65535) {
rfi = "f" + rf;
} else {
rfi = null;
}
// read (non-required) feature count
int nf = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys table reorder table: " + lo);
log.debug(tableTag + " lang sys table required feature index: " + rf);
log.debug(tableTag + " lang sys table non-required feature count: " + nf);
}
// read (non-required) feature indices
List fl = new java.util.ArrayList();
for (int i = 0; i < nf; i++) {
int fi = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys table non-required feature index: " + fi);
}
fl.add("f" + fi);
}
if (seLanguages == null) {
seLanguages = new java.util.LinkedHashMap();
}
seLanguages.put(langSysTag, new Object[] { rfi, fl });
}
private static String defaultTag = "dflt";
private void readScriptTable(OFTableName tableTag, long scriptTable, String scriptTag) throws IOException {
in.seekSet(scriptTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " script table: " + scriptTag);
}
// read default language system table offset
int dl = in.readTTFUShort();
String dt = defaultTag;
if (dl > 0) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " default lang sys tag: " + dt);
log.debug(tableTag + " default lang sys table offset: " + dl);
}
}
// read language system record count
int nl = in.readTTFUShort();
List ll = new java.util.ArrayList();
if (nl > 0) {
String[] lta = new String[nl];
int[] loa = new int[nl];
// read language system records
for (int i = 0, n = nl; i < n; i++) {
String lt = in.readTTFString(4);
int lo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys tag: " + lt);
log.debug(tableTag + " lang sys table offset: " + lo);
}
lta[i] = lt;
loa[i] = lo;
if (dl == lo) {
dl = 0;
dt = lt;
}
ll.add(lt);
}
// read non-default language system tables
for (int i = 0, n = nl; i < n; i++) {
readLangSysTable(tableTag, scriptTable + loa [ i ], lta [ i ]);
}
}
// read default language system table (if specified)
if (dl > 0) {
readLangSysTable(tableTag, scriptTable + dl, dt);
} else if (dt != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " lang sys default: " + dt);
}
}
if (seLanguages != null) {
seScripts.put(scriptTag, new Object[]{dt, ll, seLanguages});
}
seLanguages = null;
}
private void readScriptList(OFTableName tableTag, long scriptList) throws IOException {
in.seekSet(scriptList);
// read script record count
int ns = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script list record count: " + ns);
}
if (ns > 0) {
String[] sta = new String[ns];
int[] soa = new int[ns];
// read script records
for (int i = 0, n = ns; i < n; i++) {
String st = in.readTTFString(4);
int so = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script tag: " + st);
log.debug(tableTag + " script table offset: " + so);
}
sta[i] = st;
soa[i] = so;
}
// read script tables
for (int i = 0, n = ns; i < n; i++) {
seLanguages = null;
readScriptTable(tableTag, scriptList + soa [ i ], sta [ i ]);
}
}
}
private void readFeatureTable(OFTableName tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
in.seekSet(featureTable);
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature table: " + featureTag);
}
// read feature params offset
int po = in.readTTFUShort();
// read lookup list indices count
int nl = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature table parameters offset: " + po);
log.debug(tableTag + " feature table lookup list index count: " + nl);
}
// read lookup table indices
List lul = new java.util.ArrayList();
for (int i = 0; i < nl; i++) {
int li = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature table lookup index: " + li);
}
lul.add("lu" + li);
}
seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul });
}
private void readFeatureList(OFTableName tableTag, long featureList) throws IOException {
in.seekSet(featureList);
// read feature record count
int nf = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature list record count: " + nf);
}
if (nf > 0) {
String[] fta = new String[nf];
int[] foa = new int[nf];
// read feature records
for (int i = 0, n = nf; i < n; i++) {
String ft = in.readTTFString(4);
int fo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature tag: " + ft);
log.debug(tableTag + " feature table offset: " + fo);
}
fta[i] = ft;
foa[i] = fo;
}
// read feature tables
for (int i = 0, n = nf; i < n; i++) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " feature index: " + i);
}
readFeatureTable(tableTag, featureList + foa [ i ], fta [ i ], i);
}
}
}
static final class GDEFLookupType {
static final int GLYPH_CLASS = 1;
static final int ATTACHMENT_POINT = 2;
static final int LIGATURE_CARET = 3;
static final int MARK_ATTACHMENT = 4;
private GDEFLookupType() {
}
public static int getSubtableType(int lt) {
int st;
switch (lt) {
case GDEFLookupType.GLYPH_CLASS:
st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS;
break;
case GDEFLookupType.ATTACHMENT_POINT:
st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
break;
case GDEFLookupType.LIGATURE_CARET:
st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET;
break;
case GDEFLookupType.MARK_ATTACHMENT:
st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
break;
default:
st = -1;
break;
}
return st;
}
public static String toString(int type) {
String s;
switch (type) {
case GLYPH_CLASS:
s = "GlyphClass";
break;
case ATTACHMENT_POINT:
s = "AttachmentPoint";
break;
case LIGATURE_CARET:
s = "LigatureCaret";
break;
case MARK_ATTACHMENT:
s = "MarkAttachment";
break;
default:
s = "?";
break;
}
return s;
}
}
static final class GSUBLookupType {
static final int SINGLE = 1;
static final int MULTIPLE = 2;
static final int ALTERNATE = 3;
static final int LIGATURE = 4;
static final int CONTEXTUAL = 5;
static final int CHAINED_CONTEXTUAL = 6;
static final int EXTENSION = 7;
static final int REVERSE_CHAINED_SINGLE = 8;
private GSUBLookupType() {
}
public static int getSubtableType(int lt) {
int st;
switch (lt) {
case GSUBLookupType.SINGLE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE;
break;
case GSUBLookupType.MULTIPLE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE;
break;
case GSUBLookupType.ALTERNATE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE;
break;
case GSUBLookupType.LIGATURE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE;
break;
case GSUBLookupType.CONTEXTUAL:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL;
break;
case GSUBLookupType.CHAINED_CONTEXTUAL:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
break;
case GSUBLookupType.EXTENSION:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
break;
case GSUBLookupType.REVERSE_CHAINED_SINGLE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
break;
default:
st = -1;
break;
}
return st;
}
public static String toString(int type) {
String s;
switch (type) {
case SINGLE:
s = "Single";
break;
case MULTIPLE:
s = "Multiple";
break;
case ALTERNATE:
s = "Alternate";
break;
case LIGATURE:
s = "Ligature";
break;
case CONTEXTUAL:
s = "Contextual";
break;
case CHAINED_CONTEXTUAL:
s = "ChainedContextual";
break;
case EXTENSION:
s = "Extension";
break;
case REVERSE_CHAINED_SINGLE:
s = "ReverseChainedSingle";
break;
default:
s = "?";
break;
}
return s;
}
}
static final class GPOSLookupType {
static final int SINGLE = 1;
static final int PAIR = 2;
static final int CURSIVE = 3;
static final int MARK_TO_BASE = 4;
static final int MARK_TO_LIGATURE = 5;
static final int MARK_TO_MARK = 6;
static final int CONTEXTUAL = 7;
static final int CHAINED_CONTEXTUAL = 8;
static final int EXTENSION = 9;
private GPOSLookupType() {
}
public static String toString(int type) {
String s;
switch (type) {
case SINGLE:
s = "Single";
break;
case PAIR:
s = "Pair";
break;
case CURSIVE:
s = "Cursive";
break;
case MARK_TO_BASE:
s = "MarkToBase";
break;
case MARK_TO_LIGATURE:
s = "MarkToLigature";
break;
case MARK_TO_MARK:
s = "MarkToMark";
break;
case CONTEXTUAL:
s = "Contextual";
break;
case CHAINED_CONTEXTUAL:
s = "ChainedContextual";
break;
case EXTENSION:
s = "Extension";
break;
default:
s = "?";
break;
}
return s;
}
}
static final class LookupFlag {
static final int RIGHT_TO_LEFT = 0x0001;
static final int IGNORE_BASE_GLYPHS = 0x0002;
static final int IGNORE_LIGATURE = 0x0004;
static final int IGNORE_MARKS = 0x0008;
static final int USE_MARK_FILTERING_SET = 0x0010;
static final int MARK_ATTACHMENT_TYPE = 0xFF00;
private LookupFlag() {
}
public static String toString(int flags) {
StringBuffer sb = new StringBuffer();
boolean first = true;
if ((flags & RIGHT_TO_LEFT) != 0) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append("RightToLeft");
}
if ((flags & IGNORE_BASE_GLYPHS) != 0) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append("IgnoreBaseGlyphs");
}
if ((flags & IGNORE_LIGATURE) != 0) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append("IgnoreLigature");
}
if ((flags & IGNORE_MARKS) != 0) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append("IgnoreMarks");
}
if ((flags & USE_MARK_FILTERING_SET) != 0) {
if (first) {
first = false;
} else {
sb.append('|');
}
sb.append("UseMarkFilteringSet");
}
if (sb.length() == 0) {
sb.append('-');
}
return sb.toString();
}
}
private GlyphCoverageTable readCoverageTableFormat1(String label, long tableOffset, int coverageFormat) throws IOException {
List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip(2);
// read glyph count
int ng = in.readTTFUShort();
int[] ga = new int[ng];
for (int i = 0, n = ng; i < n; i++) {
int g = in.readTTFUShort();
ga[i] = g;
entries.add(g);
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(label + " glyphs: " + toString(ga));
}
return GlyphCoverageTable.createCoverageTable(entries);
}
private GlyphCoverageTable readCoverageTableFormat2(String label, long tableOffset, int coverageFormat) throws IOException {
List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip(2);
// read range record count
int nr = in.readTTFUShort();
for (int i = 0, n = nr; i < n; i++) {
// read range start
int s = in.readTTFUShort();
// read range end
int e = in.readTTFUShort();
// read range coverage (mapping) index
int m = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m);
}
entries.add(new GlyphCoverageTable.MappingRange(s, e, m));
}
return GlyphCoverageTable.createCoverageTable(entries);
}
private GlyphCoverageTable readCoverageTable(String label, long tableOffset) throws IOException {
GlyphCoverageTable gct;
long cp = in.getCurrentPos();
in.seekSet(tableOffset);
// read coverage table format
int cf = in.readTTFUShort();
if (cf == 1) {
gct = readCoverageTableFormat1(label, tableOffset, cf);
} else if (cf == 2) {
gct = readCoverageTableFormat2(label, tableOffset, cf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported coverage table format: " + cf);
}
in.seekSet(cp);
return gct;
}
private GlyphClassTable readClassDefTableFormat1(String label, long tableOffset, int classFormat) throws IOException {
List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip(2);
// read start glyph
int sg = in.readTTFUShort();
entries.add(sg);
// read glyph count
int ng = in.readTTFUShort();
// read glyph classes
int[] ca = new int[ng];
for (int i = 0, n = ng; i < n; i++) {
int gc = in.readTTFUShort();
ca[i] = gc;
entries.add(gc);
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(label + " glyph classes: " + toString(ca));
}
return GlyphClassTable.createClassTable(entries);
}
private GlyphClassTable readClassDefTableFormat2(String label, long tableOffset, int classFormat) throws IOException {
List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip(2);
// read range record count
int nr = in.readTTFUShort();
for (int i = 0, n = nr; i < n; i++) {
// read range start
int s = in.readTTFUShort();
// read range end
int e = in.readTTFUShort();
// read range glyph class (mapping) index
int m = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m);
}
entries.add(new GlyphClassTable.MappingRange(s, e, m));
}
return GlyphClassTable.createClassTable(entries);
}
private GlyphClassTable readClassDefTable(String label, long tableOffset) throws IOException {
GlyphClassTable gct;
long cp = in.getCurrentPos();
in.seekSet(tableOffset);
// read class table format
int cf = in.readTTFUShort();
if (cf == 1) {
gct = readClassDefTableFormat1(label, tableOffset, cf);
} else if (cf == 2) {
gct = readClassDefTableFormat2(label, tableOffset, cf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported class definition table format: " + cf);
}
in.seekSet(cp);
return gct;
}
private void readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read delta glyph
int dg = in.readTTFShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)");
log.debug(tableTag + " single substitution coverage table offset: " + co);
log.debug(tableTag + " single substitution delta: " + dg);
}
// read coverage table
seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co);
seEntries.add(dg);
}
private void readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read glyph count
int ng = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)");
log.debug(tableTag + " single substitution coverage table offset: " + co);
log.debug(tableTag + " single substitution glyph count: " + ng);
}
// read coverage table
seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co);
// read glyph substitutions
for (int i = 0, n = ng; i < n; i++) {
int gs = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs);
}
seEntries.add(gs);
}
}
private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readSingleSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported single substitution subtable format: " + sf);
}
return sf;
}
private void readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read sequence count
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)");
log.debug(tableTag + " multiple substitution coverage table offset: " + co);
log.debug(tableTag + " multiple substitution sequence count: " + ns);
}
// read coverage table
seMapping = readCoverageTable(tableTag + " multiple substitution coverage", subtableOffset + co);
// read sequence table offsets
int[] soa = new int[ns];
for (int i = 0, n = ns; i < n; i++) {
soa[i] = in.readTTFUShort();
}
// read sequence tables
int[][] gsa = new int [ ns ] [];
for (int i = 0, n = ns; i < n; i++) {
int so = soa[i];
int[] ga;
if (so > 0) {
in.seekSet(subtableOffset + so);
// read glyph count
int ng = in.readTTFUShort();
ga = new int[ng];
for (int j = 0; j < ng; j++) {
ga[j] = in.readTTFUShort();
}
} else {
ga = null;
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString(ga));
}
gsa [ i ] = ga;
}
seEntries.add(gsa);
}
private int readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readMultipleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported multiple substitution subtable format: " + sf);
}
return sf;
}
private void readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read alternate set count
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)");
log.debug(tableTag + " alternate substitution coverage table offset: " + co);
log.debug(tableTag + " alternate substitution alternate set count: " + ns);
}
// read coverage table
seMapping = readCoverageTable(tableTag + " alternate substitution coverage", subtableOffset + co);
// read alternate set table offsets
int[] soa = new int[ns];
for (int i = 0, n = ns; i < n; i++) {
soa[i] = in.readTTFUShort();
}
// read alternate set tables
for (int i = 0, n = ns; i < n; i++) {
int so = soa[i];
in.seekSet(subtableOffset + so);
// read glyph count
int ng = in.readTTFUShort();
int[] ga = new int[ng];
for (int j = 0; j < ng; j++) {
int gs = in.readTTFUShort();
ga[j] = gs;
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString(ga));
}
seEntries.add(ga);
}
}
private int readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readAlternateSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported alternate substitution subtable format: " + sf);
}
return sf;
}
private void readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read ligature set count
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)");
log.debug(tableTag + " ligature substitution coverage table offset: " + co);
log.debug(tableTag + " ligature substitution ligature set count: " + ns);
}
// read coverage table
seMapping = readCoverageTable(tableTag + " ligature substitution coverage", subtableOffset + co);
// read ligature set table offsets
int[] soa = new int[ns];
for (int i = 0, n = ns; i < n; i++) {
soa[i] = in.readTTFUShort();
}
// read ligature set tables
for (int i = 0, n = ns; i < n; i++) {
int so = soa[i];
in.seekSet(subtableOffset + so);
// read ligature table count
int nl = in.readTTFUShort();
int[] loa = new int[nl];
for (int j = 0; j < nl; j++) {
loa[j] = in.readTTFUShort();
}
List ligs = new java.util.ArrayList();
for (int j = 0; j < nl; j++) {
int lo = loa[j];
in.seekSet(subtableOffset + so + lo);
// read ligature glyph id
int lg = in.readTTFUShort();
// read ligature (input) component count
int nc = in.readTTFUShort();
int[] ca = new int [ nc - 1 ];
// read ligature (input) component glyph ids
for (int k = 0; k < nc - 1; k++) {
ca[k] = in.readTTFUShort();
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString(ca));
}
ligs.add(new GlyphSubstitutionTable.Ligature(lg, ca));
}
seEntries.add(new GlyphSubstitutionTable.LigatureSet(ligs));
}
}
private int readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readLigatureSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported ligature substitution subtable format: " + sf);
}
return sf;
}
private GlyphTable.RuleLookup[] readRuleLookups(int numLookups, String header) throws IOException {
GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ];
for (int i = 0, n = numLookups; i < n; i++) {
int sequenceIndex = in.readTTFUShort();
int lookupIndex = in.readTTFUShort();
la [ i ] = new GlyphTable.RuleLookup(sequenceIndex, lookupIndex);
// dump info if debugging and header is non-null
if (log.isDebugEnabled() && (header != null)) {
log.debug(header + "lookup[" + i + "]: " + la[i]);
}
}
return la;
}
private void readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for (int i = 0; i < nrs; i++) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)");
log.debug(tableTag + " contextual substitution coverage table offset: " + co);
log.debug(tableTag + " contextual substitution rule set count: " + nrs);
for (int i = 0; i < nrs; i++) {
log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co);
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for (int i = 0; i < nrs; i++) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if (rso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + rso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
GlyphTable.GlyphSequenceRule r;
int ro = roa [ j ];
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + rso + ro);
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ ng - 1 ];
for (int k = 0, nk = glyphs.length; k < nk; k++) {
glyphs [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(rsa);
}
private void readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read class def table offset
int cdo = in.readTTFUShort();
// read class rule set count
int ngc = in.readTTFUShort();
// read class rule set offsets
int[] csoa = new int [ ngc ];
for (int i = 0; i < ngc; i++) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)");
log.debug(tableTag + " contextual substitution coverage table offset: " + co);
log.debug(tableTag + " contextual substitution class set count: " + ngc);
for (int i = 0; i < ngc; i++) {
log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co);
} else {
ct = null;
}
// read class definition table
GlyphClassTable cdt;
if (cdo > 0) {
cdt = readClassDefTable(tableTag + " contextual substitution class definition", subtableOffset + cdo);
} else {
cdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for (int i = 0; i < ngc; i++) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if (cso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + cso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
int ro = roa [ j ];
GlyphTable.ClassSequenceRule r;
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + cso + ro);
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read classes
int[] classes = new int [ ng - 1 ];
for (int k = 0, nk = classes.length; k < nk; k++) {
classes [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ClassSequenceRule(lookups, ng, classes);
} else {
assert ro > 0 : "unexpected null subclass rule offset";
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(cdt);
seEntries.add(ngc);
seEntries.add(rsa);
}
private void readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read glyph (input sequence length) count
int ng = in.readTTFUShort();
// read substitution lookup count
int nl = in.readTTFUShort();
// read glyph coverage offsets, one per glyph input sequence length count
int[] gcoa = new int [ ng ];
for (int i = 0; i < ng; i++) {
gcoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)");
log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng);
log.debug(tableTag + " contextual substitution lookup count: " + nl);
for (int i = 0; i < ng; i++) {
log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i]);
}
}
// read coverage tables
GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
for (int i = 0; i < ng; i++) {
int gco = gcoa [ i ];
GlyphCoverageTable gct;
if (gco > 0) {
gct = readCoverageTable(tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco);
} else {
gct = null;
}
gca [ i ] = gct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " contextual substitution lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca);
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r});
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert (gca != null) && (gca.length > 0);
seMapping = gca[0];
seEntries.add(rsa);
}
private int readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 3) {
readContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported contextual substitution subtable format: " + sf);
}
return sf;
}
private void readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for (int i = 0; i < nrs; i++) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)");
log.debug(tableTag + " chained contextual substitution coverage table offset: " + co);
log.debug(tableTag + " chained contextual substitution rule set count: " + nrs);
for (int i = 0; i < nrs; i++) {
log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co);
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for (int i = 0; i < nrs; i++) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if (rso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + rso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
GlyphTable.ChainedGlyphSequenceRule r;
int ro = roa [ j ];
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + rso + ro);
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyphs
int[] backtrackGlyphs = new int [ nbg ];
for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) {
backtrackGlyphs [ k ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ nig - 1 ];
for (int k = 0, nk = glyphs.length; k < nk; k++) {
glyphs [ k ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read lookahead glyphs
int[] lookaheadGlyphs = new int [ nlg ];
for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) {
lookaheadGlyphs [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(rsa);
}
private void readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read backtrack class def table offset
int bcdo = in.readTTFUShort();
// read input class def table offset
int icdo = in.readTTFUShort();
// read lookahead class def table offset
int lcdo = in.readTTFUShort();
// read class set count
int ngc = in.readTTFUShort();
// read class set offsets
int[] csoa = new int [ ngc ];
for (int i = 0; i < ngc; i++) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)");
log.debug(tableTag + " chained contextual substitution coverage table offset: " + co);
log.debug(tableTag + " chained contextual substitution class set count: " + ngc);
for (int i = 0; i < ngc; i++) {
log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co);
} else {
ct = null;
}
// read backtrack class definition table
GlyphClassTable bcdt;
if (bcdo > 0) {
bcdt = readClassDefTable(tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo);
} else {
bcdt = null;
}
// read input class definition table
GlyphClassTable icdt;
if (icdo > 0) {
icdt = readClassDefTable(tableTag + " contextual substitution input class definition", subtableOffset + icdo);
} else {
icdt = null;
}
// read lookahead class definition table
GlyphClassTable lcdt;
if (lcdo > 0) {
lcdt = readClassDefTable(tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo);
} else {
lcdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for (int i = 0; i < ngc; i++) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if (cso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + cso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
int ro = roa [ j ];
GlyphTable.ChainedClassSequenceRule r;
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + cso + ro);
// read backtrack glyph class count
int nbc = in.readTTFUShort();
// read backtrack glyph classes
int[] backtrackClasses = new int [ nbc ];
for (int k = 0, nk = backtrackClasses.length; k < nk; k++) {
backtrackClasses [ k ] = in.readTTFUShort();
}
// read input glyph class count
int nic = in.readTTFUShort();
// read input glyph classes
int[] classes = new int [ nic - 1 ];
for (int k = 0, nk = classes.length; k < nk; k++) {
classes [ k ] = in.readTTFUShort();
}
// read lookahead glyph class count
int nlc = in.readTTFUShort();
// read lookahead glyph classes
int[] lookaheadClasses = new int [ nlc ];
for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) {
lookaheadClasses [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(icdt);
seEntries.add(bcdt);
seEntries.add(lcdt);
seEntries.add(ngc);
seEntries.add(rsa);
}
private void readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] bgcoa = new int [ nbg ];
for (int i = 0; i < nbg; i++) {
bgcoa [ i ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read input glyph coverage offsets
int[] igcoa = new int [ nig ];
for (int i = 0; i < nig; i++) {
igcoa [ i ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read lookahead glyph coverage offsets
int[] lgcoa = new int [ nlg ];
for (int i = 0; i < nlg; i++) {
lgcoa [ i ] = in.readTTFUShort();
}
// read substitution lookup count
int nl = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)");
log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg);
for (int i = 0; i < nbg; i++) {
log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
}
log.debug(tableTag + " chained contextual substitution input glyph count: " + nig);
for (int i = 0; i < nig; i++) {
log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i]);
}
log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg);
for (int i = 0; i < nlg; i++) {
log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
}
log.debug(tableTag + " chained contextual substitution lookup count: " + nl);
}
// read backtrack coverage tables
GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
for (int i = 0; i < nbg; i++) {
int bgco = bgcoa [ i ];
GlyphCoverageTable bgct;
if (bgco > 0) {
bgct = readCoverageTable(tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco);
} else {
bgct = null;
}
bgca[i] = bgct;
}
// read input coverage tables
GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
for (int i = 0; i < nig; i++) {
int igco = igcoa [ i ];
GlyphCoverageTable igct;
if (igco > 0) {
igct = readCoverageTable(tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco);
} else {
igct = null;
}
igca[i] = igct;
}
// read lookahead coverage tables
GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
for (int i = 0; i < nlg; i++) {
int lgco = lgcoa [ i ];
GlyphCoverageTable lgct;
if (lgco > 0) {
lgct = readCoverageTable(tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco);
} else {
lgct = null;
}
lgca[i] = lgct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " chained contextual substitution lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca);
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r});
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert (igca != null) && (igca.length > 0);
seMapping = igca[0];
seEntries.add(rsa);
}
private int readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readChainedContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readChainedContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 3) {
readChainedContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported chained contextual substitution subtable format: " + sf);
}
return sf;
}
private void readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read extension lookup type
int lt = in.readTTFUShort();
// read extension offset
long eo = in.readTTFULong();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " extension substitution subtable format: " + subtableFormat);
log.debug(tableTag + " extension substitution lookup type: " + lt);
log.debug(tableTag + " extension substitution lookup table offset: " + eo);
}
// read referenced subtable from extended offset
readGSUBSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo);
}
private int readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readExtensionSubTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported extension substitution subtable format: " + sf);
}
return sf;
}
private void readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] bgcoa = new int [ nbg ];
for (int i = 0; i < nbg; i++) {
bgcoa [ i ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] lgcoa = new int [ nlg ];
for (int i = 0; i < nlg; i++) {
lgcoa [ i ] = in.readTTFUShort();
}
// read substitution (output) glyph count
int ng = in.readTTFUShort();
// read substitution (output) glyphs
int[] glyphs = new int [ ng ];
for (int i = 0, n = ng; i < n; i++) {
glyphs [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat);
log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co);
log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg);
for (int i = 0; i < nbg; i++) {
log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
}
log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg);
for (int i = 0; i < nlg; i++) {
log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
}
log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs));
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " reverse chained contextual substitution coverage", subtableOffset + co);
// read backtrack coverage tables
GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
for (int i = 0; i < nbg; i++) {
int bgco = bgcoa[i];
GlyphCoverageTable bgct;
if (bgco > 0) {
bgct = readCoverageTable(tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco);
} else {
bgct = null;
}
bgca[i] = bgct;
}
// read lookahead coverage tables
GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
for (int i = 0; i < nlg; i++) {
int lgco = lgcoa[i];
GlyphCoverageTable lgct;
if (lgco > 0) {
lgct = readCoverageTable(tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco);
} else {
lgct = null;
}
lgca[i] = lgct;
}
// store results
seMapping = ct;
seEntries.add(bgca);
seEntries.add(lgca);
seEntries.add(glyphs);
}
private int readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read substitution subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readReverseChainedSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported reverse chained single substitution subtable format: " + sf);
}
return sf;
}
private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
initATSubState();
int subtableFormat = -1;
switch (lookupType) {
case GSUBLookupType.SINGLE:
subtableFormat = readSingleSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.MULTIPLE:
subtableFormat = readMultipleSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.ALTERNATE:
subtableFormat = readAlternateSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.LIGATURE:
subtableFormat = readLigatureSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.CONTEXTUAL:
subtableFormat = readContextualSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.CHAINED_CONTEXTUAL:
subtableFormat = readChainedContextualSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.REVERSE_CHAINED_SINGLE:
subtableFormat = readReverseChainedSingleSubTable(lookupType, lookupFlags, subtableOffset);
break;
case GSUBLookupType.EXTENSION:
subtableFormat = readExtensionSubTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset);
break;
default:
break;
}
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat);
resetATSubState();
}
private GlyphPositioningTable.DeviceTable readPosDeviceTable(long subtableOffset, long deviceTableOffset) throws IOException {
long cp = in.getCurrentPos();
in.seekSet(subtableOffset + deviceTableOffset);
// read start size
int ss = in.readTTFUShort();
// read end size
int es = in.readTTFUShort();
// read delta format
int df = in.readTTFUShort();
int s1;
int m1;
int dm;
int dd;
int s2;
if (df == 1) {
s1 = 14;
m1 = 0x3;
dm = 1;
dd = 4;
s2 = 2;
} else if (df == 2) {
s1 = 12;
m1 = 0xF;
dm = 7;
dd = 16;
s2 = 4;
} else if (df == 3) {
s1 = 8;
m1 = 0xFF;
dm = 127;
dd = 256;
s2 = 8;
} else {
log.debug("unsupported device table delta format: " + df + ", ignoring device table");
return null;
}
// read deltas
int n = (es - ss) + 1;
if (n < 0) {
log.debug("invalid device table delta count: " + n + ", ignoring device table");
return null;
}
int[] da = new int [ n ];
for (int i = 0; (i < n) && (s2 > 0);) {
int p = in.readTTFUShort();
for (int j = 0, k = 16 / s2; j < k; j++) {
int d = (p >> s1) & m1;
if (d > dm) {
d -= dd;
}
if (i < n) {
da [ i++ ] = d;
} else {
break;
}
p <<= s2;
}
}
in.seekSet(cp);
return new GlyphPositioningTable.DeviceTable(ss, es, da);
}
private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException {
// XPlacement
int xp;
if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) {
xp = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
xp = 0;
}
// YPlacement
int yp;
if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) {
yp = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
yp = 0;
}
// XAdvance
int xa;
if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) {
xa = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
xa = 0;
}
// YAdvance
int ya;
if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) {
ya = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
} else {
ya = 0;
}
// XPlaDevice
GlyphPositioningTable.DeviceTable xpd;
if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE) != 0) {
int xpdo = in.readTTFUShort();
xpd = readPosDeviceTable(subtableOffset, xpdo);
} else {
xpd = null;
}
// YPlaDevice
GlyphPositioningTable.DeviceTable ypd;
if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE) != 0) {
int ypdo = in.readTTFUShort();
ypd = readPosDeviceTable(subtableOffset, ypdo);
} else {
ypd = null;
}
// XAdvDevice
GlyphPositioningTable.DeviceTable xad;
if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE) != 0) {
int xado = in.readTTFUShort();
xad = readPosDeviceTable(subtableOffset, xado);
} else {
xad = null;
}
// YAdvDevice
GlyphPositioningTable.DeviceTable yad;
if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE) != 0) {
int yado = in.readTTFUShort();
yad = readPosDeviceTable(subtableOffset, yado);
} else {
yad = null;
}
return new GlyphPositioningTable.Value(xp, yp, xa, ya, xpd, ypd, xad, yad);
}
private void readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read value format
int vf = in.readTTFUShort();
// read value
GlyphPositioningTable.Value v = readPosValue(subtableOffset, vf);
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)");
log.debug(tableTag + " single positioning coverage table offset: " + co);
log.debug(tableTag + " single positioning value: " + v);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co);
// store results
seMapping = ct;
seEntries.add(v);
}
private void readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read value format
int vf = in.readTTFUShort();
// read value count
int nv = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)");
log.debug(tableTag + " single positioning coverage table offset: " + co);
log.debug(tableTag + " single positioning value count: " + nv);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co);
// read positioning values
GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv];
for (int i = 0, n = nv; i < n; i++) {
GlyphPositioningTable.Value pv = readPosValue(subtableOffset, vf);
if (log.isDebugEnabled()) {
log.debug(tableTag + " single positioning value[" + i + "]: " + pv);
}
pva[i] = pv;
}
// store results
seMapping = ct;
seEntries.add(pva);
}
private int readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positionining subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readSinglePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readSinglePosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported single positioning subtable format: " + sf);
}
return sf;
}
private GlyphPositioningTable.PairValues readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException {
// read glyph (if present)
int glyph;
if (hasGlyph) {
glyph = in.readTTFUShort();
} else {
glyph = 0;
}
// read first value (if present)
GlyphPositioningTable.Value v1;
if (vf1 != 0) {
v1 = readPosValue(subtableOffset, vf1);
} else {
v1 = null;
}
// read second value (if present)
GlyphPositioningTable.Value v2;
if (vf2 != 0) {
v2 = readPosValue(subtableOffset, vf2);
} else {
v2 = null;
}
return new GlyphPositioningTable.PairValues(glyph, v1, v2);
}
private GlyphPositioningTable.PairValues[] readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException {
String tableTag = "GPOS";
long cp = in.getCurrentPos();
in.seekSet(subtableOffset + pairSetTableOffset);
// read pair values count
int npv = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " pair set table offset: " + pairSetTableOffset);
log.debug(tableTag + " pair set table values count: " + npv);
}
// read pair values
GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ];
for (int i = 0, n = npv; i < n; i++) {
GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, true, vf1, vf2);
pva [ i ] = pv;
if (log.isDebugEnabled()) {
log.debug(tableTag + " pair set table value[" + i + "]: " + pv);
}
}
in.seekSet(cp);
return pva;
}
private void readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read value format for first glyph
int vf1 = in.readTTFUShort();
// read value format for second glyph
int vf2 = in.readTTFUShort();
// read number (count) of pair sets
int nps = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)");
log.debug(tableTag + " pair positioning coverage table offset: " + co);
log.debug(tableTag + " pair positioning value format #1: " + vf1);
log.debug(tableTag + " pair positioning value format #2: " + vf2);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co);
// read pair value matrix
GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][];
for (int i = 0, n = nps; i < n; i++) {
// read pair set offset
int pso = in.readTTFUShort();
// read pair set table at offset
pvm [ i ] = readPosPairSetTable(subtableOffset, pso, vf1, vf2);
}
// store results
seMapping = ct;
seEntries.add(pvm);
}
private void readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read value format for first glyph
int vf1 = in.readTTFUShort();
// read value format for second glyph
int vf2 = in.readTTFUShort();
// read class def 1 offset
int cd1o = in.readTTFUShort();
// read class def 2 offset
int cd2o = in.readTTFUShort();
// read number (count) of classes in class def 1 table
int nc1 = in.readTTFUShort();
// read number (count) of classes in class def 2 table
int nc2 = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)");
log.debug(tableTag + " pair positioning coverage table offset: " + co);
log.debug(tableTag + " pair positioning value format #1: " + vf1);
log.debug(tableTag + " pair positioning value format #2: " + vf2);
log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o);
log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o);
log.debug(tableTag + " pair positioning class #1 count: " + nc1);
log.debug(tableTag + " pair positioning class #2 count: " + nc2);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co);
// read class definition table #1
GlyphClassTable cdt1 = readClassDefTable(tableTag + " pair positioning class definition #1", subtableOffset + cd1o);
// read class definition table #2
GlyphClassTable cdt2 = readClassDefTable(tableTag + " pair positioning class definition #2", subtableOffset + cd2o);
// read pair value matrix
GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ];
for (int i = 0; i < nc1; i++) {
for (int j = 0; j < nc2; j++) {
GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, false, vf1, vf2);
pvm [ i ] [ j ] = pv;
if (log.isDebugEnabled()) {
log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv);
}
}
}
// store results
seMapping = ct;
seEntries.add(cdt1);
seEntries.add(cdt2);
seEntries.add(nc1);
seEntries.add(nc2);
seEntries.add(pvm);
}
private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readPairPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readPairPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported pair positioning subtable format: " + sf);
}
return sf;
}
private GlyphPositioningTable.Anchor readPosAnchor(long anchorTableOffset) throws IOException {
GlyphPositioningTable.Anchor a;
long cp = in.getCurrentPos();
in.seekSet(anchorTableOffset);
// read anchor table format
int af = in.readTTFUShort();
if (af == 1) {
// read x coordinate
int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
a = new GlyphPositioningTable.Anchor(x, y);
} else if (af == 2) {
// read x coordinate
int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read anchor point index
int ap = in.readTTFUShort();
a = new GlyphPositioningTable.Anchor(x, y, ap);
} else if (af == 3) {
// read x coordinate
int x = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read y coordinate
int y = otf.convertTTFUnit2PDFUnit(in.readTTFShort());
// read x device table offset
int xdo = in.readTTFUShort();
// read y device table offset
int ydo = in.readTTFUShort();
// read x device table (if present)
GlyphPositioningTable.DeviceTable xd;
if (xdo != 0) {
xd = readPosDeviceTable(cp, xdo);
} else {
xd = null;
}
// read y device table (if present)
GlyphPositioningTable.DeviceTable yd;
if (ydo != 0) {
yd = readPosDeviceTable(cp, ydo);
} else {
yd = null;
}
a = new GlyphPositioningTable.Anchor(x, y, xd, yd);
} else {
throw new AdvancedTypographicTableFormatException("unsupported positioning anchor format: " + af);
}
in.seekSet(cp);
return a;
}
private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read entry/exit count
int ec = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat);
log.debug(tableTag + " cursive positioning coverage table offset: " + co);
log.debug(tableTag + " cursive positioning entry/exit count: " + ec);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " cursive positioning coverage", subtableOffset + co);
// read entry/exit records
GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ];
for (int i = 0, n = ec; i < n; i++) {
// read entry anchor offset
int eno = in.readTTFUShort();
// read exit anchor offset
int exo = in.readTTFUShort();
// read entry anchor
GlyphPositioningTable.Anchor ena;
if (eno > 0) {
ena = readPosAnchor(subtableOffset + eno);
} else {
ena = null;
}
// read exit anchor
GlyphPositioningTable.Anchor exa;
if (exo > 0) {
exa = readPosAnchor(subtableOffset + exo);
} else {
exa = null;
}
aa [ (i * 2) + 0 ] = ena;
aa [ (i * 2) + 1 ] = exa;
if (log.isDebugEnabled()) {
if (ena != null) {
log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena);
}
if (exa != null) {
log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa);
}
}
}
// store results
seMapping = ct;
seEntries.add(aa);
}
private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readCursivePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported cursive positioning subtable format: " + sf);
}
return sf;
}
private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read mark coverage offset
int mco = in.readTTFUShort();
// read base coverage offset
int bco = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark array offset
int mao = in.readTTFUShort();
// read base array offset
int bao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat);
log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco);
log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco);
log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc);
log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao);
log.debug(tableTag + " mark-to-base positioning base array offset: " + bao);
}
// read mark coverage table
GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco);
// read base coverage table
GlyphCoverageTable bct = readCoverageTable(tableTag + " mark-to-base positioning base coverage", subtableOffset + bco);
// read mark anchor array
// seek to mark array
in.seekSet(subtableOffset + mao);
// read mark count
int nm = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning mark count: " + nm);
}
// read mark anchor array, where i:{0...markCount}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
for (int i = 0; i < nm; i++) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + mao + ao);
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if (a != null) {
ma = new GlyphPositioningTable.MarkAnchor(mc, a);
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma);
}
}
// read base anchor matrix
// seek to base array
in.seekSet(subtableOffset + bao);
// read base count
int nb = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning base count: " + nb);
}
// read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ];
for (int i = 0; i < nb; i++) {
for (int j = 0; j < nmc; j++) {
// read base anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + bao + ao);
} else {
a = null;
}
bam [ i ] [ j ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a);
}
}
}
// store results
seMapping = mct;
seEntries.add(bct);
seEntries.add(nmc);
seEntries.add(maa);
seEntries.add(bam);
}
private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readMarkToBasePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported mark-to-base positioning subtable format: " + sf);
}
return sf;
}
private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read mark coverage offset
int mco = in.readTTFUShort();
// read ligature coverage offset
int lco = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark array offset
int mao = in.readTTFUShort();
// read ligature array offset
int lao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat);
log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco);
log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco);
log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc);
log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao);
log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao);
}
// read mark coverage table
GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco);
// read ligature coverage table
GlyphCoverageTable lct = readCoverageTable(tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco);
// read mark anchor array
// seek to mark array
in.seekSet(subtableOffset + mao);
// read mark count
int nm = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm);
}
// read mark anchor array, where i:{0...markCount}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
for (int i = 0; i < nm; i++) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + mao + ao);
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if (a != null) {
ma = new GlyphPositioningTable.MarkAnchor(mc, a);
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma);
}
}
// read ligature anchor matrix
// seek to ligature array
in.seekSet(subtableOffset + lao);
// read ligature count
int nl = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl);
}
// read ligature attach table offsets
int[] laoa = new int [ nl ];
for (int i = 0; i < nl; i++) {
laoa [ i ] = in.readTTFUShort();
}
// iterate over ligature attach tables, recording maximum component count
int mxc = 0;
for (int i = 0; i < nl; i++) {
int lato = laoa [ i ];
in.seekSet(subtableOffset + lao + lato);
// read component count
int cc = in.readTTFUShort();
if (cc > mxc) {
mxc = cc;
}
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc);
}
// read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][];
for (int i = 0; i < nl; i++) {
int lato = laoa [ i ];
// seek to ligature attach table for ligature[i]
in.seekSet(subtableOffset + lao + lato);
// read component count
int cc = in.readTTFUShort();
GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ];
for (int j = 0; j < cc; j++) {
for (int k = 0; k < nmc; k++) {
// read ligature anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + lao + lato + ao);
} else {
a = null;
}
lcm [ j ] [ k ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a);
}
}
}
lam [ i ] = lcm;
}
// store results
seMapping = mct;
seEntries.add(lct);
seEntries.add(nmc);
seEntries.add(mxc);
seEntries.add(maa);
seEntries.add(lam);
}
private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readMarkToLigaturePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported mark-to-ligature positioning subtable format: " + sf);
}
return sf;
}
private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read mark #1 coverage offset
int m1co = in.readTTFUShort();
// read mark #2 coverage offset
int m2co = in.readTTFUShort();
// read mark class count
int nmc = in.readTTFUShort();
// read mark #1 array offset
int m1ao = in.readTTFUShort();
// read mark #2 array offset
int m2ao = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat);
log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co);
log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co);
log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc);
log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao);
log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao);
}
// read mark #1 coverage table
GlyphCoverageTable mct1 = readCoverageTable(tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co);
// read mark #2 coverage table
GlyphCoverageTable mct2 = readCoverageTable(tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co);
// read mark #1 anchor array
// seek to mark array
in.seekSet(subtableOffset + m1ao);
// read mark count
int nm1 = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1);
}
// read mark anchor array, where i:{0...mark1Count}
GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ];
for (int i = 0; i < nm1; i++) {
// read mark class
int mc = in.readTTFUShort();
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + m1ao + ao);
} else {
a = null;
}
GlyphPositioningTable.MarkAnchor ma;
if (a != null) {
ma = new GlyphPositioningTable.MarkAnchor(mc, a);
} else {
ma = null;
}
maa [ i ] = ma;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma);
}
}
// read mark #2 anchor matrix
// seek to mark #2 array
in.seekSet(subtableOffset + m2ao);
// read mark #2 count
int nm2 = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2);
}
// read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1}
GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ];
for (int i = 0; i < nm2; i++) {
for (int j = 0; j < nmc; j++) {
// read mark anchor offset
int ao = in.readTTFUShort();
GlyphPositioningTable.Anchor a;
if (ao > 0) {
a = readPosAnchor(subtableOffset + m2ao + ao);
} else {
a = null;
}
mam [ i ] [ j ] = a;
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a);
}
}
}
// store results
seMapping = mct1;
seEntries.add(mct2);
seEntries.add(nmc);
seEntries.add(maa);
seEntries.add(mam);
}
private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readMarkToMarkPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported mark-to-mark positioning subtable format: " + sf);
}
return sf;
}
private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for (int i = 0; i < nrs; i++) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)");
log.debug(tableTag + " contextual positioning coverage table offset: " + co);
log.debug(tableTag + " contextual positioning rule set count: " + nrs);
for (int i = 0; i < nrs; i++) {
log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co);
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for (int i = 0; i < nrs; i++) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if (rso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + rso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
GlyphTable.GlyphSequenceRule r;
int ro = roa [ j ];
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + rso + ro);
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ ng - 1 ];
for (int k = 0, nk = glyphs.length; k < nk; k++) {
glyphs [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.GlyphSequenceRule(lookups, ng, glyphs);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(rsa);
}
private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read class def table offset
int cdo = in.readTTFUShort();
// read class rule set count
int ngc = in.readTTFUShort();
// read class rule set offsets
int[] csoa = new int [ ngc ];
for (int i = 0; i < ngc; i++) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)");
log.debug(tableTag + " contextual positioning coverage table offset: " + co);
log.debug(tableTag + " contextual positioning class set count: " + ngc);
for (int i = 0; i < ngc; i++) {
log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co);
} else {
ct = null;
}
// read class definition table
GlyphClassTable cdt;
if (cdo > 0) {
cdt = readClassDefTable(tableTag + " contextual positioning class definition", subtableOffset + cdo);
} else {
cdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for (int i = 0; i < ngc; i++) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if (cso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + cso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
int ro = roa [ j ];
GlyphTable.ClassSequenceRule r;
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + cso + ro);
// read glyph count
int ng = in.readTTFUShort();
// read rule lookup count
int nl = in.readTTFUShort();
// read classes
int[] classes = new int [ ng - 1 ];
for (int k = 0, nk = classes.length; k < nk; k++) {
classes [ k ] = in.readTTFUShort();
}
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ClassSequenceRule(lookups, ng, classes);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(cdt);
seEntries.add(ngc);
seEntries.add(rsa);
}
private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read glyph (input sequence length) count
int ng = in.readTTFUShort();
// read positioning lookup count
int nl = in.readTTFUShort();
// read glyph coverage offsets, one per glyph input sequence length count
int[] gcoa = new int [ ng ];
for (int i = 0; i < ng; i++) {
gcoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)");
log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng);
log.debug(tableTag + " contextual positioning lookup count: " + nl);
for (int i = 0; i < ng; i++) {
log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i]);
}
}
// read coverage tables
GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
for (int i = 0; i < ng; i++) {
int gco = gcoa [ i ];
GlyphCoverageTable gct;
if (gco > 0) {
gct = readCoverageTable(tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i]);
} else {
gct = null;
}
gca [ i ] = gct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule(lookups, ng, gca);
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r});
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert (gca != null) && (gca.length > 0);
seMapping = gca[0];
seEntries.add(rsa);
}
private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 3) {
readContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported contextual positioning subtable format: " + sf);
}
return sf;
}
private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read rule set count
int nrs = in.readTTFUShort();
// read rule set offsets
int[] rsoa = new int [ nrs ];
for (int i = 0; i < nrs; i++) {
rsoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)");
log.debug(tableTag + " chained contextual positioning coverage table offset: " + co);
log.debug(tableTag + " chained contextual positioning rule set count: " + nrs);
for (int i = 0; i < nrs; i++) {
log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co);
} else {
ct = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
String header = null;
for (int i = 0; i < nrs; i++) {
GlyphTable.RuleSet rs;
int rso = rsoa [ i ];
if (rso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + rso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
GlyphTable.ChainedGlyphSequenceRule r;
int ro = roa [ j ];
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + rso + ro);
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyphs
int[] backtrackGlyphs = new int [ nbg ];
for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) {
backtrackGlyphs [ k ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read glyphs
int[] glyphs = new int [ nig - 1 ];
for (int k = 0, nk = glyphs.length; k < nk; k++) {
glyphs [ k ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read lookahead glyphs
int[] lookaheadGlyphs = new int [ nlg ];
for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) {
lookaheadGlyphs [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(rsa);
}
private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read coverage offset
int co = in.readTTFUShort();
// read backtrack class def table offset
int bcdo = in.readTTFUShort();
// read input class def table offset
int icdo = in.readTTFUShort();
// read lookahead class def table offset
int lcdo = in.readTTFUShort();
// read class set count
int ngc = in.readTTFUShort();
// read class set offsets
int[] csoa = new int [ ngc ];
for (int i = 0; i < ngc; i++) {
csoa [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)");
log.debug(tableTag + " chained contextual positioning coverage table offset: " + co);
log.debug(tableTag + " chained contextual positioning class set count: " + ngc);
for (int i = 0; i < ngc; i++) {
log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i]);
}
}
// read coverage table
GlyphCoverageTable ct;
if (co > 0) {
ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co);
} else {
ct = null;
}
// read backtrack class definition table
GlyphClassTable bcdt;
if (bcdo > 0) {
bcdt = readClassDefTable(tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo);
} else {
bcdt = null;
}
// read input class definition table
GlyphClassTable icdt;
if (icdo > 0) {
icdt = readClassDefTable(tableTag + " contextual positioning input class definition", subtableOffset + icdo);
} else {
icdt = null;
}
// read lookahead class definition table
GlyphClassTable lcdt;
if (lcdo > 0) {
lcdt = readClassDefTable(tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo);
} else {
lcdt = null;
}
// read rule sets
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
String header = null;
for (int i = 0; i < ngc; i++) {
int cso = csoa [ i ];
GlyphTable.RuleSet rs;
if (cso > 0) {
// seek to rule set [ i ]
in.seekSet(subtableOffset + cso);
// read rule count
int nr = in.readTTFUShort();
// read rule offsets
int[] roa = new int [ nr ];
GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
for (int j = 0; j < nr; j++) {
roa [ j ] = in.readTTFUShort();
}
// read glyph sequence rules
for (int j = 0; j < nr; j++) {
GlyphTable.ChainedClassSequenceRule r;
int ro = roa [ j ];
if (ro > 0) {
// seek to rule [ j ]
in.seekSet(subtableOffset + cso + ro);
// read backtrack glyph class count
int nbc = in.readTTFUShort();
// read backtrack glyph classes
int[] backtrackClasses = new int [ nbc ];
for (int k = 0, nk = backtrackClasses.length; k < nk; k++) {
backtrackClasses [ k ] = in.readTTFUShort();
}
// read input glyph class count
int nic = in.readTTFUShort();
// read input glyph classes
int[] classes = new int [ nic - 1 ];
for (int k = 0, nk = classes.length; k < nk; k++) {
classes [ k ] = in.readTTFUShort();
}
// read lookahead glyph class count
int nlc = in.readTTFUShort();
// read lookahead glyph classes
int[] lookaheadClasses = new int [ nlc ];
for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) {
lookaheadClasses [ k ] = in.readTTFUShort();
}
// read rule lookup count
int nl = in.readTTFUShort();
// read rule lookups
if (log.isDebugEnabled()) {
header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
r = new GlyphTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses);
} else {
r = null;
}
ra [ j ] = r;
}
rs = new GlyphTable.HomogeneousRuleSet(ra);
} else {
rs = null;
}
rsa [ i ] = rs;
}
// store results
seMapping = ct;
seEntries.add(icdt);
seEntries.add(bcdt);
seEntries.add(lcdt);
seEntries.add(ngc);
seEntries.add(rsa);
}
private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read backtrack glyph count
int nbg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] bgcoa = new int [ nbg ];
for (int i = 0; i < nbg; i++) {
bgcoa [ i ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] igcoa = new int [ nig ];
for (int i = 0; i < nig; i++) {
igcoa [ i ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
// read backtrack glyph coverage offsets
int[] lgcoa = new int [ nlg ];
for (int i = 0; i < nlg; i++) {
lgcoa [ i ] = in.readTTFUShort();
}
// read positioning lookup count
int nl = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)");
log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg);
for (int i = 0; i < nbg; i++) {
log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
}
log.debug(tableTag + " chained contextual positioning input glyph count: " + nig);
for (int i = 0; i < nig; i++) {
log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i]);
}
log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg);
for (int i = 0; i < nlg; i++) {
log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
}
log.debug(tableTag + " chained contextual positioning lookup count: " + nl);
}
// read backtrack coverage tables
GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
for (int i = 0; i < nbg; i++) {
int bgco = bgcoa [ i ];
GlyphCoverageTable bgct;
if (bgco > 0) {
bgct = readCoverageTable(tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco);
} else {
bgct = null;
}
bgca[i] = bgct;
}
// read input coverage tables
GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
for (int i = 0; i < nig; i++) {
int igco = igcoa [ i ];
GlyphCoverageTable igct;
if (igco > 0) {
igct = readCoverageTable(tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco);
} else {
igct = null;
}
igca[i] = igct;
}
// read lookahead coverage tables
GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
for (int i = 0; i < nlg; i++) {
int lgco = lgcoa [ i ];
GlyphCoverageTable lgct;
if (lgco > 0) {
lgct = readCoverageTable(tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco);
} else {
lgct = null;
}
lgca[i] = lgct;
}
// read rule lookups
String header = null;
if (log.isDebugEnabled()) {
header = tableTag + " chained contextual positioning lookups: ";
}
GlyphTable.RuleLookup[] lookups = readRuleLookups(nl, header);
// construct rule, rule set, and rule set array
GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca);
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet(new GlyphTable.Rule[] {r});
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
// store results
assert (igca != null) && (igca.length > 0);
seMapping = igca[0];
seEntries.add(rsa);
}
private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readChainedContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 2) {
readChainedContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
} else if (sf == 3) {
readChainedContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported chained contextual positioning subtable format: " + sf);
}
return sf;
}
private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GPOS";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read extension lookup type
int lt = in.readTTFUShort();
// read extension offset
long eo = in.readTTFULong();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " extension positioning subtable format: " + subtableFormat);
log.debug(tableTag + " extension positioning lookup type: " + lt);
log.debug(tableTag + " extension positioning lookup table offset: " + eo);
}
// read referenced subtable from extended offset
readGPOSSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo);
}
private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read positioning subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readExtensionPosTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported extension positioning subtable format: " + sf);
}
return sf;
}
private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
initATSubState();
int subtableFormat = -1;
switch (lookupType) {
case GPOSLookupType.SINGLE:
subtableFormat = readSinglePosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.PAIR:
subtableFormat = readPairPosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.CURSIVE:
subtableFormat = readCursivePosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.MARK_TO_BASE:
subtableFormat = readMarkToBasePosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.MARK_TO_LIGATURE:
subtableFormat = readMarkToLigaturePosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.MARK_TO_MARK:
subtableFormat = readMarkToMarkPosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.CONTEXTUAL:
subtableFormat = readContextualPosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.CHAINED_CONTEXTUAL:
subtableFormat = readChainedContextualPosTable(lookupType, lookupFlags, subtableOffset);
break;
case GPOSLookupType.EXTENSION:
subtableFormat = readExtensionPosTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset);
break;
default:
break;
}
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat);
resetATSubState();
}
private void readLookupTable(OFTableName tableTag, int lookupSequence, long lookupTable) throws IOException {
boolean isGSUB = tableTag.equals(OFTableName.GSUB);
boolean isGPOS = tableTag.equals(OFTableName.GPOS);
in.seekSet(lookupTable);
// read lookup type
int lt = in.readTTFUShort();
// read lookup flags
int lf = in.readTTFUShort();
// read sub-table count
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
String lts;
if (isGSUB) {
lts = GSUBLookupType.toString(lt);
} else if (isGPOS) {
lts = GPOSLookupType.toString(lt);
} else {
lts = "?";
}
log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")");
log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString(lf) + ")");
log.debug(tableTag + " lookup table subtable count: " + ns);
}
// read subtable offsets
int[] soa = new int[ns];
for (int i = 0; i < ns; i++) {
int so = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table subtable offset: " + so);
}
soa[i] = so;
}
// read mark filtering set
if ((lf & LookupFlag.USE_MARK_FILTERING_SET) != 0) {
// read mark filtering set
int fs = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table mark filter set: " + fs);
}
}
// read subtables
for (int i = 0; i < ns; i++) {
int so = soa[i];
if (isGSUB) {
readGSUBSubtable(lt, lf, lookupSequence, i, lookupTable + so);
} else if (isGPOS) {
readGPOSSubtable(lt, lf, lookupSequence, i, lookupTable + so);
}
}
}
private void readLookupList(OFTableName tableTag, long lookupList) throws IOException {
in.seekSet(lookupList);
// read lookup record count
int nl = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup list record count: " + nl);
}
if (nl > 0) {
int[] loa = new int[nl];
// read lookup records
for (int i = 0, n = nl; i < n; i++) {
int lo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup table offset: " + lo);
}
loa[i] = lo;
}
// read lookup tables
for (int i = 0, n = nl; i < n; i++) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " lookup index: " + i);
}
readLookupTable(tableTag, i, lookupList + loa [ i ]);
}
}
}
/**
* Read the common layout tables (used by GSUB and GPOS).
* @param tableTag tag of table being read
* @param scriptList offset to script list from beginning of font file
* @param featureList offset to feature list from beginning of font file
* @param lookupList offset to lookup list from beginning of font file
* @throws IOException In case of a I/O problem
*/
private void readCommonLayoutTables(OFTableName tableTag, long scriptList, long featureList, long lookupList) throws IOException {
if (scriptList > 0) {
readScriptList(tableTag, scriptList);
}
if (featureList > 0) {
readFeatureList(tableTag, featureList);
}
if (lookupList > 0) {
readLookupList(tableTag, lookupList);
}
}
private void readGDEFClassDefTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset);
// store results
seMapping = ct;
// extract subtable
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1);
resetATSubState();
}
private void readGDEFAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
int co = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " attachment point coverage table offset: " + co);
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " attachment point coverage", subtableOffset + co);
// store results
seMapping = ct;
// extract subtable
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1);
resetATSubState();
}
private void readGDEFLigatureCaretTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// read coverage offset
int co = in.readTTFUShort();
// read ligature glyph count
int nl = in.readTTFUShort();
// read ligature glyph table offsets
int[] lgto = new int [ nl ];
for (int i = 0; i < nl; i++) {
lgto [ i ] = in.readTTFUShort();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " ligature caret coverage table offset: " + co);
log.debug(tableTag + " ligature caret ligature glyph count: " + nl);
for (int i = 0; i < nl; i++) {
log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i]);
}
}
// read coverage table
GlyphCoverageTable ct = readCoverageTable(tableTag + " ligature caret coverage", subtableOffset + co);
// store results
seMapping = ct;
// extract subtable
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1);
resetATSubState();
}
private void readGDEFMarkAttachmentTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// subtable is a bare class definition table
GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset);
// store results
seMapping = ct;
// extract subtable
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1);
resetATSubState();
}
private void readGDEFMarkGlyphsTableFormat1(OFTableName tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
initATSubState();
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip(2);
// read mark set class count
int nmc = in.readTTFUShort();
long[] mso = new long [ nmc ];
// read mark set coverage offsets
for (int i = 0; i < nmc; i++) {
mso [ i ] = in.readTTFULong();
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)");
log.debug(tableTag + " mark set class count: " + nmc);
for (int i = 0; i < nmc; i++) {
log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i]);
}
}
// read mark set coverage tables, one per class
GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc];
for (int i = 0; i < nmc; i++) {
msca[i] = readCoverageTable(tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i]);
}
// create combined class table from per-class coverage tables
GlyphClassTable ct = GlyphClassTable.createClassTable(Arrays.asList(msca));
// store results
seMapping = ct;
// extract subtable
extractSESubState(GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1);
resetATSubState();
}
private void readGDEFMarkGlyphsTable(OFTableName tableTag, int lookupSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
// read mark set subtable format
int sf = in.readTTFUShort();
if (sf == 1) {
readGDEFMarkGlyphsTableFormat1(tableTag, lookupSequence, subtableOffset, sf);
} else {
throw new AdvancedTypographicTableFormatException("unsupported mark glyph sets subtable format: " + sf);
}
}
/**
* Read the GDEF table.
* @throws IOException In case of a I/O problem
*/
private void readGDEF() throws IOException {
OFTableName tableTag = OFTableName.GDEF;
// Initialize temporary state
initATState();
// Read glyph definition (GDEF) table
OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gdef != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
otf.seekTab(in, tableTag, 0);
long version = in.readTTFULong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
}
// glyph class definition table offset (may be null)
int cdo = in.readTTFUShort();
// attach point list offset (may be null)
int apo = in.readTTFUShort();
// ligature caret list offset (may be null)
int lco = in.readTTFUShort();
// mark attach class definition table offset (may be null)
int mao = in.readTTFUShort();
// mark glyph sets definition table offset (may be null)
int mgo;
if (version >= 0x00010002) {
mgo = in.readTTFUShort();
} else {
mgo = 0;
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " glyph class definition table offset: " + cdo);
log.debug(tableTag + " attachment point list offset: " + apo);
log.debug(tableTag + " ligature caret list offset: " + lco);
log.debug(tableTag + " mark attachment class definition table offset: " + mao);
log.debug(tableTag + " mark glyph set definitions table offset: " + mgo);
}
// initialize subtable sequence number
int seqno = 0;
// obtain offset to start of gdef table
long to = dirTab.getOffset();
// (optionally) read glyph class definition subtable
if (cdo != 0) {
readGDEFClassDefTable(tableTag, seqno++, to + cdo);
}
// (optionally) read glyph attachment point subtable
if (apo != 0) {
readGDEFAttachmentTable(tableTag, seqno++, to + apo);
}
// (optionally) read ligature caret subtable
if (lco != 0) {
readGDEFLigatureCaretTable(tableTag, seqno++, to + lco);
}
// (optionally) read mark attachment class subtable
if (mao != 0) {
readGDEFMarkAttachmentTable(tableTag, seqno++, to + mao);
}
// (optionally) read mark glyph sets subtable
if (mgo != 0) {
readGDEFMarkGlyphsTable(tableTag, seqno++, to + mgo);
}
GlyphDefinitionTable gdef;
if ((gdef = constructGDEF()) != null) {
this.gdef = gdef;
}
}
}
/**
* Read the GSUB table.
* @throws IOException In case of a I/O problem
*/
private void readGSUB() throws IOException {
OFTableName tableTag = OFTableName.GSUB;
// Initialize temporary state
initATState();
// Read glyph substitution (GSUB) table
OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gpos != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
otf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
}
int slo = in.readTTFUShort();
int flo = in.readTTFUShort();
int llo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script list offset: " + slo);
log.debug(tableTag + " feature list offset: " + flo);
log.debug(tableTag + " lookup list offset: " + llo);
}
long to = dirTab.getOffset();
readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo);
GlyphSubstitutionTable gsub;
if ((gsub = constructGSUB()) != null) {
this.gsub = gsub;
}
}
}
/**
* Read the GPOS table.
* @throws IOException In case of a I/O problem
*/
private void readGPOS() throws IOException {
OFTableName tableTag = OFTableName.GPOS;
// Initialize temporary state
initATState();
// Read glyph positioning (GPOS) table
OFDirTabEntry dirTab = otf.getDirectoryEntry(tableTag);
if (gpos != null) {
if (log.isDebugEnabled()) {
log.debug(tableTag + ": ignoring duplicate table");
}
} else if (dirTab != null) {
otf.seekTab(in, tableTag, 0);
int version = in.readTTFLong();
if (log.isDebugEnabled()) {
log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
}
int slo = in.readTTFUShort();
int flo = in.readTTFUShort();
int llo = in.readTTFUShort();
if (log.isDebugEnabled()) {
log.debug(tableTag + " script list offset: " + slo);
log.debug(tableTag + " feature list offset: " + flo);
log.debug(tableTag + " lookup list offset: " + llo);
}
long to = dirTab.getOffset();
readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo);
GlyphPositioningTable gpos;
if ((gpos = constructGPOS()) != null) {
this.gpos = gpos;
}
}
}
/**
* Construct the (internal representation of the) GDEF table based on previously
* parsed state.
* @returns glyph definition table or null if insufficient or invalid state
*/
private GlyphDefinitionTable constructGDEF() {
GlyphDefinitionTable gdef = null;
List subtables;
if ((subtables = constructGDEFSubtables()) != null) {
if (subtables.size() > 0) {
gdef = new GlyphDefinitionTable(subtables, processors);
}
}
resetATState();
return gdef;
}
/**
* Construct the (internal representation of the) GSUB table based on previously
* parsed state.
* @returns glyph substitution table or null if insufficient or invalid state
*/
private GlyphSubstitutionTable constructGSUB() {
GlyphSubstitutionTable gsub = null;
Map lookups;
if ((lookups = constructLookups()) != null) {
List subtables;
if ((subtables = constructGSUBSubtables()) != null) {
if ((lookups.size() > 0) && (subtables.size() > 0)) {
gsub = new GlyphSubstitutionTable(gdef, lookups, subtables, processors);
}
}
}
resetATState();
return gsub;
}
/**
* Construct the (internal representation of the) GPOS table based on previously
* parsed state.
* @returns glyph positioning table or null if insufficient or invalid state
*/
private GlyphPositioningTable constructGPOS() {
GlyphPositioningTable gpos = null;
Map lookups;
if ((lookups = constructLookups()) != null) {
List subtables;
if ((subtables = constructGPOSSubtables()) != null) {
if ((lookups.size() > 0) && (subtables.size() > 0)) {
gpos = new GlyphPositioningTable(gdef, lookups, subtables, processors);
}
}
}
resetATState();
return gpos;
}
private void constructLookupsFeature(Map lookups, String st, String lt, String fid) {
Object[] fp = (Object[]) seFeatures.get(fid);
if (fp != null) {
assert fp.length == 2;
String ft = (String) fp[0]; // feature tag
List<String> lul = (List) fp[1]; // list of lookup table ids
if ((ft != null) && (lul != null) && (lul.size() > 0)) {
GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec(st, lt, ft);
lookups.put(ls, lul);
}
}
}
private void constructLookupsFeatures(Map lookups, String st, String lt, List<String> fids) {
for (Object fid1 : fids) {
String fid = (String) fid1;
constructLookupsFeature(lookups, st, lt, fid);
}
}
private void constructLookupsLanguage(Map lookups, String st, String lt, Map<String, Object> languages) {
Object[] lp = (Object[]) languages.get(lt);
if (lp != null) {
assert lp.length == 2;
if (lp[0] != null) { // required feature id
constructLookupsFeature(lookups, st, lt, (String) lp[0]);
}
if (lp[1] != null) { // non-required features ids
constructLookupsFeatures(lookups, st, lt, (List) lp[1]);
}
}
}
private void constructLookupsLanguages(Map lookups, String st, List<String> ll, Map<String, Object> languages) {
for (Object aLl : ll) {
String lt = (String) aLl;
constructLookupsLanguage(lookups, st, lt, languages);
}
}
private Map constructLookups() {
Map<GlyphTable.LookupSpec, List<String>> lookups = new java.util.LinkedHashMap();
for (Object o : seScripts.keySet()) {
String st = (String) o;
Object[] sp = (Object[]) seScripts.get(st);
if (sp != null) {
assert sp.length == 3;
Map<String, Object> languages = (Map) sp[2];
if (sp[0] != null) { // default language
constructLookupsLanguage(lookups, st, (String) sp[0], languages);
}
if (sp[1] != null) { // non-default languages
constructLookupsLanguages(lookups, st, (List) sp[1], languages);
}
}
}
return lookups;
}
private List constructGDEFSubtables() {
List<GlyphSubtable> subtables = new java.util.ArrayList();
if (seSubtables != null) {
for (Object seSubtable : seSubtables) {
Object[] stp = (Object[]) seSubtable;
GlyphSubtable st;
if ((st = constructGDEFSubtable(stp)) != null) {
subtables.add(st);
}
}
}
return subtables;
}
private GlyphSubtable constructGDEFSubtable(Object[] stp) {
GlyphSubtable st = null;
assert (stp != null) && (stp.length == 8);
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphMappingTable mapping = (GlyphMappingTable) stp[6];
List entries = (List) stp[7];
if (tt == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) {
int type = GDEFLookupType.getSubtableType(lt);
String lid = "lu" + ln;
int sequence = sn;
int flags = lf;
int format = sf;
st = GlyphDefinitionTable.createSubtable(type, lid, sequence, flags, format, mapping, entries);
}
return st;
}
private List constructGSUBSubtables() {
List<GlyphSubtable> subtables = new java.util.ArrayList();
if (seSubtables != null) {
for (Object seSubtable : seSubtables) {
Object[] stp = (Object[]) seSubtable;
GlyphSubtable st;
if ((st = constructGSUBSubtable(stp)) != null) {
subtables.add(st);
}
}
}
return subtables;
}
private GlyphSubtable constructGSUBSubtable(Object[] stp) {
GlyphSubtable st = null;
assert (stp != null) && (stp.length == 8);
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
List entries = (List) stp[7];
if (tt == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION) {
int type = GSUBLookupType.getSubtableType(lt);
String lid = "lu" + ln;
int sequence = sn;
int flags = lf;
int format = sf;
st = GlyphSubstitutionTable.createSubtable(type, lid, sequence, flags, format, coverage, entries);
}
return st;
}
private List constructGPOSSubtables() {
List<GlyphSubtable> subtables = new java.util.ArrayList();
if (seSubtables != null) {
for (Object seSubtable : seSubtables) {
Object[] stp = (Object[]) seSubtable;
GlyphSubtable st;
if ((st = constructGPOSSubtable(stp)) != null) {
subtables.add(st);
}
}
}
return subtables;
}
private GlyphSubtable constructGPOSSubtable(Object[] stp) {
GlyphSubtable st = null;
assert (stp != null) && (stp.length == 8);
Integer tt = (Integer) stp[0]; // table type
Integer lt = (Integer) stp[1]; // lookup type
Integer ln = (Integer) stp[2]; // lookup sequence number
Integer lf = (Integer) stp[3]; // lookup flags
Integer sn = (Integer) stp[4]; // subtable sequence number
Integer sf = (Integer) stp[5]; // subtable format
GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
List entries = (List) stp[7];
if (tt == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING) {
int type = GSUBLookupType.getSubtableType(lt);
String lid = "lu" + ln;
int sequence = sn;
int flags = lf;
int format = sf;
st = GlyphPositioningTable.createSubtable(type, lid, sequence, flags, format, coverage, entries);
}
return st;
}
private void initATState() {
seScripts = new java.util.LinkedHashMap();
seLanguages = new java.util.LinkedHashMap();
seFeatures = new java.util.LinkedHashMap();
seSubtables = new java.util.ArrayList();
resetATSubState();
}
private void resetATState() {
seScripts = null;
seLanguages = null;
seFeatures = null;
seSubtables = null;
resetATSubState();
}
private void initATSubState() {
seMapping = null;
seEntries = new java.util.ArrayList();
}
private void extractSESubState(int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat) {
if (seEntries != null) {
if ((tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION) || (seEntries.size() > 0)) {
if (seSubtables != null) {
Integer tt = tableType;
Integer lt = lookupType;
Integer ln = lookupSequence;
Integer lf = lookupFlags;
Integer sn = subtableSequence;
Integer sf = subtableFormat;
seSubtables.add(new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries });
}
}
}
}
private void resetATSubState() {
seMapping = null;
seEntries = null;
}
private void resetATStateAll() {
resetATState();
gdef = null;
gsub = null;
gpos = null;
}
/** helper method for formatting an integer array for output */
private String toString(int[] ia) {
StringBuffer sb = new StringBuffer();
if ((ia == null) || (ia.length == 0)) {
sb.append('-');
} else {
boolean first = true;
for (int anIa : ia) {
if (!first) {
sb.append(' ');
} else {
first = false;
}
sb.append(anIa);
}
}
return sb.toString();
}
}