blob: 6dcb87a3b82520865f34dfcaf77759c57c17bb29 [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.util;
import java.io.File;
import java.io.IOException;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
import java.util.Vector;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.fonts.GlyphClassTable;
import org.apache.fop.fonts.GlyphCoverageTable;
import org.apache.fop.fonts.GlyphDefinitionSubtable;
import org.apache.fop.fonts.GlyphDefinitionTable;
import org.apache.fop.fonts.GlyphMappingTable;
import org.apache.fop.fonts.GlyphPositioningSubtable;
import org.apache.fop.fonts.GlyphPositioningTable;
import org.apache.fop.fonts.GlyphPositioningTable.Anchor;
import org.apache.fop.fonts.GlyphPositioningTable.MarkAnchor;
import org.apache.fop.fonts.GlyphPositioningTable.PairValues;
import org.apache.fop.fonts.GlyphPositioningTable.Value;
import org.apache.fop.fonts.GlyphSequence;
import org.apache.fop.fonts.GlyphSubstitutionSubtable;
import org.apache.fop.fonts.GlyphSubstitutionTable;
import org.apache.fop.fonts.GlyphSubstitutionTable.Ligature;
import org.apache.fop.fonts.GlyphSubstitutionTable.LigatureSet;
import org.apache.fop.fonts.GlyphSubtable;
import org.apache.fop.fonts.GlyphTable;
import org.apache.fop.fonts.GlyphTable.RuleLookup;
import org.apache.fop.util.CharUtilities;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
// CSOFF: NoWhitespaceAfterCheck
/**
* This class supports a subset of the <code>TTX</code> file as produced by the Adobe FLEX
* SDK (AFDKO). In particular, it is used to parse a <code>TTX</code> file in order to
* extract character to glyph code mapping data, glyph definition data, glyph substitution
* data, and glyph positioning data.
*
* <code>TTX</code> files are used in FOP for testing and debugging purposes only. Such
* files are used to represent font data employed by complex script processing, and
* normally extracted directly from an opentype (or truetype) file. However, due to
* copyright restrictions, it is not possible to include most opentype (or truetype) font
* files directly in the FOP distribution. In such cases, <code>TTX</code> files are used
* to distribute a subset of the complex script advanced table information contained in
* certain font files to facilitate testing.
*
* @author Glenn Adams
*/
public class TTXFile {
/** logging instance */
private static final Log log = LogFactory.getLog(TTXFile.class); // CSOK: ConstantNameCheck
/** default script tag */
private static final String DEFAULT_SCRIPT_TAG = "dflt";
/** default language tag */
private static final String DEFAULT_LANGUAGE_TAG = "dflt";
/** ttxfile cache */
private static Map<String,TTXFile> cache = new HashMap<String,TTXFile>();
// transient parsing state
private Locator locator; // current document locator
private Stack<String[]> elements; // stack of ttx elements being parsed
private Map<String,Integer> glyphIds; // map of glyph names to glyph identifiers
private List<int[]> cmapEntries; // list of <charCode,glyphCode> pairs
private Vector<int[]> hmtxEntries; // vector of <width,lsb> pairs
private Map<String,Integer> glyphClasses; // map of glyph names to glyph classes
private Map<String,Map<String,List<String>>> scripts; // map of script tag to Map<language-tag,List<features-id>>>
private Map<String,List<String>> languages; // map of language tag to List<feature-id>
private Map<String,Object[]> features; // map of feature id to Object[2] : { feature-tag, List<lookup-id> }
private List<String> languageFeatures; // list of language system feature ids, where first is (possibly null) required feature id
private List<String> featureLookups; // list of lookup ids for feature being constructed
private List<Integer> coverageEntries; // list of entries for coverage table being constructed
private Map<String,GlyphCoverageTable> coverages; // map of coverage table keys to coverage tables
private List subtableEntries; // list of lookup subtable entries
private List<GlyphSubtable> subtables; // list of constructed subtables
private List<Integer> alternates; // list of alternates in alternate set being constructed
private List<Ligature> ligatures; // list of ligatures in ligature set being constructed
private List<Integer> substitutes; // list of substitutes in (multiple substitution) sequence being constructed
private List<PairValues> pairs; // list of pair value records being constructed
private List<PairValues[]> pairSets; // list of pair value sets (as arrays) being constructed
private List<Anchor> anchors; // list of anchors of base|mark|component record being constructed
private List<Anchor[]> components; // list of ligature component anchors being constructed
private List<MarkAnchor> markAnchors; // list of mark anchors being constructed
private List<Anchor[]> baseOrMarkAnchors; // list of base|mark2 anchors being constructed
private List<Anchor[][]> ligatureAnchors; // list of ligature anchors being constructed
private List<Anchor[]> attachmentAnchors; // list of entry|exit attachment anchors being constructed
private List<RuleLookup> ruleLookups; // list of rule lookups being constructed
private int glyphIdMax; // maximum glyph id
private int cmPlatform; // plaform id of cmap being constructed
private int cmEncoding; // plaform id of cmap being constructed
private int cmLanguage; // plaform id of cmap being constructed
private int flIndex; // index of feature being constructed
private int flSequence; // feature sequence within feature list
private int ltIndex; // index of lookup table being constructed
private int ltSequence; // lookup sequence within table
private int ltFlags; // flags of current lookup being constructed
private int stSequence; // subtable sequence number within lookup
private int stFormat; // format of current subtable being constructed
private int ctFormat; // format of coverage table being constructed
private int ctIndex; // index of coverage table being constructed
private int rlSequence; // rule lookup sequence index
private int rlLookup; // rule lookup lookup index
private int psIndex; // pair set index
private int vf1; // value format 1 (used with pair pos and single pos)
private int vf2; // value format 2 (used with pair pos)
private int g2; // glyph id 2 (used with pair pos)
private int xCoord; // x coordinate of anchor being constructed
private int yCoord; // y coordinate of anchor being constructed
private int markClass; // mark class of mark anchor being constructed
private String defaultScriptTag; // tag of default script
private String scriptTag; // tag of script being constructed
private String defaultLanguageTag; // tag of default language system
private String languageTag; // tag of language system being constructed
private String featureTag; // tag of feature being constructed
private Value v1; // positioining value 1
private Value v2; // positioining value 2
// resultant state
private int upem; // units per em
private Map<Integer,Integer> cmap; // constructed character map
private Map<Integer,Integer> gmap; // constructed glyph map
private int[][] hmtx; // constructed horizontal metrics - array of design { width, lsb } pairs, indexed by glyph code
private int[] widths; // pdf normalized widths (millipoints)
private GlyphDefinitionTable gdef; // constructed glyph definition table
private GlyphSubstitutionTable gsub; // constructed glyph substitution table
private GlyphPositioningTable gpos; // constructed glyph positioning table
public TTXFile() {
elements = new Stack<String[]>();
glyphIds = new HashMap<String,Integer>();
cmapEntries = new ArrayList<int[]>();
hmtxEntries = new Vector<int[]>();
glyphClasses = new HashMap<String,Integer>();
scripts = new HashMap<String,Map<String,List<String>>>();
languages = new HashMap<String,List<String>>();
features = new HashMap<String,Object[]>();
languageFeatures = new ArrayList<String>();
featureLookups = new ArrayList<String>();
coverageEntries = new ArrayList<Integer>();
coverages = new HashMap<String,GlyphCoverageTable>();
subtableEntries = new ArrayList();
subtables = new ArrayList<GlyphSubtable>();
alternates = new ArrayList<Integer>();
ligatures = new ArrayList<Ligature>();
substitutes = new ArrayList<Integer>();
pairs = new ArrayList<PairValues>();
pairSets = new ArrayList<PairValues[]>();
anchors = new ArrayList<Anchor>();
markAnchors = new ArrayList<MarkAnchor>();
baseOrMarkAnchors = new ArrayList<Anchor[]>();
ligatureAnchors = new ArrayList<Anchor[][]>();
components = new ArrayList<Anchor[]>();
attachmentAnchors = new ArrayList<Anchor[]>();
ruleLookups = new ArrayList<RuleLookup>();
glyphIdMax = -1;
cmPlatform = -1;
cmEncoding = -1;
cmLanguage = -1;
flIndex = -1;
flSequence = 0;
ltIndex = -1;
ltSequence = 0;
ltFlags = 0;
stSequence = 0;
stFormat = 0;
ctFormat = -1;
ctIndex = -1;
rlSequence = -1;
rlLookup = -1;
psIndex = -1;
vf1 = -1;
vf2 = -1;
g2 = -1;
xCoord = Integer.MIN_VALUE;
yCoord = Integer.MIN_VALUE;
markClass = -1;
defaultScriptTag = DEFAULT_SCRIPT_TAG;
scriptTag = null;
defaultLanguageTag = DEFAULT_LANGUAGE_TAG;
languageTag = null;
featureTag = null;
v1 = null;
v2 = null;
upem = -1;
}
public void parse ( String filename ) {
parse ( new File ( filename ) );
}
public void parse ( File f ) {
assert f != null;
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp = spf.newSAXParser();
sp.parse ( f, new Handler() );
} catch ( FactoryConfigurationError e ) {
throw new RuntimeException ( e.getMessage() );
} catch ( ParserConfigurationException e ) {
throw new RuntimeException ( e.getMessage() );
} catch ( SAXException e ) {
throw new RuntimeException ( e.getMessage() );
} catch ( IOException e ) {
throw new RuntimeException ( e.getMessage() );
}
}
public GlyphSequence mapCharsToGlyphs ( String s ) {
Integer[] ca = CharUtilities.toUTF32 ( s, 0, true );
int ng = ca.length;
IntBuffer cb = IntBuffer.allocate ( ng );
IntBuffer gb = IntBuffer.allocate ( ng );
for ( Integer c : ca ) {
int g = mapCharToGlyph ( (int) c );
if ( g >= 0 ) {
cb.put ( c );
gb.put ( g );
} else {
throw new IllegalArgumentException ( "character " + CharUtilities.format ( c ) + " has no corresponding glyph" );
}
}
cb.rewind();
gb.rewind();
return new GlyphSequence ( cb, gb, null );
}
public int mapCharToGlyph ( int c ) {
if ( cmap != null ) {
Integer g = cmap.get ( Integer.valueOf ( c ) );
if ( g != null ) {
return (int) g;
} else {
return -1;
}
} else {
return -1;
}
}
public int getGlyph ( String gid ) {
return mapGlyphId0 ( gid );
}
public GlyphSequence getGlyphSequence ( String[] gids ) {
assert gids != null;
int ng = gids.length;
IntBuffer cb = IntBuffer.allocate ( ng );
IntBuffer gb = IntBuffer.allocate ( ng );
for ( String gid : gids ) {
int g = mapGlyphId0 ( gid );
if ( g >= 0 ) {
int c = mapGlyphIdToChar ( gid );
if ( c < 0 ) {
c = CharUtilities.NOT_A_CHARACTER;
}
cb.put ( c );
gb.put ( g );
} else {
throw new IllegalArgumentException ( "unmapped glyph id \"" + gid + "\"" );
}
}
cb.rewind();
gb.rewind();
return new GlyphSequence ( cb, gb, null );
}
public int[] getWidths ( String[] gids ) {
assert gids != null;
int ng = gids.length;
int[] widths = new int [ ng ];
int i = 0;
for ( String gid : gids ) {
int g = mapGlyphId0 ( gid );
int w = 0;
if ( g >= 0 ) {
if ( ( hmtx != null ) && ( g < hmtx.length ) ) {
int[] mtx = hmtx [ g ];
assert mtx != null;
assert mtx.length > 0;
w = mtx[0];
}
}
widths [ i++ ] = w;
}
assert i == ng;
return widths;
}
public int[] getWidths() {
if ( this.widths == null ) {
if ( ( hmtx != null ) && ( upem > 0 ) ) {
int[] widths = new int [ hmtx.length ];
for ( int i = 0, n = widths.length; i < n; i++ ) {
widths [ i ] = getPDFWidth ( hmtx [ i ] [ 0 ], upem );
}
this.widths = widths;
}
}
return this.widths;
}
public static int getPDFWidth ( int tw, int upem ) {
// N.B. The following is copied (with minor edits) from TTFFile to insure same results
int pw;
if ( tw < 0 ) {
long rest1 = tw % upem;
long storrest = 1000 * rest1;
long ledd2 = ( storrest != 0 ) ? ( rest1 / storrest ) : 0;
pw = - ( ( -1000 * tw ) / upem - (int) ledd2 );
} else {
pw = ( tw / upem ) * 1000 + ( ( tw % upem ) * 1000 ) / upem;
}
return pw;
}
public GlyphDefinitionTable getGDEF() {
return gdef;
}
public GlyphSubstitutionTable getGSUB() {
return gsub;
}
public GlyphPositioningTable getGPOS() {
return gpos;
}
public static synchronized TTXFile getFromCache ( String filename ) {
assert cache != null;
TTXFile f;
if ( ( f = (TTXFile) cache.get ( filename ) ) == null ) {
f = new TTXFile();
f.parse ( filename );
cache.put ( filename, f );
}
return f;
}
public static synchronized void clearCache() {
cache.clear();
}
private class Handler extends DefaultHandler {
private Handler() {
}
@Override
public void startDocument() {
}
@Override
public void endDocument() {
}
@Override
public void setDocumentLocator ( Locator locator ) {
TTXFile.this.locator = locator;
}
@Override
public void startElement ( String uri, String localName, String qName, Attributes attrs ) throws SAXException {
String[] en = makeExpandedName ( uri, localName, qName );
if ( en[0] != null ) {
unsupportedElement ( en );
} else if ( en[1].equals ( "Alternate" ) ) {
String[] pn = new String[] { null, "AlternateSet" };
if ( isParent ( pn ) ) {
String glyph = attrs.getValue ( "glyph" );
if ( glyph == null ) {
missingRequiredAttribute ( en, "glyph" );
}
int gid = mapGlyphId ( glyph, en );
alternates.add ( Integer.valueOf ( gid ) );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "AlternateSet" ) ) {
String[] pn = new String[] { null, "AlternateSubst" };
if ( isParent ( pn ) ) {
String glyph = attrs.getValue ( "glyph" );
if ( glyph == null ) {
missingRequiredAttribute ( en, "glyph" );
}
int gid = mapGlyphId ( glyph, en );
coverageEntries.add ( Integer.valueOf ( gid ) );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "AlternateSubst" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = 1;
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "BacktrackCoverage" ) ) {
String[] pn1 = new String[] { null, "ChainContextSubst" };
String[] pn2 = new String[] { null, "ChainContextPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
int ci = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
ci = Integer.parseInt ( index );
}
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = ci;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "BaseAnchor" ) ) {
String[] pn = new String[] { null, "BaseRecord" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "BaseArray" ) ) {
String[] pn = new String[] { null, "MarkBasePos" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "BaseCoverage" ) ) {
String[] pn = new String[] { null, "MarkBasePos" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "BaseRecord" ) ) {
String[] pn = new String[] { null, "BaseArray" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ChainContextPos" ) || en[1].equals ( "ChainContextSubst" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
case 3:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Class" ) ) {
String[] pn = new String[] { null, "MarkRecord" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
assert markClass == -1;
markClass = v;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ClassDef" ) ) {
String[] pn1 = new String[] { null, "GlyphClassDef" };
String[] pn2 = new String[] { null, "MarkAttachClassDef" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String glyph = attrs.getValue ( "glyph" );
if ( glyph == null ) {
missingRequiredAttribute ( en, "glyph" );
}
String glyphClass = attrs.getValue ( "class" );
if ( glyphClass == null ) {
missingRequiredAttribute ( en, "class" );
}
if ( ! glyphIds.containsKey ( glyph ) ) {
unsupportedGlyph ( en, glyph );
} else if ( isParent ( pn1 ) ) {
if ( glyphClasses.containsKey ( glyph ) ) {
duplicateGlyphClass ( en, glyph, glyphClass );
} else {
glyphClasses.put ( glyph, Integer.parseInt(glyphClass) );
}
} else if ( isParent ( pn2 ) ) {
if ( glyphClasses.containsKey ( glyph ) ) {
duplicateGlyphClass ( en, glyph, glyphClass );
} else {
glyphClasses.put ( glyph, Integer.parseInt(glyphClass) );
}
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "ComponentRecord" ) ) {
String[] pn = new String[] { null, "LigatureAttach" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
assert anchors.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Coverage" ) ) {
String[] pn1 = new String[] { null, "CursivePos" };
String[] pn2 = new String[] { null, "LigCaretList" };
String[] pn3 = new String[] { null, "MultipleSubst" };
String[] pn4 = new String[] { null, "PairPos" };
String[] pn5 = new String[] { null, "SinglePos" };
String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5 };
if ( isParent ( pnx ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "CursivePos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
assert attachmentAnchors.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "DefaultLangSys" ) ) {
String[] pn = new String[] { null, "Script" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
} else {
assertLanguageFeaturesClear();
assert languageTag == null;
languageTag = defaultLanguageTag;
}
} else if ( en[1].equals ( "EntryAnchor" ) ) {
String[] pn = new String[] { null, "EntryExitRecord" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "EntryExitRecord" ) ) {
String[] pn = new String[] { null, "CursivePos" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ExitAnchor" ) ) {
String[] pn = new String[] { null, "EntryExitRecord" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Feature" ) ) {
String[] pn = new String[] { null, "FeatureRecord" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
} else {
assertFeatureLookupsClear();
}
} else if ( en[1].equals ( "FeatureIndex" ) ) {
String[] pn1 = new String[] { null, "DefaultLangSys" };
String[] pn2 = new String[] { null, "LangSys" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
if ( languageFeatures.size() == 0 ) {
languageFeatures.add ( null );
}
if ( ( v >= 0 ) && ( v < 65535 ) ) {
languageFeatures.add ( makeFeatureId ( v ) );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "FeatureList" ) ) {
String[] pn1 = new String[] { null, "GSUB" };
String[] pn2 = new String[] { null, "GPOS" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( ! isParent ( pnx ) ) {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "FeatureRecord" ) ) {
String[] pn = new String[] { null, "FeatureList" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
int fi = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
fi = Integer.parseInt ( index );
}
assertFeatureClear();
assert flIndex == -1;
flIndex = fi;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "FeatureTag" ) ) {
String[] pn = new String[] { null, "FeatureRecord" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
assert featureTag == null;
featureTag = value;
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "GDEF" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( isParent ( pn ) ) {
assertSubtablesClear();
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "GPOS" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( isParent ( pn ) ) {
assertCoveragesClear();
assertSubtablesClear();
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "GSUB" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( isParent ( pn ) ) {
assertCoveragesClear();
assertSubtablesClear();
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Glyph" ) ) {
String[] pn1 = new String[] { null, "Coverage" };
String[] pn2 = new String[] { null, "InputCoverage" };
String[] pn3 = new String[] { null, "LookAheadCoverage" };
String[] pn4 = new String[] { null, "BacktrackCoverage" };
String[] pn5 = new String[] { null, "MarkCoverage" };
String[] pn6 = new String[] { null, "Mark1Coverage" };
String[] pn7 = new String[] { null, "Mark2Coverage" };
String[] pn8 = new String[] { null, "BaseCoverage" };
String[] pn9 = new String[] { null, "LigatureCoverage" };
String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6, pn7, pn8, pn9 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
int gid = mapGlyphId ( value, en );
coverageEntries.add ( Integer.valueOf ( gid ) );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "GlyphClassDef" ) ) {
String[] pn = new String[] { null, "GDEF" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
// force format 1 since TTX always writes entries as non-range entries
if ( sf != 1 ) {
sf = 1;
}
stFormat = sf;
assert glyphClasses.isEmpty();
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "GlyphID" ) ) {
String[] pn = new String[] { null, "GlyphOrder" };
if ( isParent ( pn ) ) {
String id = attrs.getValue ( "id" );
int gid = -1;
if ( id == null ) {
missingRequiredAttribute ( en, "id" );
} else {
gid = Integer.parseInt ( id );
}
String name = attrs.getValue ( "name" );
if ( name == null ) {
missingRequiredAttribute ( en, "name" );
}
if ( glyphIds.containsKey ( name ) ) {
duplicateGlyph ( en, name, gid );
} else {
if ( gid > glyphIdMax ) {
glyphIdMax = gid;
}
glyphIds.put ( name, gid );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "GlyphOrder" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "InputCoverage" ) ) {
String[] pn1 = new String[] { null, "ChainContextSubst" };
String[] pn2 = new String[] { null, "ChainContextPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
int ci = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
ci = Integer.parseInt ( index );
}
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = ci;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "LangSys" ) ) {
String[] pn = new String[] { null, "LangSysRecord" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
} else {
assertLanguageFeaturesClear();
}
} else if ( en[1].equals ( "LangSysRecord" ) ) {
String[] pn = new String[] { null, "Script" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LangSysTag" ) ) {
String[] pn = new String[] { null, "LangSysRecord" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
assert languageTag == null;
languageTag = value;
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigCaretList" ) ) {
String[] pn = new String[] { null, "GDEF" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Ligature" ) ) {
String[] pn = new String[] { null, "LigatureSet" };
if ( isParent ( pn ) ) {
String components = attrs.getValue ( "components" );
if ( components == null ) {
missingRequiredAttribute ( en, "components" );
}
int[] cids = mapGlyphIds ( components, en );
String glyph = attrs.getValue ( "glyph" );
if ( glyph == null ) {
missingRequiredAttribute ( en, "glyph" );
}
int gid = mapGlyphId ( glyph, en );
ligatures.add ( new Ligature ( gid, cids ) );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureAnchor" ) ) {
String[] pn = new String[] { null, "ComponentRecord" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureArray" ) ) {
String[] pn = new String[] { null, "MarkLigPos" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureAttach" ) ) {
String[] pn = new String[] { null, "LigatureArray" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
assert components.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureCoverage" ) ) {
String[] pn = new String[] { null, "MarkLigPos" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureSet" ) ) {
String[] pn = new String[] { null, "LigatureSubst" };
if ( isParent ( pn ) ) {
String glyph = attrs.getValue ( "glyph" );
if ( glyph == null ) {
missingRequiredAttribute ( en, "glyph" );
}
int gid = mapGlyphId ( glyph, en );
coverageEntries.add ( Integer.valueOf ( gid ) );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LigatureSubst" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = 1;
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LookAheadCoverage" ) ) {
String[] pn1 = new String[] { null, "ChainContextSubst" };
String[] pn2 = new String[] { null, "ChainContextPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
int ci = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
ci = Integer.parseInt ( index );
}
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = ci;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "Lookup" ) ) {
String[] pn = new String[] { null, "LookupList" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
int li = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
li = Integer.parseInt ( index );
}
assertLookupClear();
assert ltIndex == -1;
ltIndex = li;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LookupFlag" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int lf = 0;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
lf = Integer.parseInt ( value );
}
assert ltFlags == 0;
ltFlags = lf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "LookupList" ) ) {
String[] pn1 = new String[] { null, "GSUB" };
String[] pn2 = new String[] { null, "GPOS" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( ! isParent ( pnx ) ) {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "LookupListIndex" ) ) {
String[] pn1 = new String[] { null, "Feature" };
String[] pn2 = new String[] { null, "SubstLookupRecord" };
String[] pn3 = new String[] { null, "PosLookupRecord" };
String[][] pnx = new String[][] { pn1, pn2, pn3 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
String[][] pny = new String[][] { pn2, pn3 };
if ( isParent ( pny ) ) {
assert rlLookup == -1;
assert v != -1;
rlLookup = v;
} else {
featureLookups.add ( makeLookupId ( v ) );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "LookupType" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark1Array" ) ) {
String[] pn = new String[] { null, "MarkMarkPos" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark1Coverage" ) ) {
String[] pn = new String[] { null, "MarkMarkPos" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark2Anchor" ) ) {
String[] pn = new String[] { null, "Mark2Record" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark2Array" ) ) {
String[] pn = new String[] { null, "MarkMarkPos" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark2Coverage" ) ) {
String[] pn = new String[] { null, "MarkMarkPos" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Mark2Record" ) ) {
String[] pn = new String[] { null, "Mark2Array" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkAnchor" ) ) {
String[] pn = new String[] { null, "MarkRecord" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
}
assert xCoord == Integer.MIN_VALUE;
assert yCoord == Integer.MIN_VALUE;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkArray" ) ) {
String[] pn1 = new String[] { null, "MarkBasePos" };
String[] pn2 = new String[] { null, "MarkLigPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( ! isParent ( pnx ) ) {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "MarkAttachClassDef" ) ) {
String[] pn = new String[] { null, "GDEF" };
if ( isParent ( pn ) ) {
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
// force format 1 since TTX always writes entries as non-range entries
if ( sf != 1 ) {
sf = 1;
}
stFormat = sf;
assert glyphClasses.isEmpty();
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkBasePos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
assert markAnchors.size() == 0;
assert baseOrMarkAnchors.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkCoverage" ) ) {
String[] pn1 = new String[] { null, "MarkBasePos" };
String[] pn2 = new String[] { null, "MarkLigPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String format = attrs.getValue ( "Format" );
int cf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
cf = Integer.parseInt ( format );
switch ( cf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, cf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = cf;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "MarkLigPos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
assert markAnchors.size() == 0;
assert ligatureAnchors.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkMarkPos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
assert markAnchors.size() == 0;
assert baseOrMarkAnchors.size() == 0;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "MarkRecord" ) ) {
String[] pn1 = new String[] { null, "MarkArray" };
String[] pn2 = new String[] { null, "Mark1Array" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "MultipleSubst" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "PairPos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "PairSet" ) ) {
String[] pn = new String[] { null, "PairPos" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
int psi = -1;
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
psi = Integer.parseInt ( index );
}
assert psIndex == -1;
psIndex = psi;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "PairValueRecord" ) ) {
String[] pn = new String[] { null, "PairSet" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
assertPairClear();
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "PosLookupRecord" ) ) {
String[] pn1 = new String[] { null, "ChainContextSubst" };
String[] pn2 = new String[] { null, "ChainContextPos" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "ReqFeatureIndex" ) ) {
String[] pn1 = new String[] { null, "DefaultLangSys" };
String[] pn2 = new String[] { null, "LangSys" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
String fid;
if ( ( v >= 0 ) && ( v < 65535 ) ) {
fid = makeFeatureId ( v );
} else {
fid = null;
}
assertLanguageFeaturesClear();
languageFeatures.add ( fid );
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "Script" ) ) {
String[] pn = new String[] { null, "ScriptRecord" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ScriptList" ) ) {
String[] pn1 = new String[] { null, "GSUB" };
String[] pn2 = new String[] { null, "GPOS" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( ! isParent ( pnx ) ) {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "ScriptRecord" ) ) {
String[] pn = new String[] { null, "ScriptList" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ScriptTag" ) ) {
String[] pn = new String[] { null, "ScriptRecord" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
assert scriptTag == null;
scriptTag = value;
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "SecondGlyph" ) ) {
String[] pn = new String[] { null, "PairValueRecord" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
int gid = mapGlyphId ( value, en );
assert g2 == -1;
g2 = gid;
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Sequence" ) ) {
String[] pn = new String[] { null, "MultipleSubst" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
int i = Integer.parseInt ( index );
if ( i != subtableEntries.size() ) {
invalidIndex ( en, i, subtableEntries.size() );
}
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "SequenceIndex" ) ) {
String[] pn1 = new String[] { null, "PosLookupRecord" };
String[] pn2 = new String[] { null, "SubstLookupRecord" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
assert rlSequence == -1;
assert v != -1;
rlSequence = v;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "SinglePos" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "SingleSubst" ) ) {
String[] pn = new String[] { null, "Lookup" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
String format = attrs.getValue ( "Format" );
int sf = -1;
if ( format == null ) {
missingRequiredAttribute ( en, "Format" );
} else {
sf = Integer.parseInt ( format );
switch ( sf ) {
case 1:
case 2:
break;
default:
unsupportedFormat ( en, sf );
break;
}
}
assertCoverageClear();
ctIndex = 0;
ctFormat = 1;
assertSubtableClear();
assert sf >= 0;
stFormat = sf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "SubstLookupRecord" ) ) {
String[] pn = new String[] { null, "ChainContextSubst" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Substitute" ) ) {
String[] pn = new String[] { null, "Sequence" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( index == null ) {
missingRequiredAttribute ( en, "index" );
} else {
int i = Integer.parseInt ( index );
if ( i != substitutes.size() ) {
invalidIndex ( en, i, substitutes.size() );
}
}
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
int gid = mapGlyphId ( value, en );
substitutes.add ( Integer.valueOf ( gid ) );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Substitution" ) ) {
String[] pn = new String[] { null, "SingleSubst" };
if ( isParent ( pn ) ) {
String in = attrs.getValue ( "in" );
int igid = -1;
int ogid = -1;
if ( in == null ) {
missingRequiredAttribute ( en, "in" );
} else {
igid = mapGlyphId ( in, en );
}
String out = attrs.getValue ( "out" );
if ( out == null ) {
missingRequiredAttribute ( en, "out" );
} else {
ogid = mapGlyphId ( out, en );
}
coverageEntries.add ( Integer.valueOf ( igid ) );
subtableEntries.add ( Integer.valueOf ( ogid ) );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Value" ) ) {
String[] pn = new String[] { null, "SinglePos" };
if ( isParent ( pn ) ) {
String index = attrs.getValue ( "index" );
if ( vf1 < 0 ) {
missingParameter ( en, "value format" );
} else {
subtableEntries.add ( parseValue ( en, attrs, vf1 ) );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Value1" ) ) {
String[] pn = new String[] { null, "PairValueRecord" };
if ( isParent ( pn ) ) {
if ( vf1 < 0 ) {
missingParameter ( en, "value format 1" );
} else {
assert v1 == null;
v1 = parseValue ( en, attrs, vf1 );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Value2" ) ) {
String[] pn = new String[] { null, "PairValueRecord" };
if ( isParent ( pn ) ) {
if ( vf2 < 0 ) {
missingParameter ( en, "value format 2" );
} else {
assert v2 == null;
v2 = parseValue ( en, attrs, vf2 );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ValueFormat" ) ) {
String[] pn = new String[] { null, "SinglePos" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int vf = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
vf = Integer.parseInt ( value );
}
assert vf1 == -1;
vf1 = vf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ValueFormat1" ) ) {
String[] pn = new String[] { null, "PairPos" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int vf = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
vf = Integer.parseInt ( value );
}
assert vf1 == -1;
vf1 = vf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "ValueFormat2" ) ) {
String[] pn = new String[] { null, "PairPos" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int vf = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
vf = Integer.parseInt ( value );
}
assert vf2 == -1;
vf2 = vf;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "Version" ) ) {
String[] pn1 = new String[] { null, "GDEF" };
String[] pn2 = new String[] { null, "GPOS" };
String[] pn3 = new String[] { null, "GSUB" };
String[][] pnx = new String[][] { pn1, pn2, pn3 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "XCoordinate" ) ) {
String[] pn1 = new String[] { null, "BaseAnchor" };
String[] pn2 = new String[] { null, "EntryAnchor" };
String[] pn3 = new String[] { null, "ExitAnchor" };
String[] pn4 = new String[] { null, "LigatureAnchor" };
String[] pn5 = new String[] { null, "MarkAnchor" };
String[] pn6 = new String[] { null, "Mark2Anchor" };
String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
int x = 0;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
x = Integer.parseInt ( value );
}
assert xCoord == Integer.MIN_VALUE;
xCoord = x;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "YCoordinate" ) ) {
String[] pn1 = new String[] { null, "BaseAnchor" };
String[] pn2 = new String[] { null, "EntryAnchor" };
String[] pn3 = new String[] { null, "ExitAnchor" };
String[] pn4 = new String[] { null, "LigatureAnchor" };
String[] pn5 = new String[] { null, "MarkAnchor" };
String[] pn6 = new String[] { null, "Mark2Anchor" };
String[][] pnx = new String[][] { pn1, pn2, pn3, pn4, pn5, pn6 };
if ( isParent ( pnx ) ) {
String value = attrs.getValue ( "value" );
int y = 0;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
y = Integer.parseInt ( value );
}
assert yCoord == Integer.MIN_VALUE;
yCoord = y;
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "checkSumAdjustment" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "cmap" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "cmap_format_0" ) ) {
String[] pn = new String[] { null, "cmap" };
if ( isParent ( pn ) ) {
String platformID = attrs.getValue ( "platformID" );
if ( platformID == null ) {
missingRequiredAttribute ( en, "platformID" );
}
String platEncID = attrs.getValue ( "platEncID" );
if ( platEncID == null ) {
missingRequiredAttribute ( en, "platEncID" );
}
String language = attrs.getValue ( "language" );
if ( language == null ) {
missingRequiredAttribute ( en, "language" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "cmap_format_4" ) ) {
String[] pn = new String[] { null, "cmap" };
if ( isParent ( pn ) ) {
String platformID = attrs.getValue ( "platformID" );
int pid = -1;
if ( platformID == null ) {
missingRequiredAttribute ( en, "platformID" );
} else {
pid = Integer.parseInt ( platformID );
}
String platEncID = attrs.getValue ( "platEncID" );
int eid = -1;
if ( platEncID == null ) {
missingRequiredAttribute ( en, "platEncID" );
} else {
eid = Integer.parseInt ( platEncID );
}
String language = attrs.getValue ( "language" );
int lid = -1;
if ( language == null ) {
missingRequiredAttribute ( en, "language" );
} else {
lid = Integer.parseInt ( language );
}
assert cmapEntries.size() == 0;
assert cmPlatform == -1;
assert cmEncoding == -1;
assert cmLanguage == -1;
cmPlatform = pid;
cmEncoding = eid;
cmLanguage = lid;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "created" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "flags" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "fontDirectionHint" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "fontRevision" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "glyphDataFormat" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "head" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "hmtx" ) ) {
String[] pn = new String[] { null, "ttFont" };
if ( ! isParent ( pn ) ) {
notPermittedInElementContext ( en, getParent(), pn );
} else if ( glyphIdMax > 0 ) {
hmtxEntries.setSize ( glyphIdMax + 1 );
}
} else if ( en[1].equals ( "indexToLocFormat" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "lowestRecPPEM" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "macStyle" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "magicNumber" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "map" ) ) {
String[] pn1 = new String[] { null, "cmap_format_0" };
String[] pn2 = new String[] { null, "cmap_format_4" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pnx ) ) {
String code = attrs.getValue ( "code" );
int cid = -1;
if ( code == null ) {
missingRequiredAttribute ( en, "code" );
} else {
code = code.toLowerCase();
if ( code.startsWith ( "0x" ) ) {
cid = Integer.parseInt ( code.substring ( 2 ), 16 );
} else {
cid = Integer.parseInt ( code, 10 );
}
}
String name = attrs.getValue ( "name" );
int gid = -1;
if ( name == null ) {
missingRequiredAttribute ( en, "name" );
} else {
gid = mapGlyphId ( name, en );
}
if ( ( cmPlatform == 3 ) && ( cmEncoding == 1 ) ) {
cmapEntries.add ( new int[] { cid, gid } );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "modified" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "mtx" ) ) {
String[] pn = new String[] { null, "hmtx" };
if ( isParent ( pn ) ) {
String name = attrs.getValue ( "name" );
int gid = -1;
if ( name == null ) {
missingRequiredAttribute ( en, "name" );
} else {
gid = mapGlyphId ( name, en );
}
String width = attrs.getValue ( "width" );
int w = -1;
if ( width == null ) {
missingRequiredAttribute ( en, "width" );
} else {
w = Integer.parseInt ( width );
}
String lsb = attrs.getValue ( "lsb" );
int l = -1;
if ( lsb == null ) {
missingRequiredAttribute ( en, "lsb" );
} else {
l = Integer.parseInt ( lsb );
}
hmtxEntries.set ( gid, new int[] { w, l } );
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "tableVersion" ) ) {
String[] pn1 = new String[] { null, "cmap" };
String[] pn2 = new String[] { null, "head" };
String[][] pnx = new String[][] { pn1, pn2 };
if ( isParent ( pn1 ) ) { // child of cmap
String version = attrs.getValue ( "version" );
if ( version == null ) {
missingRequiredAttribute ( en, "version" );
}
} else if ( isParent ( pn2 ) ) { // child of head
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pnx );
}
} else if ( en[1].equals ( "ttFont" ) ) {
String[] pn = new String[] { null, null };
if ( isParent ( pn ) ) {
String sfntVersion = attrs.getValue ( "sfntVersion" );
if ( sfntVersion == null ) {
missingRequiredAttribute ( en, "sfntVersion" );
}
String ttLibVersion = attrs.getValue ( "ttLibVersion" );
if ( ttLibVersion == null ) {
missingRequiredAttribute ( en, "ttLibVersion" );
}
} else {
notPermittedInElementContext ( en, getParent(), null );
}
} else if ( en[1].equals ( "unitsPerEm" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
int v = -1;
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
} else {
v = Integer.parseInt ( value );
}
assert upem == -1;
upem = v;
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "xMax" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "xMin" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "yMax" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else if ( en[1].equals ( "yMin" ) ) {
String[] pn = new String[] { null, "head" };
if ( isParent ( pn ) ) {
String value = attrs.getValue ( "value" );
if ( value == null ) {
missingRequiredAttribute ( en, "value" );
}
} else {
notPermittedInElementContext ( en, getParent(), pn );
}
} else {
unsupportedElement ( en );
}
elements.push ( en );
}
@Override
public void endElement ( String uri, String localName, String qName ) throws SAXException {
if ( elements.empty() ) {
throw new SAXException ( "element stack is unbalanced, no elements on stack!" );
}
String[] enParent = elements.peek();
if ( enParent == null ) {
throw new SAXException ( "element stack is empty, elements are not balanced" );
}
String[] en = makeExpandedName ( uri, localName, qName );
if ( ! sameExpandedName ( enParent, en ) ) {
throw new SAXException ( "element stack is unbalanced, expanded name mismatch" );
}
if ( en[0] != null ) {
unsupportedElement ( en );
} else if ( isAnchorElement ( en[1] ) ) {
if ( xCoord == Integer.MIN_VALUE ) {
missingParameter ( en, "x coordinate" );
} else if ( yCoord == Integer.MIN_VALUE ) {
missingParameter ( en, "y coordinate" );
} else {
if ( en[1].equals ( "EntryAnchor" ) ) {
if ( anchors.size() > 0 ) {
duplicateParameter ( en, "entry anchor" );
}
} else if ( en[1].equals ( "ExitAnchor" ) ) {
if ( anchors.size() > 1 ) {
duplicateParameter ( en, "exit anchor" );
} else if ( anchors.size() == 0 ) {
anchors.add ( null );
}
}
anchors.add ( new GlyphPositioningTable.Anchor ( xCoord, yCoord ) );
xCoord = yCoord = Integer.MIN_VALUE;
}
} else if ( en[1].equals ( "AlternateSet" ) ) {
subtableEntries.add ( extractAlternates() );
} else if ( en[1].equals ( "AlternateSubst" ) ) {
if ( ! sortEntries ( coverageEntries, subtableEntries ) ) {
mismatchedEntries ( en, coverageEntries.size(), subtableEntries.size() );
}
addGSUBSubtable ( GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE, extractCoverage() );
} else if ( en[1].equals ( "BacktrackCoverage" ) ) {
String ck = makeCoverageKey ( "bk", ctIndex );
if ( coverages.containsKey ( ck ) ) {
duplicateCoverageIndex ( en, ctIndex );
} else {
coverages.put ( ck, extractCoverage() );
}
} else if ( en[1].equals ( "BaseCoverage" ) ) {
coverages.put ( "base", extractCoverage() );
} else if ( en[1].equals ( "BaseRecord" ) ) {
baseOrMarkAnchors.add ( extractAnchors() );
} else if ( en[1].equals ( "ChainContextPos" ) || en[1].equals ( "ChainContextSubst" ) ) {
GlyphCoverageTable coverage = null;
if ( stFormat == 3 ) {
GlyphCoverageTable igca[] = getCoveragesWithPrefix ( "in" );
GlyphCoverageTable bgca[] = getCoveragesWithPrefix ( "bk" );
GlyphCoverageTable lgca[] = getCoveragesWithPrefix ( "la" );
if ( ( igca.length == 0 ) || hasMissingCoverage ( igca ) ) {
missingCoverage ( en, "input", igca.length );
} else if ( hasMissingCoverage ( bgca ) ) {
missingCoverage ( en, "backtrack", bgca.length );
} else if ( hasMissingCoverage ( lgca ) ) {
missingCoverage ( en, "lookahead", lgca.length );
} else {
GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( extractRuleLookups(), igca.length, igca, bgca, lgca );
GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
coverage = igca [ 0 ];
subtableEntries.add ( rsa );
}
} else {
unsupportedFormat ( en, stFormat );
}
if ( en[1].equals ( "ChainContextPos" ) ) {
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL, coverage );
} else if ( en[1].equals ( "ChainContextSubst" ) ) {
addGSUBSubtable ( GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL, coverage );
}
} else if ( en[1].equals ( "ComponentRecord" ) ) {
components.add ( extractAnchors() );
} else if ( en[1].equals ( "Coverage" ) ) {
coverages.put ( "main", extractCoverage() );
} else if ( en[1].equals ( "DefaultLangSys" ) || en[1].equals ( "LangSysRecord" ) ) {
if ( languageTag == null ) {
missingTag ( en, "language" );
} else if ( languages.containsKey ( languageTag ) ) {
duplicateTag ( en, "language", languageTag );
} else {
languages.put ( languageTag, extractLanguageFeatures() );
languageTag = null;
}
} else if ( en[1].equals ( "CursivePos" ) ) {
GlyphCoverageTable ct = coverages.get ( "main" );
if ( ct == null ) {
missingParameter ( en, "coverages" );
} else if ( stFormat == 1 ) {
subtableEntries.add ( extractAttachmentAnchors() );
} else {
unsupportedFormat ( en, stFormat );
}
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE, ct );
} else if ( en[1].equals ( "EntryExitRecord" ) ) {
int na = anchors.size();
if ( na == 0 ) {
missingParameter ( en, "entry or exit anchor" );
} else if ( na == 1 ) {
anchors.add ( null );
} else if ( na > 2 ) {
duplicateParameter ( en, "entry or exit anchor" );
}
attachmentAnchors.add ( extractAnchors() );
} else if ( en[1].equals ( "BaseRecord" ) ) {
baseOrMarkAnchors.add ( extractAnchors() );
} else if ( en[1].equals ( "FeatureRecord" ) ) {
if ( flIndex != flSequence ) {
mismatchedIndex ( en, "feature", flIndex, flSequence );
} else if ( featureTag == null ) {
missingTag ( en, "feature" );
} else {
String fid = makeFeatureId ( flIndex );
features.put ( fid, extractFeature() );
nextFeature();
}
} else if ( en[1].equals ( "GDEF" ) ) {
if ( subtables.size() > 0 ) {
gdef = new GlyphDefinitionTable ( subtables );
}
clearTable();
} else if ( en[1].equals ( "GPOS" ) ) {
if ( subtables.size() > 0 ) {
gpos = new GlyphPositioningTable ( gdef, extractLookups(), subtables );
}
clearTable();
} else if ( en[1].equals ( "GSUB" ) ) {
if ( subtables.size() > 0 ) {
gsub = new GlyphSubstitutionTable ( gdef, extractLookups(), subtables );
}
clearTable();
} else if ( en[1].equals ( "GlyphClassDef" ) ) {
GlyphMappingTable mapping = extractClassDefMapping ( glyphClasses, stFormat, true );
addGDEFSubtable ( GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS, mapping );
} else if ( en[1].equals ( "InputCoverage" ) ) {
String ck = makeCoverageKey ( "in", ctIndex );
if ( coverages.containsKey ( ck ) ) {
duplicateCoverageIndex ( en, ctIndex );
} else {
coverages.put ( ck, extractCoverage() );
}
} else if ( en[1].equals ( "LigatureAttach" ) ) {
ligatureAnchors.add ( extractComponents() );
} else if ( en[1].equals ( "LigatureCoverage" ) ) {
coverages.put ( "liga", extractCoverage() );
} else if ( en[1].equals ( "LigatureSet" ) ) {
subtableEntries.add ( extractLigatures() );
} else if ( en[1].equals ( "LigatureSubst" ) ) {
if ( ! sortEntries ( coverageEntries, subtableEntries ) ) {
mismatchedEntries ( en, coverageEntries.size(), subtableEntries.size() );
}
GlyphCoverageTable coverage = extractCoverage();
addGSUBSubtable ( GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE, coverage );
} else if ( en[1].equals ( "LookAheadCoverage" ) ) {
String ck = makeCoverageKey ( "la", ctIndex );
if ( coverages.containsKey ( ck ) ) {
duplicateCoverageIndex ( en, ctIndex );
} else {
coverages.put ( ck, extractCoverage() );
}
} else if ( en[1].equals ( "Lookup" ) ) {
if ( ltIndex != ltSequence ) {
mismatchedIndex ( en, "lookup", ltIndex, ltSequence );
} else {
nextLookup();
}
} else if ( en[1].equals ( "MarkAttachClassDef" ) ) {
GlyphMappingTable mapping = extractClassDefMapping ( glyphClasses, stFormat, true );
addGDEFSubtable ( GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT, mapping );
} else if ( en[1].equals ( "MarkCoverage" ) ) {
coverages.put ( "mark", extractCoverage() );
} else if ( en[1].equals ( "Mark1Coverage" ) ) {
coverages.put ( "mrk1", extractCoverage() );
} else if ( en[1].equals ( "Mark2Coverage" ) ) {
coverages.put ( "mrk2", extractCoverage() );
} else if ( en[1].equals ( "MarkBasePos" ) ) {
GlyphCoverageTable mct = coverages.get ( "mark" );
GlyphCoverageTable bct = coverages.get ( "base" );
if ( mct == null ) {
missingParameter ( en, "mark coverages" );
} else if ( bct == null ) {
missingParameter ( en, "base coverages" );
} else if ( stFormat == 1 ) {
MarkAnchor[] maa = extractMarkAnchors();
Anchor[][] bam = extractBaseOrMarkAnchors();
subtableEntries.add ( bct );
subtableEntries.add ( computeClassCount ( bam ) );
subtableEntries.add ( maa );
subtableEntries.add ( bam );
} else {
unsupportedFormat ( en, stFormat );
}
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE, mct );
} else if ( en[1].equals ( "MarkLigPos" ) ) {
GlyphCoverageTable mct = coverages.get ( "mark" );
GlyphCoverageTable lct = coverages.get ( "liga" );
if ( mct == null ) {
missingParameter ( en, "mark coverages" );
} else if ( lct == null ) {
missingParameter ( en, "ligature coverages" );
} else if ( stFormat == 1 ) {
MarkAnchor[] maa = extractMarkAnchors();
Anchor[][][] lam = extractLigatureAnchors();
subtableEntries.add ( lct );
subtableEntries.add ( computeLigaturesClassCount ( lam ) );
subtableEntries.add ( computeLigaturesComponentCount ( lam ) );
subtableEntries.add ( maa );
subtableEntries.add ( lam );
} else {
unsupportedFormat ( en, stFormat );
}
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE, mct );
} else if ( en[1].equals ( "MarkMarkPos" ) ) {
GlyphCoverageTable mct1 = coverages.get ( "mrk1" );
GlyphCoverageTable mct2 = coverages.get ( "mrk2" );
if ( mct1 == null ) {
missingParameter ( en, "mark coverages 1" );
} else if ( mct2 == null ) {
missingParameter ( en, "mark coverages 2" );
} else if ( stFormat == 1 ) {
MarkAnchor[] maa = extractMarkAnchors();
Anchor[][] mam = extractBaseOrMarkAnchors();
subtableEntries.add ( mct2 );
subtableEntries.add ( computeClassCount ( mam ) );
subtableEntries.add ( maa );
subtableEntries.add ( mam );
} else {
unsupportedFormat ( en, stFormat );
}
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK, mct1 );
} else if ( en[1].equals ( "MarkRecord" ) ) {
if ( markClass == -1 ) {
missingParameter ( en, "mark class" );
} else if ( anchors.size() == 0 ) {
missingParameter ( en, "mark anchor" );
} else if ( anchors.size() > 1 ) {
duplicateParameter ( en, "mark anchor" );
} else {
markAnchors.add ( new GlyphPositioningTable.MarkAnchor ( markClass, anchors.get(0) ) );
markClass = -1;
anchors.clear();
}
} else if ( en[1].equals ( "Mark2Record" ) ) {
baseOrMarkAnchors.add ( extractAnchors() );
} else if ( en[1].equals ( "MultipleSubst" ) ) {
GlyphCoverageTable coverage = coverages.get ( "main" );
addGSUBSubtable ( GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE, coverage, extractSequenceEntries() );
} else if ( en[1].equals ( "PairPos" ) ) {
assertSubtableEntriesClear();
if ( stFormat == 1 ) {
if ( pairSets.size() == 0 ) {
missingParameter ( en, "pair set" );
} else {
subtableEntries.add ( extractPairSets() );
}
} else if ( stFormat == 2 ) {
unsupportedFormat ( en, stFormat );
}
GlyphCoverageTable coverage = coverages.get ( "main" );
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR, coverage );
vf1 = vf2 = -1; psIndex = -1;
} else if ( en[1].equals ( "PairSet" ) ) {
if ( psIndex != pairSets.size() ) {
invalidIndex ( en, psIndex, pairSets.size() );
} else {
pairSets.add ( extractPairs() );
}
} else if ( en[1].equals ( "PairValueRecord" ) ) {
if ( g2 == -1 ) {
missingParameter ( en, "second glyph" );
} else if ( ( v1 == null ) && ( v2 == null ) ) {
missingParameter ( en, "first or second value" );
} else {
pairs.add ( new PairValues ( g2, v1, v2 ) );
clearPair();
}
} else if ( en[1].equals ( "PosLookupRecord" ) || en[1].equals ( "SubstLookupRecord" ) ) {
if ( rlSequence < 0 ) {
missingParameter ( en, "sequence index" );
} else if ( rlLookup < 0 ) {
missingParameter ( en, "lookup index" );
} else {
ruleLookups.add ( new GlyphTable.RuleLookup ( rlSequence, rlLookup ) );
rlSequence = rlLookup = -1;
}
} else if ( en[1].equals ( "Script" ) ) {
if ( scriptTag == null ) {
missingTag ( en, "script" );
} else if ( scripts.containsKey ( scriptTag ) ) {
duplicateTag ( en, "script", scriptTag );
} else {
scripts.put ( scriptTag, extractLanguages() );
scriptTag = null;
}
} else if ( en[1].equals ( "Sequence" ) ) {
subtableEntries.add ( extractSubstitutes() );
} else if ( en[1].equals ( "SinglePos" ) ) {
int nv = subtableEntries.size();
if ( stFormat == 1 ) {
if ( nv < 0 ) {
missingParameter ( en, "value" );
} else if ( nv > 1 ) {
duplicateParameter ( en, "value" );
}
} else if ( stFormat == 2 ) {
GlyphPositioningTable.Value[] pva = (GlyphPositioningTable.Value[]) subtableEntries.toArray ( new GlyphPositioningTable.Value [ nv ] );
subtableEntries.clear();
subtableEntries.add ( pva );
}
GlyphCoverageTable coverage = coverages.get ( "main" );
addGPOSSubtable ( GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE, coverage );
vf1 = -1;
} else if ( en[1].equals ( "SingleSubst" ) ) {
if ( ! sortEntries ( coverageEntries, subtableEntries ) ) {
mismatchedEntries ( en, coverageEntries.size(), subtableEntries.size() );
}
GlyphCoverageTable coverage = extractCoverage();
addGSUBSubtable ( GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE, coverage );
} else if ( en[1].equals ( "cmap" ) ) {
cmap = getCMAP();
gmap = getGMAP();
cmapEntries.clear();
} else if ( en[1].equals ( "cmap_format_4" ) ) {
cmPlatform = cmEncoding = cmLanguage = -1;
} else if ( en[1].equals ( "hmtx" ) ) {
hmtx = getHMTX();
hmtxEntries.clear();
} else if ( en[1].equals ( "ttFont" ) ) {
if ( cmap == null ) {
missingParameter ( en, "cmap" );
}
if ( hmtx == null ) {
missingParameter ( en, "hmtx" );
}
}
elements.pop();
}
@Override
public void characters ( char[] chars, int start, int length ) {
}
private String[] getParent() {
if ( ! elements.empty() ) {
return elements.peek();
} else {
return new String[] { null, null };
}
}
private boolean isParent ( Object enx ) {
if ( enx instanceof String[][] ) {
for ( String[] en : (String[][]) enx ) {
if ( isParent ( en ) ) {
return true;
}
}
return false;
} else if ( enx instanceof String[] ) {
String[] en = (String[]) enx;
if ( ! elements.empty() ) {
String[] pn = elements.peek();
return ( pn != null ) && sameExpandedName ( en, pn );
} else if ( ( en[0] == null ) && ( en[1] == null ) ) {
return true;
} else {
return false;
}
} else {
return false;
}
}
private boolean isAnchorElement ( String ln ) {
if ( ln.equals ( "BaseAnchor" ) ) {
return true;
} else if ( ln.equals ( "EntryAnchor" ) ) {
return true;
} else if ( ln.equals ( "ExitAnchor" ) ) {
return true;
} else if ( ln.equals ( "LigatureAnchor" ) ) {
return true;
} else if ( ln.equals ( "MarkAnchor" ) ) {
return true;
} else if ( ln.equals ( "Mark2Anchor" ) ) {
return true;
} else {
return false;
}
}
private Map<Integer,Integer> getCMAP() {
Map<Integer,Integer> cmap = new TreeMap();
for ( int[] cme : cmapEntries ) {
Integer c = Integer.valueOf ( cme[0] );
Integer g = Integer.valueOf ( cme[1] );
cmap.put ( c, g );
}
return cmap;
}
private Map<Integer,Integer> getGMAP() {
Map<Integer,Integer> gmap = new TreeMap();
for ( int[] cme : cmapEntries ) {
Integer c = Integer.valueOf ( cme[0] );
Integer g = Integer.valueOf ( cme[1] );
gmap.put ( g, c );
}
return gmap;
}
private int[][] getHMTX() {
int ne = hmtxEntries.size();
int[][] hmtx = new int [ ne ] [ 2 ];
for ( int i = 0; i < ne; i++ ) {
int[] ea = hmtxEntries.get(i);
if ( ea != null ) {
hmtx [ i ] [ 0 ] = ea[0];
hmtx [ i ] [ 1 ] = ea[1];
}
}
return hmtx;
}
private GlyphClassTable extractClassDefMapping ( Map<String,Integer> glyphClasses, int format, boolean clearSourceMap ) {
GlyphClassTable ct;
if ( format == 1 ) {
ct = extractClassDefMapping1 ( extractClassMappings ( glyphClasses, clearSourceMap ) );
} else if ( format == 2 ) {
ct = extractClassDefMapping2 ( extractClassMappings ( glyphClasses, clearSourceMap ) );
} else {
ct = null;
}
return ct;
}
private GlyphClassTable extractClassDefMapping1 ( int[][] cma ) {
List entries = new ArrayList<Integer>();
int s = -1;
int l = -1;
Integer zero = Integer.valueOf(0);
for ( int[] m : cma ) {
int g = m[0];
int c = m[1];
if ( s < 0 ) {
s = g;
l = g - 1;
entries.add ( Integer.valueOf ( s ) );
}
while ( g > ( l + 1 ) ) {
entries.add ( zero );
l++;
}
assert l == ( g - 1 );
entries.add ( Integer.valueOf ( c ) );
l = g;
}
return GlyphClassTable.createClassTable ( entries );
}
private GlyphClassTable extractClassDefMapping2 ( int[][] cma ) {
List entries = new ArrayList<Integer>();
int s = -1;
int e = s;
int l = -1;
for ( int[] m : cma ) {
int g = m[0];
int c = m[1];
if ( c != l ) {
if ( s >= 0 ) {
entries.add ( new GlyphClassTable.MappingRange ( s, e, l ) );
}
s = e = g;
} else {
e = g;
}
l = c;
}
return GlyphClassTable.createClassTable ( entries );
}
private int[][] extractClassMappings ( Map<String,Integer> glyphClasses, boolean clearSourceMap ) {
int nc = glyphClasses.size();
int i = 0;
int[][] cma = new int [ nc ] [ 2 ];
for ( Map.Entry<String,Integer> e : glyphClasses.entrySet() ) {
Integer gid = glyphIds.get ( e.getKey() );
assert gid != null;
int[] m = cma [ i ];
m [ 0 ] = (int) gid;
m [ 1 ] = (int) e.getValue();
i++;
}
if ( clearSourceMap ) {
glyphClasses.clear();
}
return sortClassMappings ( cma );
}
private int[][] sortClassMappings ( int[][] cma ) {
Arrays.sort ( cma, new Comparator<int[]>() {
public int compare ( int[] m1, int[] m2 ) {
assert m1.length > 0;
assert m2.length > 0;
if ( m1[0] < m2[0] ) {
return -1;
} else if ( m1[0] > m2[0] ) {
return 1;
} else {
return 0;
}
}
}
);
return cma;
}
// sort coverage entries and subtable entries together
private boolean sortEntries ( List cel, List sel ) {
assert cel != null;
assert sel != null;
if ( cel.size() == sel.size() ) {
int np = cel.size();
Object[][] pa = new Object [ np ] [ 2 ];
for ( int i = 0; i < np; i++ ) {
pa [ i ] [ 0 ] = cel.get ( i );
pa [ i ] [ 1 ] = sel.get ( i );
}
Arrays.sort ( pa, new Comparator<Object[]>() {
public int compare ( Object[] p1, Object[] p2 ) {
assert p1.length == 2;
assert p2.length == 2;
int c1 = (Integer) p1[0];
int c2 = (Integer) p2[0];
if ( c1 < c2 ) {
return -1;
} else if ( c1 > c2 ) {
return 1;
} else {
return 0;
}
}
}
);
cel.clear();
sel.clear();
for ( int i = 0; i < np; i++ ) {
cel.add ( pa [ i ] [ 0 ] );
sel.add ( pa [ i ] [ 1 ] );
}
assert cel.size() == sel.size();
return true;
} else {
return false;
}
}
private String makeCoverageKey ( String prefix, int index ) {
assert prefix != null;
assert prefix.length() == 2;
assert index < 100;
return prefix + CharUtilities.padLeft ( Integer.toString ( index, 10 ), 2, '0' );
}
private List extractCoverageEntries() {
List entries = new ArrayList<Integer> ( coverageEntries );
clearCoverage();
return entries;
}
private void clearCoverageEntries() {
coverageEntries.clear();
ctFormat = -1;
ctIndex = -1;
}
private void assertCoverageEntriesClear() {
assert coverageEntries.size() == 0;
}
private GlyphCoverageTable extractCoverage() {
assert ( ctFormat == 1 ) || ( ctFormat == 2 );
assert ctIndex >= 0;
GlyphCoverageTable coverage = GlyphCoverageTable.createCoverageTable ( extractCoverageEntries() );
clearCoverage();
return coverage;
}
private void clearCoverages() {
coverages.clear();
}
private void assertCoverageClear() {
assert ctFormat == -1;
assert ctIndex == -1;
assertCoverageEntriesClear();
}
private void clearCoverage() {
ctFormat = -1;
ctIndex = -1;
clearCoverageEntries();
}
private void assertCoveragesClear() {
assert coverages.size() == 0;
}
private GlyphCoverageTable[] getCoveragesWithPrefix ( String prefix ) {
assert prefix != null;
int prefixLength = prefix.length();
Set<String> keys = coverages.keySet();
int mi = -1; // maximum coverage table index
for ( String k : keys ) {
if ( k.startsWith ( prefix ) ) {
int i = Integer.parseInt ( k.substring ( prefixLength ) );
if ( i > mi ) {
mi = i;
}
}
}
GlyphCoverageTable[] gca = new GlyphCoverageTable [ mi + 1 ];
for ( String k : keys ) {
if ( k.startsWith ( prefix ) ) {
int i = Integer.parseInt ( k.substring ( prefixLength ) );
if ( i >= 0 ) {
gca [ i ] = coverages.get ( k );
}
}
}
return gca;
}
private boolean hasMissingCoverage ( GlyphCoverageTable[] gca ) {
assert gca != null;
int nc = 0;
for ( int i = 0, n = gca.length; i < n; i++ ) {
if ( gca [ i ] != null ) {
nc++;
}
}
return nc != gca.length;
}
private String makeFeatureId ( int fid ) {
assert fid >= 0;
return "f" + fid;
}
private String makeLookupId ( int lid ) {
assert lid >= 0;
return "lu" + lid;
}
private void clearScripts() {
scripts.clear();
}
private List<String> extractLanguageFeatures() {
List<String> lfl = new ArrayList<String>(languageFeatures);
clearLanguageFeatures();
return lfl;
}
private void assertLanguageFeaturesClear() {
assert languageFeatures.size() == 0;
}
private void clearLanguageFeatures() {
languageFeatures.clear();
}
private Map<String,List<String>> extractLanguages() {
Map<String,List<String>> lm = new HashMap ( languages );
clearLanguages();
return lm;
}
private void clearLanguages() {
languages.clear();
}
private void assertFeatureLookupsClear() {
assert featureLookups.size() == 0;
}
private List extractFeatureLookups() {
List lookups = new ArrayList<String> ( featureLookups );
clearFeatureLookups();
return lookups;
}
private void clearFeatureLookups() {
featureLookups.clear();
}
private void assertFeatureClear() {
assert flIndex == -1;
assert featureTag == null;
assertFeatureLookupsClear();
}
private Object[] extractFeature() {
Object[] fa = new Object [ 2 ];
fa[0] = featureTag;
fa[1] = extractFeatureLookups();
clearFeature();
return fa;
}
private void clearFeature() {
flIndex = -1;
featureTag = null;
clearFeatureLookups();
}
private void nextFeature() {
flSequence++;
}
private void clearFeatures() {
features.clear();
}
private void clearSubtableInLookup() {
stFormat = 0;
clearCoverages();
}
private void clearSubtablesInLookup() {
clearSubtableInLookup();
stSequence = 0;
}
private void clearSubtablesInTable() {
clearSubtablesInLookup();
subtables.clear();
}
private void nextSubtableInLookup() {
stSequence++;
clearSubtableInLookup();
}
private void assertLookupClear() {
assert ltIndex == -1;
assert ltFlags == 0;
}
private void clearLookup() {
ltIndex = -1;
ltFlags = 0;
clearSubtablesInLookup();
}
private Map<GlyphTable.LookupSpec,List<String>> extractLookups() {
Map<GlyphTable.LookupSpec,List<String>> lookups = new LinkedHashMap<GlyphTable.LookupSpec,List<String>>();
for ( String st : scripts.keySet() ) {
Map<String,List<String>> lm = scripts.get ( st );
if ( lm != null ) {
for ( String lt : lm.keySet() ) {
List<String> fids = lm.get ( lt );
if ( fids != null ) {
for ( String fid : fids ) {
if ( fid != null ) {
Object[] fa = features.get ( fid );
if ( fa != null ) {
assert fa.length == 2;
String ft = (String) fa[0];
List<String> lids = (List<String>) fa[1];
if ( ( lids != null ) && ( lids.size() > 0 ) ) {
GlyphTable.LookupSpec ls = new GlyphTable.LookupSpec ( st, lt, ft );
lookups.put ( ls, lids );
}
}
}
}
}
}
}
}
clearScripts();
clearLanguages();
clearFeatures();
return lookups;
}
private void clearLookups() {
clearLookup();
clearSubtablesInTable();
ltSequence = 0;
flSequence = 0;
}
private void nextLookup() {
ltSequence++;
clearLookup();
}
private void clearTable() {
clearLookups();
}
private void assertSubtableClear() {
assert stFormat == 0;
assertCoverageEntriesClear();
}
private void assertSubtablesClear() {
assertSubtableClear();
assert subtables.size() == 0;
}
private void clearSubtableEntries() {
subtableEntries.clear();
}
private void assertSubtableEntriesClear() {
assert subtableEntries.size() == 0;
}
private List extractSubtableEntries() {
List entries = new ArrayList ( subtableEntries );
clearSubtableEntries();
return entries;
}
private int[] extractAlternates() {
int[] aa = new int [ alternates.size() ];
int i = 0;
for ( Integer a : alternates ) {
aa[i++] = (int) a;
}
clearAlternates();
return aa;
}
private void clearAlternates() {
alternates.clear();
}
private LigatureSet extractLigatures() {
LigatureSet ls = new LigatureSet ( ligatures );
clearLigatures();
return ls;
}
private void clearLigatures() {
ligatures.clear();
}
private int[] extractSubstitutes() {
int[] aa = new int [ substitutes.size() ];
int i = 0;
for ( Integer a : substitutes ) {
aa[i++] = (int) a;
}
clearSubstitutes();
return aa;
}
private void clearSubstitutes() {
substitutes.clear();
}
private List extractSequenceEntries() {
List sequences = extractSubtableEntries();
int[][] sa = new int [ sequences.size() ] [];
int i = 0;
for ( Object s : sequences ) {
if ( s instanceof int[] ) {
sa[i++] = (int[]) s;
}
}
List entries = new ArrayList();
entries.add ( sa );
return entries;
}
private RuleLookup[] extractRuleLookups() {
RuleLookup[] lookups = (RuleLookup[]) ruleLookups.toArray ( new RuleLookup [ ruleLookups.size() ] );
clearRuleLookups();
return lookups;
}
private void clearRuleLookups() {
ruleLookups.clear();
}
private GlyphPositioningTable.Value parseValue ( String[] en, Attributes attrs, int format ) throws SAXException {
String xPlacement = attrs.getValue ( "XPlacement" );
int xp = 0;
if ( xPlacement != null ) {
xp = Integer.parseInt ( xPlacement );
} else if ( ( format & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) {
missingParameter ( en, "xPlacement" );
}
String yPlacement = attrs.getValue ( "YPlacement" );
int yp = 0;
if ( yPlacement != null ) {
yp = Integer.parseInt ( yPlacement );
} else if ( ( format & GlyphPositioningTable.Value.Y_PLACEMENT ) != 0 ) {
missingParameter ( en, "yPlacement" );
}
String xAdvance = attrs.getValue ( "XAdvance" );
int xa = 0;
if ( xAdvance != null ) {
xa = Integer.parseInt ( xAdvance );
} else if ( ( format & GlyphPositioningTable.Value.X_ADVANCE ) != 0 ) {
missingParameter ( en, "xAdvance" );
}
String yAdvance = attrs.getValue ( "YAdvance" );
int ya = 0;;
if ( yAdvance != null ) {
ya = Integer.parseInt ( yAdvance );
} else if ( ( format & GlyphPositioningTable.Value.Y_ADVANCE ) != 0 ) {
missingParameter ( en, "yAdvance" );
}
return new GlyphPositioningTable.Value ( xp, yp, xa, ya, null, null, null, null );
}
private void assertPairClear() {
assert g2 == -1;
assert v1 == null;
assert v2 == null;
}
private void clearPair() {
g2 = -1;
v1 = null;
v2 = null;
}
private void assertPairsClear() {
assert pairs.size() == 0;
}
private void clearPairs() {
pairs.clear();
psIndex = -1;
}
private PairValues[] extractPairs() {
PairValues[] pva = (PairValues[]) pairs.toArray ( new PairValues [ pairs.size() ] );
clearPairs();
return pva;
}
private void assertPairSetsClear() {
assert pairSets.size() == 0;
}
private void clearPairSets() {
pairSets.clear();
}
private PairValues[][] extractPairSets() {
PairValues[][] pvm = (PairValues[][]) pairSets.toArray ( new PairValues [ pairSets.size() ][] );
clearPairSets();
return pvm;
}
private Anchor[] extractAnchors() {
Anchor[] aa = (Anchor[]) anchors.toArray ( new Anchor [ anchors.size() ] );
anchors.clear();
return aa;
}
private MarkAnchor[] extractMarkAnchors() {
MarkAnchor[] maa = new MarkAnchor [ markAnchors.size() ];
maa = (MarkAnchor[]) markAnchors.toArray ( new MarkAnchor [ maa.length ] );
markAnchors.clear();
return maa;
}
private Anchor[][] extractBaseOrMarkAnchors() {
int na = baseOrMarkAnchors.size();
int ncMax = 0;
for ( Anchor[] aa : baseOrMarkAnchors ) {
if ( aa != null ) {
int nc = aa.length;
if ( nc > ncMax ) {
ncMax = nc;
}
}
}
Anchor[][] am = new Anchor [ na ][ ncMax ];
for ( int i = 0; i < na; i++ ) {
Anchor[] aa = baseOrMarkAnchors.get(i);
if ( aa != null ) {
for ( int j = 0; j < ncMax; j++ ) {
if ( j < aa.length ) {
am [ i ] [ j ] = aa [ j ];
}
}
}
}
baseOrMarkAnchors.clear();
return am;
}
private Integer computeClassCount ( Anchor[][] am ) {
int ncMax = 0;
for ( int i = 0, n = am.length; i < n; i++ ) {
Anchor[] aa = am [ i ];
if ( aa != null ) {
int nc = aa.length;
if ( nc > ncMax ) {
ncMax = nc;
}
}
}
return Integer.valueOf ( ncMax );
}
private Anchor[][] extractComponents() {
Anchor[][] cam = new Anchor [ components.size() ][];
cam = (Anchor[][]) components.toArray ( new Anchor [ cam.length ][] );
components.clear();
return cam;
}
private Anchor[][][] extractLigatureAnchors() {
int na = ligatureAnchors.size();
int ncMax = 0;
int nxMax = 0;
for ( Anchor[][] cm : ligatureAnchors ) {
if ( cm != null ) {
int nx = cm.length;
if ( nx > nxMax ) {
nxMax = nx;
}
for ( Anchor[] aa : cm ) {
if ( aa != null ) {
int nc = aa.length;
if ( nc > ncMax ) {
ncMax = nc;
}
}
}
}
}
Anchor[][][] lam = new Anchor [ na ] [ nxMax ] [ ncMax ];
for ( int i = 0; i < na; i++ ) {
Anchor[][] cm = ligatureAnchors.get(i);
if ( cm != null ) {
for ( int j = 0; j < nxMax; j++ ) {
if ( j < cm.length ) {
Anchor[] aa = cm [ j ];
if ( aa != null ) {
for ( int k = 0; k < ncMax; k++ ) {
if ( k < aa.length ) {
lam [ i ] [ j ] [ k ] = aa [ k ];
}
}
}
}
}
}
}
ligatureAnchors.clear();
return lam;
}
private Integer computeLigaturesClassCount ( Anchor[][][] lam ) {
int ncMax = 0;
if ( lam != null ) {
for ( Anchor[][] cm : lam ) {
if ( cm != null ) {
for ( Anchor[] aa : cm ) {
if ( aa != null ) {
int nc = aa.length;;
if ( nc > ncMax ) {
ncMax = nc;
}
}
}
}
}
}
return Integer.valueOf ( ncMax );
}
private Integer computeLigaturesComponentCount ( Anchor[][][] lam ) {
int nxMax = 0;
if ( lam != null ) {
for ( Anchor[][] cm : lam ) {
if ( cm != null ) {
int nx = cm.length;;
if ( nx > nxMax ) {
nxMax = nx;
}
}
}
}
return Integer.valueOf ( nxMax );
}
private Anchor[] extractAttachmentAnchors() {
int na = attachmentAnchors.size();
Anchor[] aa = new Anchor [ na * 2 ];
for ( int i = 0; i < na; i++ ) {
Anchor[] ea = attachmentAnchors.get(i);
int ne = ea.length;
if ( ne > 0 ) {
aa [ ( i * 2 ) + 0 ] = ea[0];
}
if ( ne > 1 ) {
aa [ ( i * 2 ) + 1 ] = ea[1];
}
}
attachmentAnchors.clear();
return aa;
}
private void addGDEFSubtable ( int stType, GlyphMappingTable mapping ) {
subtables.add ( GlyphDefinitionTable.createSubtable ( stType, makeLookupId ( ltSequence ), stSequence, ltFlags, stFormat, mapping, extractSubtableEntries() ) );
nextSubtableInLookup();
}
private void addGSUBSubtable ( int stType, GlyphCoverageTable coverage, List entries ) {
subtables.add ( GlyphSubstitutionTable.createSubtable ( stType, makeLookupId ( ltSequence ), stSequence, ltFlags, stFormat, coverage, entries ) );
nextSubtableInLookup();
}
private void addGSUBSubtable ( int stType, GlyphCoverageTable coverage ) {
addGSUBSubtable ( stType, coverage, extractSubtableEntries() );
}
private void addGPOSSubtable ( int stType, GlyphCoverageTable coverage, List entries ) {
subtables.add ( GlyphPositioningTable.createSubtable ( stType, makeLookupId ( ltSequence ), stSequence, ltFlags, stFormat, coverage, entries ) );
nextSubtableInLookup();
}
private void addGPOSSubtable ( int stType, GlyphCoverageTable coverage ) {
addGPOSSubtable ( stType, coverage, extractSubtableEntries() );
}
}
private int mapGlyphId0 ( String glyph ) {
assert glyphIds != null;
Integer gid = glyphIds.get ( glyph );
if ( gid != null ) {
return (int) gid;
} else {
return -1;
}
}
private int mapGlyphId ( String glyph, String[] currentElement ) throws SAXException {
int g = mapGlyphId0 ( glyph );
if ( g < 0 ) {
unsupportedGlyph ( currentElement, glyph );
return -1;
} else {
return g;
}
}
private int[] mapGlyphIds ( String glyphs, String[] currentElement ) throws SAXException {
String[] ga = glyphs.split(",");
int[] gids = new int [ ga.length ];
int i = 0;
for ( String glyph : ga ) {
gids[i++] = mapGlyphId ( glyph, currentElement );
}
return gids;
}
private int mapGlyphIdToChar ( String glyph ) {
assert glyphIds != null;
Integer gid = glyphIds.get ( glyph );
if ( gid != null ) {
if ( gmap != null ) {
Integer cid = gmap.get ( gid );
if ( cid != null ) {
return cid.intValue();
}
}
}
return -1;
}
private String formatLocator() {
if ( locator == null ) {
return "{null}";
} else {
return "{" + locator.getSystemId() + ":" + locator.getLineNumber() + ":" + locator.getColumnNumber() + "}";
}
}
private void unsupportedElement ( String[] en ) throws SAXException {
throw new SAXException ( formatLocator() + ": unsupported element " + formatExpandedName ( en ) );
}
private void notPermittedInElementContext ( String[] en, String[] cn, Object xns ) throws SAXException {
assert en != null;
assert cn != null;
String s = "element " + formatExpandedName(en) + " not permitted in current element context " + formatExpandedName(cn);
if ( xns == null ) {
s += ", expected root context";
} else if ( xns instanceof String[][] ) {
int nxn = 0;
s += ", expected one of { ";
for ( String[] xn : (String[][]) xns ) {
if ( nxn++ > 0 ) {
s += ", ";
}
s += formatExpandedName ( xn );
}
s += " }";
} else if ( xns instanceof String[] ) {
s += ", expected " + formatExpandedName ( (String[]) xns );
}
throw new SAXException ( formatLocator() + ": " + s );
}
private void missingRequiredAttribute ( String[] en, String name ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " missing required attribute " + name );
}
private void duplicateGlyph ( String[] en, String name, int gid ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate name \"" + name + "\", with identifier value " + gid );
}
private void unsupportedGlyph ( String[] en, String name ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " refers to unsupported glyph id \"" + name + "\"" );
}
private void duplicateCMAPCharacter ( String[] en, int cid ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate cmap character code: " + CharUtilities.format ( cid ) );
}
private void duplicateCMAPGlyph ( String[] en, int gid ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate cmap glyph code: " + gid );
}
private void duplicateGlyphClass ( String[] en, String name, String glyphClass ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " contains duplicate glyph class for \"" + name + "\", with class value " + glyphClass );
}
private void unsupportedFormat ( String[] en, int format ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " refers to unsupported table format \"" + format + "\"" );
}
private void invalidIndex ( String[] en, int actual, int expected ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " specifies invalid index " + actual + ", expected " + expected );
}
private void mismatchedIndex ( String[] en, String label, int actual, int expected ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " mismatched " + label + " index: got " + actual + ", expected " + expected );
}
private void mismatchedEntries ( String[] en, int nce, int nse ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " mismatched coverage and subtable entry counts, # coverages " + nce + ", # entries " + nse );
}
private void missingParameter ( String[] en, String label ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " missing " + label + " parameter" );
}
private void duplicateParameter ( String[] en, String label ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " duplicate " + label + " parameter" );
}
private void duplicateCoverageIndex ( String[] en, int index ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " duplicate coverage table index " + index );
}
private void missingCoverage ( String[] en, String type, int expected ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " missing " + type + " coverage table, expected " + ( ( expected > 0 ) ? expected : 1 ) + " table(s)" );
}
private void missingTag ( String[] en, String label ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " missing " + label + " tag" );
}
private void duplicateTag ( String[] en, String label, String tag ) throws SAXException {
throw new SAXException ( formatLocator() + ": element " + formatExpandedName(en) + " duplicate " + label + " tag: " + tag );
}
private static String[] makeExpandedName ( String uri, String localName, String qName ) {
if ( ( uri != null ) && ( uri.length() == 0 ) ) {
uri = null;
}
if ( ( localName != null ) && ( localName.length() == 0 ) ) {
localName = null;
}
if ( ( uri == null ) && ( localName == null ) ) {
uri = extractPrefix ( qName );
localName = extractLocalName ( qName );
}
return new String[] { uri, localName };
}
private static String extractPrefix ( String qName ) {
String[] sa = qName.split(":");
if ( sa.length == 2 ) {
return sa[0];
} else {
return null;
}
}
private static String extractLocalName ( String qName ) {
String[] sa = qName.split(":");
if ( sa.length == 2 ) {
return sa[1];
} else if ( sa.length == 1 ) {
return sa[0];
} else {
return null;
}
}
private static boolean sameExpandedName ( String[] n1, String[] n2 ) {
String u1 = n1[0];
String u2 = n2[0];
if ( ( u1 == null ) ^ ( u2 == null ) ) {
return false;
}
if ( ( u1 != null ) && ( u2 != null ) ) {
if ( ! u1.equals ( u2 ) ) {
return false;
}
}
String l1 = n1[1];
String l2 = n2[1];
if ( ( l1 == null ) ^ ( l2 == null ) ) {
return false;
}
if ( ( l1 != null ) && ( l2 != null ) ) {
if ( ! l1.equals ( l2 ) ) {
return false;
}
}
return true;
}
private static String formatExpandedName ( String[] n ) {
String u = ( n[0] != null ) ? n[0] : "null";
String l = ( n[1] != null ) ? n[1] : "null";
return "{" + u + "}" + l;
}
}