org.apache.fop.complexscripts.fonts.GlyphTable Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* $Id$ */
package org.apache.fop.complexscripts.fonts;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.fop.complexscripts.scripts.ScriptProcessor;
import org.apache.fop.complexscripts.util.GlyphSequence;
import org.apache.fop.complexscripts.util.ScriptContextTester;
// CSOFF: LineLengthCheck
/**
* Base class for all advanced typographic glyph tables.
*
* This work was originally authored by Glenn Adams ([email protected]).
*/
public class GlyphTable {
/** logging instance */
private static final Log log = LogFactory.getLog(GlyphTable.class);
/** substitution glyph table type */
public static final int GLYPH_TABLE_TYPE_SUBSTITUTION = 1;
/** positioning glyph table type */
public static final int GLYPH_TABLE_TYPE_POSITIONING = 2;
/** justification glyph table type */
public static final int GLYPH_TABLE_TYPE_JUSTIFICATION = 3;
/** baseline glyph table type */
public static final int GLYPH_TABLE_TYPE_BASELINE = 4;
/** definition glyph table type */
public static final int GLYPH_TABLE_TYPE_DEFINITION = 5;
// (optional) glyph definition table in table types other than glyph definition table
private GlyphTable gdef;
// map from lookup specs to lists of strings, each of which identifies a lookup table (consisting of one or more subtables)
private Map> lookups;
// map from lookup identifiers to lookup tables
private Map lookupTables;
// cache for lookups matching
private Map>> matchedLookups;
// if true, then prevent further subtable addition
private boolean frozen;
protected Map processors;
/**
* Instantiate glyph table with specified lookups.
* @param gdef glyph definition table that applies
* @param lookups map from lookup specs to lookup tables
*/
public GlyphTable(GlyphTable gdef, Map> lookups,
Map processors) {
this.processors = processors;
if ((gdef != null) && !(gdef instanceof GlyphDefinitionTable)) {
throw new AdvancedTypographicTableFormatException("bad glyph definition table");
} else if (lookups == null) {
throw new AdvancedTypographicTableFormatException("lookups must be non-null map");
} else {
this.gdef = gdef;
this.lookups = lookups;
this.lookupTables = new LinkedHashMap();
this.matchedLookups = new HashMap>>();
}
}
/**
* Obtain glyph definition table.
* @return (possibly null) glyph definition table
*/
public GlyphDefinitionTable getGlyphDefinitions() {
return (GlyphDefinitionTable) gdef;
}
/**
* Obtain list of all lookup specifications.
* @return (possibly empty) list of all lookup specifications
*/
public List getLookups() {
return matchLookupSpecs("*", "*", "*");
}
/**
* Obtain ordered list of all lookup tables, where order is by lookup identifier, which
* lexicographic ordering follows the lookup list order.
* @return (possibly empty) ordered list of all lookup tables
*/
public List getLookupTables() {
TreeSet lids = new TreeSet(lookupTables.keySet());
List ltl = new ArrayList(lids.size());
for (Object lid1 : lids) {
String lid = (String) lid1;
ltl.add(lookupTables.get(lid));
}
return ltl;
}
/**
* Obtain lookup table by lookup id. This method is used by test code, and provides
* access to embedded lookups not normally accessed by {script, language, feature} lookup spec.
* @param lid lookup id
* @return table associated with lookup id or null if none
*/
public LookupTable getLookupTable(String lid) {
return lookupTables.get(lid);
}
/**
* Add a subtable.
* @param subtable a (non-null) glyph subtable
*/
protected void addSubtable(GlyphSubtable subtable) {
// ensure table is not frozen
if (frozen) {
throw new IllegalStateException("glyph table is frozen, subtable addition prohibited");
}
// set subtable's table reference to this table
subtable.setTable(this);
// add subtable to this table's subtable collection
String lid = subtable.getLookupId();
if (lookupTables.containsKey(lid)) {
LookupTable lt = lookupTables.get(lid);
lt.addSubtable(subtable);
} else {
LookupTable lt = new LookupTable(lid, subtable);
lookupTables.put(lid, lt);
}
}
/**
* Freeze subtables, i.e., do not allow further subtable addition, and
* create resulting cached state.
*/
protected void freezeSubtables() {
if (!frozen) {
for (Object o : lookupTables.values()) {
LookupTable lt = (LookupTable) o;
lt.freezeSubtables(lookupTables);
}
frozen = true;
}
}
/**
* Match lookup specifications according to <script,language,feature> tuple, where
* '*' is a wildcard for a tuple component.
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
* @return a (possibly empty) array of matching lookup specifications
*/
public List matchLookupSpecs(String script, String language, String feature) {
Set keys = lookups.keySet();
List matches = new ArrayList();
for (Object key : keys) {
LookupSpec ls = (LookupSpec) key;
if (!"*".equals(script)) {
if (!ls.getScript().equals(script)) {
continue;
}
}
if (!"*".equals(language)) {
if (!ls.getLanguage().equals(language)) {
continue;
}
}
if (!"*".equals(feature)) {
if (!ls.getFeature().equals(feature)) {
continue;
}
}
matches.add(ls);
}
return matches;
}
/**
* Match lookup specifications according to <script,language,feature> tuple, where
* '*' is a wildcard for a tuple component.
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
* @return a (possibly empty) map from matching lookup specifications to lists of corresponding lookup tables
*/
public Map> matchLookups(String script, String language, String feature) {
LookupSpec lsm = new LookupSpec(script, language, feature, true, true);
Map> lm = matchedLookups.get(lsm);
if (lm == null) {
lm = new LinkedHashMap();
List lsl = matchLookupSpecs(script, language, feature);
for (Object aLsl : lsl) {
LookupSpec ls = (LookupSpec) aLsl;
lm.put(ls, findLookupTables(ls));
}
matchedLookups.put(lsm, lm);
}
if (lm.isEmpty() && !OTFScript.isDefault(script) && !OTFScript.isWildCard(script)) {
return matchLookups(OTFScript.DEFAULT, OTFLanguage.DEFAULT, feature);
} else {
return lm;
}
}
/**
* Obtain ordered list of glyph lookup tables that match a specific lookup specification.
* @param ls a (non-null) lookup specification
* @return a (possibly empty) ordered list of lookup tables whose corresponding lookup specifications match the specified lookup spec
*/
public List findLookupTables(LookupSpec ls) {
TreeSet lts = new TreeSet();
List ids;
if ((ids = lookups.get(ls)) != null) {
for (Object id : ids) {
String lid = (String) id;
LookupTable lt;
if ((lt = lookupTables.get(lid)) != null) {
lts.add(lt);
}
}
}
return new ArrayList(lts);
}
/**
* Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
* where the order of the array is in accordance to the order of the applicable lookup list.
* @param features array of feature identifiers to apply
* @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
* @return ordered array of assembled lookup table use specifications
*/
public UseSpec[] assembleLookups(String[] features, Map> lookups) {
TreeSet uss = new TreeSet();
for (String feature : features) {
for (Object o : lookups.entrySet()) {
Map.Entry> e = (Map.Entry>) o;
LookupSpec ls = e.getKey();
if (ls.getFeature().equals(feature)) {
List ltl = e.getValue();
if (ltl != null) {
for (Object aLtl : ltl) {
LookupTable lt = (LookupTable) aLtl;
uss.add(new UseSpec(lt, feature));
}
}
}
}
}
return uss.toArray(new UseSpec [ uss.size() ]);
}
/**
* Determine if table supports specific feature, i.e., supports at least one lookup.
*
* @param script to qualify feature lookup
* @param language to qualify feature lookup
* @param feature to test
* @return true if feature supported (has at least one lookup)
*/
public boolean hasFeature(String script, String language, String feature) {
UseSpec[] usa = assembleLookups(new String[] { feature }, matchLookups(script, language, feature));
return usa.length > 0;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer(super.toString());
sb.append("{");
sb.append("lookups={");
sb.append(lookups.toString());
sb.append("},lookupTables={");
sb.append(lookupTables.toString());
sb.append("}}");
return sb.toString();
}
/**
* Obtain glyph table type from name.
* @param name of table type to map to type value
* @return glyph table type (as an integer constant)
*/
public static int getTableTypeFromName(String name) {
int t;
String s = name.toLowerCase();
if ("gsub".equals(s)) {
t = GLYPH_TABLE_TYPE_SUBSTITUTION;
} else if ("gpos".equals(s)) {
t = GLYPH_TABLE_TYPE_POSITIONING;
} else if ("jstf".equals(s)) {
t = GLYPH_TABLE_TYPE_JUSTIFICATION;
} else if ("base".equals(s)) {
t = GLYPH_TABLE_TYPE_BASELINE;
} else if ("gdef".equals(s)) {
t = GLYPH_TABLE_TYPE_DEFINITION;
} else {
t = -1;
}
return t;
}
/**
* Resolve references to lookup tables in a collection of rules sets.
* @param rsa array of rule sets
* @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
*/
public static void resolveLookupReferences(RuleSet[] rsa, Map lookupTables) {
if ((rsa != null) && (lookupTables != null)) {
for (RuleSet rs : rsa) {
if (rs != null) {
rs.resolveLookupReferences(lookupTables);
}
}
}
}
/**
* A structure class encapsulating a lookup specification as a <script,language,feature> tuple.
*/
public static class LookupSpec implements Comparable {
private final String script;
private final String language;
private final String feature;
/**
* Instantiate lookup spec.
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
*/
public LookupSpec(String script, String language, String feature) {
this (script, language, feature, false, false);
}
/**
* Instantiate lookup spec.
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
* @param permitEmpty if true then permit empty script, language, or feature
* @param permitWildcard if true then permit wildcard script, language, or feature
*/
LookupSpec(String script, String language, String feature, boolean permitEmpty, boolean permitWildcard) {
if ((script == null) || (!permitEmpty && (script.length() == 0))) {
throw new AdvancedTypographicTableFormatException("script must be non-empty string");
} else if ((language == null) || (!permitEmpty && (language.length() == 0))) {
throw new AdvancedTypographicTableFormatException("language must be non-empty string");
} else if ((feature == null) || (!permitEmpty && (feature.length() == 0))) {
throw new AdvancedTypographicTableFormatException("feature must be non-empty string");
} else if (!permitWildcard && script.equals("*")) {
throw new AdvancedTypographicTableFormatException("script must not be wildcard");
} else if (!permitWildcard && language.equals("*")) {
throw new AdvancedTypographicTableFormatException("language must not be wildcard");
} else if (!permitWildcard && feature.equals("*")) {
throw new AdvancedTypographicTableFormatException("feature must not be wildcard");
}
this.script = script.trim();
this.language = language.trim();
this.feature = feature.trim();
}
/** @return script identifier */
public String getScript() {
return script;
}
/** @return language identifier */
public String getLanguage() {
return language;
}
/** @return feature identifier */
public String getFeature() {
return feature;
}
/** {@inheritDoc} */
public int hashCode() {
int hc = 0;
hc = 7 * hc + (hc ^ script.hashCode());
hc = 11 * hc + (hc ^ language.hashCode());
hc = 17 * hc + (hc ^ feature.hashCode());
return hc;
}
/** {@inheritDoc} */
public boolean equals(Object o) {
if (o instanceof LookupSpec) {
LookupSpec l = (LookupSpec) o;
if (!l.script.equals(script)) {
return false;
} else if (!l.language.equals(language)) {
return false;
} else {
return l.feature.equals(feature);
}
} else {
return false;
}
}
/** {@inheritDoc} */
public int compareTo(Object o) {
int d;
if (o instanceof LookupSpec) {
LookupSpec ls = (LookupSpec) o;
if ((d = script.compareTo(ls.script)) == 0) {
if ((d = language.compareTo(ls.language)) == 0) {
if ((d = feature.compareTo(ls.feature)) == 0) {
d = 0;
}
}
}
} else {
d = -1;
}
return d;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer(super.toString());
sb.append("{");
sb.append("<'" + script + "'");
sb.append(",'" + language + "'");
sb.append(",'" + feature + "'");
sb.append(">}");
return sb.toString();
}
}
/**
* The LookupTable
class comprising an identifier and an ordered list
* of glyph subtables, each of which employ the same lookup identifier.
*/
public static class LookupTable implements Comparable {
private final String id; // lookup identifier
private final int idOrdinal; // parsed lookup identifier ordinal
private final List subtables; // list of subtables
private boolean doesSub; // performs substitutions
private boolean doesPos; // performs positioning
private boolean frozen; // if true, then don't permit further subtable additions
// frozen state
private GlyphSubtable[] subtablesArray;
private static GlyphSubtable[] subtablesArrayEmpty = new GlyphSubtable[0];
/**
* Instantiate a LookupTable.
* @param id the lookup table's identifier
* @param subtable an initial subtable (or null)
*/
public LookupTable(String id, GlyphSubtable subtable) {
this (id, makeSingleton(subtable));
}
/**
* Instantiate a LookupTable.
* @param id the lookup table's identifier
* @param subtables a pre-poplated list of subtables or null
*/
public LookupTable(String id, List subtables) {
assert id != null;
assert id.length() != 0;
assert id.startsWith("lu");
this.id = id;
this.idOrdinal = Integer.parseInt(id.substring(2));
this.subtables = new LinkedList();
if (subtables != null) {
for (Object subtable : subtables) {
GlyphSubtable st = (GlyphSubtable) subtable;
addSubtable(st);
}
}
}
/** @return the subtables as an array */
public GlyphSubtable[] getSubtables() {
if (frozen) {
return (subtablesArray != null) ? subtablesArray : subtablesArrayEmpty;
} else {
if (doesSub) {
return subtables.toArray(new GlyphSubstitutionSubtable [ subtables.size() ]);
} else if (doesPos) {
return subtables.toArray(new GlyphPositioningSubtable [ subtables.size() ]);
} else {
return null;
}
}
}
/**
* Add a subtable into this lookup table's collecion of subtables according to its
* natural order.
* @param subtable to add
* @return true if subtable was not already present, otherwise false
*/
public boolean addSubtable(GlyphSubtable subtable) {
boolean added = false;
// ensure table is not frozen
if (frozen) {
throw new IllegalStateException("glyph table is frozen, subtable addition prohibited");
}
// validate subtable to ensure consistency with current subtables
validateSubtable(subtable);
// insert subtable into ordered list
for (ListIterator lit = subtables.listIterator(0); lit.hasNext(); ) {
GlyphSubtable st = lit.next();
int d;
if ((d = subtable.compareTo(st)) < 0) {
// insert within list
lit.set(subtable);
lit.add(st);
added = true;
} else if (d == 0) {
// duplicate entry is ignored
added = false;
subtable = null;
}
}
// append at end of list
if (!added && (subtable != null)) {
subtables.add(subtable);
added = true;
}
return added;
}
private void validateSubtable(GlyphSubtable subtable) {
if (subtable == null) {
throw new AdvancedTypographicTableFormatException("subtable must be non-null");
}
if (subtable instanceof GlyphSubstitutionSubtable) {
if (doesPos) {
throw new AdvancedTypographicTableFormatException("subtable must be positioning subtable, but is: " + subtable);
} else {
doesSub = true;
}
}
if (subtable instanceof GlyphPositioningSubtable) {
if (doesSub) {
throw new AdvancedTypographicTableFormatException("subtable must be substitution subtable, but is: " + subtable);
} else {
doesPos = true;
}
}
if (subtables.size() > 0) {
GlyphSubtable st = subtables.get(0);
if (!st.isCompatible(subtable)) {
throw new AdvancedTypographicTableFormatException("subtable " + subtable + " is not compatible with subtable " + st);
}
}
}
/**
* Freeze subtables, i.e., do not allow further subtable addition, and
* create resulting cached state. In addition, resolve any references to
* lookup tables that appear in this lookup table's subtables.
* @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
*/
public void freezeSubtables(Map lookupTables) {
if (!frozen) {
GlyphSubtable[] sta = getSubtables();
resolveLookupReferences(sta, lookupTables);
this.subtablesArray = sta;
this.frozen = true;
}
}
private void resolveLookupReferences(GlyphSubtable[] subtables, Map lookupTables) {
if (subtables != null) {
for (GlyphSubtable st : subtables) {
if (st != null) {
st.resolveLookupReferences(lookupTables);
}
}
}
}
/**
* Determine if this glyph table performs substitution.
* @return true if it performs substitution
*/
public boolean performsSubstitution() {
return doesSub;
}
/**
* Perform substitution processing using this lookup table's subtables.
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
* @param sct a script specific context tester (or null)
* @return the substituted (output) glyph sequence
*/
public GlyphSequence substitute(GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct) {
if (performsSubstitution()) {
return GlyphSubstitutionSubtable.substitute(gs, script, language, feature, (GlyphSubstitutionSubtable[]) subtablesArray, sct);
} else {
return gs;
}
}
/**
* Perform substitution processing on an existing glyph substitution state object using this lookup table's subtables.
* @param ss a glyph substitution state object
* @param sequenceIndex if non negative, then apply subtables only at specified sequence index
* @return the substituted (output) glyph sequence
*/
public GlyphSequence substitute(GlyphSubstitutionState ss, int sequenceIndex) {
if (performsSubstitution()) {
return GlyphSubstitutionSubtable.substitute(ss, (GlyphSubstitutionSubtable[]) subtablesArray, sequenceIndex);
} else {
return ss.getInput();
}
}
/**
* Determine if this glyph table performs positioning.
* @return true if it performs positioning
*/
public boolean performsPositioning() {
return doesPos;
}
/**
* Perform positioning processing using this lookup table's subtables.
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
* @param fontSize size in device units
* @param widths array of default advancements for each glyph in font
* @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
* with one 4-tuple for each element of glyph sequence
* @param sct a script specific context tester (or null)
* @return true if some adjustment is not zero; otherwise, false
*/
public boolean position(GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct) {
if (performsPositioning()) {
return GlyphPositioningSubtable.position(gs, script, language, feature, fontSize, (GlyphPositioningSubtable[]) subtablesArray, widths, adjustments, sct);
} else {
return false;
}
}
/**
* Perform positioning processing on an existing glyph positioning state object using this lookup table's subtables.
* @param ps a glyph positioning state object
* @param sequenceIndex if non negative, then apply subtables only at specified sequence index
* @return true if some adjustment is not zero; otherwise, false
*/
public boolean position(GlyphPositioningState ps, int sequenceIndex) {
if (performsPositioning()) {
return GlyphPositioningSubtable.position(ps, (GlyphPositioningSubtable[]) subtablesArray, sequenceIndex);
} else {
return false;
}
}
/** {@inheritDoc} */
public int hashCode() {
return idOrdinal;
}
/**
* {@inheritDoc}
* @return true if identifier of the specified lookup table is the same
* as the identifier of this lookup table
*/
public boolean equals(Object o) {
if (o instanceof LookupTable) {
LookupTable lt = (LookupTable) o;
return idOrdinal == lt.idOrdinal;
} else {
return false;
}
}
/**
* {@inheritDoc}
* @return the result of comparing the identifier of the specified lookup table with
* the identifier of this lookup table; lookup table identifiers take the form
* "lu(DIGIT)+", with comparison based on numerical ordering of numbers expressed by
* (DIGIT)+.
*/
public int compareTo(Object o) {
if (o instanceof LookupTable) {
LookupTable lt = (LookupTable) o;
int i = idOrdinal;
int j = lt.idOrdinal;
if (i < j) {
return -1;
} else if (i > j) {
return 1;
} else {
return 0;
}
} else {
return -1;
}
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("id = " + id);
sb.append(", subtables = " + subtables);
sb.append(" }");
return sb.toString();
}
private static List makeSingleton(GlyphSubtable subtable) {
if (subtable == null) {
return null;
} else {
List stl = new ArrayList(1);
stl.add(subtable);
return stl;
}
}
}
/**
* The UseSpec
class comprises a lookup table reference
* and the feature that selected the lookup table.
*/
public static class UseSpec implements Comparable {
/** lookup table to apply */
private final LookupTable lookupTable;
/** feature that caused selection of the lookup table */
private final String feature;
/**
* Construct a glyph lookup table use specification.
* @param lookupTable a glyph lookup table
* @param feature a feature that caused lookup table selection
*/
public UseSpec(LookupTable lookupTable, String feature) {
this.lookupTable = lookupTable;
this.feature = feature;
}
/** @return the lookup table */
public LookupTable getLookupTable() {
return lookupTable;
}
/** @return the feature that selected this lookup table */
public String getFeature() {
return feature;
}
/**
* Perform substitution processing using this use specification's lookup table.
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
* @param sct a script specific context tester (or null)
* @return the substituted (output) glyph sequence
*/
public GlyphSequence substitute(GlyphSequence gs, String script, String language, ScriptContextTester sct) {
return lookupTable.substitute(gs, script, language, feature, sct);
}
/**
* Perform positioning processing using this use specification's lookup table.
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
* @param fontSize size in device units
* @param widths array of default advancements for each glyph in font
* @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
* with one 4-tuple for each element of glyph sequence
* @param sct a script specific context tester (or null)
* @return true if some adjustment is not zero; otherwise, false
*/
public boolean position(GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct) {
return lookupTable.position(gs, script, language, feature, fontSize, widths, adjustments, sct);
}
/** {@inheritDoc} */
public int hashCode() {
return lookupTable.hashCode();
}
/** {@inheritDoc} */
public boolean equals(Object o) {
if (o instanceof UseSpec) {
UseSpec u = (UseSpec) o;
return lookupTable.equals(u.lookupTable);
} else {
return false;
}
}
/** {@inheritDoc} */
public int compareTo(Object o) {
if (o instanceof UseSpec) {
UseSpec u = (UseSpec) o;
return lookupTable.compareTo(u.lookupTable);
} else {
return -1;
}
}
}
/**
* The RuleLookup
class implements a rule lookup record, comprising
* a glyph sequence index and a lookup table index (in an applicable lookup list).
*/
public static class RuleLookup {
private final int sequenceIndex; // index into input glyph sequence
private final int lookupIndex; // lookup list index
private LookupTable lookup; // resolved lookup table
/**
* Instantiate a RuleLookup.
* @param sequenceIndex the index into the input sequence
* @param lookupIndex the lookup table index
*/
public RuleLookup(int sequenceIndex, int lookupIndex) {
this.sequenceIndex = sequenceIndex;
this.lookupIndex = lookupIndex;
this.lookup = null;
}
/** @return the sequence index */
public int getSequenceIndex() {
return sequenceIndex;
}
/** @return the lookup index */
public int getLookupIndex() {
return lookupIndex;
}
/** @return the lookup table */
public LookupTable getLookup() {
return lookup;
}
/**
* Resolve references to lookup tables.
* @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
*/
public void resolveLookupReferences(Map lookupTables) {
if (lookupTables != null) {
String lid = "lu" + Integer.toString(lookupIndex);
LookupTable lt = lookupTables.get(lid);
if (lt != null) {
this.lookup = lt;
} else {
log.warn("unable to resolve glyph lookup table reference '" + lid + "' amongst lookup tables: " + lookupTables.values());
}
}
}
/** {@inheritDoc} */
public String toString() {
return "{ sequenceIndex = " + sequenceIndex + ", lookupIndex = " + lookupIndex + " }";
}
}
/**
* The Rule
class implements an array of rule lookup records.
*/
public abstract static class Rule {
private final RuleLookup[] lookups; // rule lookups
private final int inputSequenceLength; // input sequence length
/**
* Instantiate a Rule.
* @param lookups the rule's lookups
* @param inputSequenceLength the number of glyphs in the input sequence for this rule
*/
protected Rule(RuleLookup[] lookups, int inputSequenceLength) {
assert lookups != null;
this.lookups = lookups;
this.inputSequenceLength = inputSequenceLength;
}
/** @return the lookups */
public RuleLookup[] getLookups() {
return lookups;
}
/** @return the input sequence length */
public int getInputSequenceLength() {
return inputSequenceLength;
}
/**
* Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
* @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
*/
public void resolveLookupReferences(Map lookupTables) {
if (lookups != null) {
for (RuleLookup l : lookups) {
if (l != null) {
l.resolveLookupReferences(lookupTables);
}
}
}
}
/** {@inheritDoc} */
public String toString() {
return "{ lookups = " + Arrays.toString(lookups) + ", inputSequenceLength = " + inputSequenceLength + " }";
}
}
/**
* The GlyphSequenceRule
class implements a subclass of Rule
* that supports matching on a specific glyph sequence.
*/
public static class GlyphSequenceRule extends Rule {
private final int[] glyphs; // glyphs
/**
* Instantiate a GlyphSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param glyphs the rule's glyph sequence to match, starting with second glyph in sequence
*/
public GlyphSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] glyphs) {
super(lookups, inputSequenceLength);
assert glyphs != null;
this.glyphs = glyphs;
}
/**
* Obtain glyphs. N.B. that this array starts with the second
* glyph of the input sequence.
* @return the glyphs
*/
public int[] getGlyphs() {
return glyphs;
}
/**
* Obtain glyphs augmented by specified first glyph entry.
* @param firstGlyph to fill in first glyph entry
* @return the glyphs augmented by first glyph
*/
public int[] getGlyphs(int firstGlyph) {
int[] ga = new int [ glyphs.length + 1 ];
ga [ 0 ] = firstGlyph;
System.arraycopy(glyphs, 0, ga, 1, glyphs.length);
return ga;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", glyphs = " + Arrays.toString(glyphs));
sb.append(" }");
return sb.toString();
}
}
/**
* The ClassSequenceRule
class implements a subclass of Rule
* that supports matching on a specific glyph class sequence.
*/
public static class ClassSequenceRule extends Rule {
private final int[] classes; // glyph classes
/**
* Instantiate a ClassSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param classes the rule's glyph class sequence to match, starting with second glyph in sequence
*/
public ClassSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] classes) {
super(lookups, inputSequenceLength);
assert classes != null;
this.classes = classes;
}
/**
* Obtain glyph classes. N.B. that this array starts with the class of the second
* glyph of the input sequence.
* @return the classes
*/
public int[] getClasses() {
return classes;
}
/**
* Obtain glyph classes augmented by specified first class entry.
* @param firstClass to fill in first class entry
* @return the classes augmented by first class
*/
public int[] getClasses(int firstClass) {
int[] ca = new int [ classes.length + 1 ];
ca [ 0 ] = firstClass;
System.arraycopy(classes, 0, ca, 1, classes.length);
return ca;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", classes = " + Arrays.toString(classes));
sb.append(" }");
return sb.toString();
}
}
/**
* The CoverageSequenceRule
class implements a subclass of Rule
* that supports matching on a specific glyph coverage sequence.
*/
public static class CoverageSequenceRule extends Rule {
private final GlyphCoverageTable[] coverages; // glyph coverages
/**
* Instantiate a ClassSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param coverages the rule's glyph coverage sequence to match, starting with first glyph in sequence
*/
public CoverageSequenceRule(RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages) {
super(lookups, inputSequenceLength);
assert coverages != null;
this.coverages = coverages;
}
/** @return the coverages */
public GlyphCoverageTable[] getCoverages() {
return coverages;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", coverages = " + Arrays.toString(coverages));
sb.append(" }");
return sb.toString();
}
}
/**
* The ChainedGlyphSequenceRule
class implements a subclass of GlyphSequenceRule
* that supports matching on a specific glyph sequence in a specific chained contextual.
*/
public static class ChainedGlyphSequenceRule extends GlyphSequenceRule {
private final int[] backtrackGlyphs; // backtrack glyphs
private final int[] lookaheadGlyphs; // lookahead glyphs
/**
* Instantiate a ChainedGlyphSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param glyphs the rule's input glyph sequence to match, starting with second glyph in sequence
* @param backtrackGlyphs the rule's backtrack glyph sequence to match, starting with first glyph in sequence
* @param lookaheadGlyphs the rule's lookahead glyph sequence to match, starting with first glyph in sequence
*/
public ChainedGlyphSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] glyphs, int[] backtrackGlyphs, int[] lookaheadGlyphs) {
super(lookups, inputSequenceLength, glyphs);
assert backtrackGlyphs != null;
assert lookaheadGlyphs != null;
this.backtrackGlyphs = backtrackGlyphs;
this.lookaheadGlyphs = lookaheadGlyphs;
}
/** @return the backtrack glyphs */
public int[] getBacktrackGlyphs() {
return backtrackGlyphs;
}
/** @return the lookahead glyphs */
public int[] getLookaheadGlyphs() {
return lookaheadGlyphs;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", glyphs = " + Arrays.toString(getGlyphs()));
sb.append(", backtrackGlyphs = " + Arrays.toString(backtrackGlyphs));
sb.append(", lookaheadGlyphs = " + Arrays.toString(lookaheadGlyphs));
sb.append(" }");
return sb.toString();
}
}
/**
* The ChainedClassSequenceRule
class implements a subclass of ClassSequenceRule
* that supports matching on a specific glyph class sequence in a specific chained contextual.
*/
public static class ChainedClassSequenceRule extends ClassSequenceRule {
private final int[] backtrackClasses; // backtrack classes
private final int[] lookaheadClasses; // lookahead classes
/**
* Instantiate a ChainedClassSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param classes the rule's input glyph class sequence to match, starting with second glyph in sequence
* @param backtrackClasses the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
* @param lookaheadClasses the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
*/
public ChainedClassSequenceRule(RuleLookup[] lookups, int inputSequenceLength, int[] classes, int[] backtrackClasses, int[] lookaheadClasses) {
super(lookups, inputSequenceLength, classes);
assert backtrackClasses != null;
assert lookaheadClasses != null;
this.backtrackClasses = backtrackClasses;
this.lookaheadClasses = lookaheadClasses;
}
/** @return the backtrack classes */
public int[] getBacktrackClasses() {
return backtrackClasses;
}
/** @return the lookahead classes */
public int[] getLookaheadClasses() {
return lookaheadClasses;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", classes = " + Arrays.toString(getClasses()));
sb.append(", backtrackClasses = " + Arrays.toString(backtrackClasses));
sb.append(", lookaheadClasses = " + Arrays.toString(lookaheadClasses));
sb.append(" }");
return sb.toString();
}
}
/**
* The ChainedCoverageSequenceRule
class implements a subclass of CoverageSequenceRule
* that supports matching on a specific glyph class sequence in a specific chained contextual.
*/
public static class ChainedCoverageSequenceRule extends CoverageSequenceRule {
private final GlyphCoverageTable[] backtrackCoverages; // backtrack coverages
private final GlyphCoverageTable[] lookaheadCoverages; // lookahead coverages
/**
* Instantiate a ChainedCoverageSequenceRule.
* @param lookups the rule's lookups
* @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
* @param coverages the rule's input glyph class sequence to match, starting with first glyph in sequence
* @param backtrackCoverages the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
* @param lookaheadCoverages the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
*/
public ChainedCoverageSequenceRule(RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages, GlyphCoverageTable[] backtrackCoverages, GlyphCoverageTable[] lookaheadCoverages) {
super(lookups, inputSequenceLength, coverages);
assert backtrackCoverages != null;
assert lookaheadCoverages != null;
this.backtrackCoverages = backtrackCoverages;
this.lookaheadCoverages = lookaheadCoverages;
}
/** @return the backtrack coverages */
public GlyphCoverageTable[] getBacktrackCoverages() {
return backtrackCoverages;
}
/** @return the lookahead coverages */
public GlyphCoverageTable[] getLookaheadCoverages() {
return lookaheadCoverages;
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("{ ");
sb.append("lookups = " + Arrays.toString(getLookups()));
sb.append(", coverages = " + Arrays.toString(getCoverages()));
sb.append(", backtrackCoverages = " + Arrays.toString(backtrackCoverages));
sb.append(", lookaheadCoverages = " + Arrays.toString(lookaheadCoverages));
sb.append(" }");
return sb.toString();
}
}
/**
* The RuleSet
class implements a collection of rules, which
* may or may not be the same rule type.
*/
public static class RuleSet {
private final Rule[] rules; // set of rules
/**
* Instantiate a Rule Set.
* @param rules the rules
* @throws AdvancedTypographicTableFormatException if rules or some element of rules is null
*/
public RuleSet(Rule[] rules) throws AdvancedTypographicTableFormatException {
// enforce rules array instance
if (rules == null) {
throw new AdvancedTypographicTableFormatException("rules[] is null");
}
this.rules = rules;
}
/** @return the rules */
public Rule[] getRules() {
return rules;
}
/**
* Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
* @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
*/
public void resolveLookupReferences(Map lookupTables) {
if (rules != null) {
for (Rule r : rules) {
if (r != null) {
r.resolveLookupReferences(lookupTables);
}
}
}
}
/** {@inheritDoc} */
public String toString() {
return "{ rules = " + Arrays.toString(rules) + " }";
}
}
/**
* The HomogenousRuleSet
class implements a collection of rules, which
* must be the same rule type (i.e., same concrete rule class) or null.
*/
public static class HomogeneousRuleSet extends RuleSet {
/**
* Instantiate a Homogeneous Rule Set.
* @param rules the rules
* @throws AdvancedTypographicTableFormatException if some rule[i] is not an instance of rule[0]
*/
public HomogeneousRuleSet(Rule[] rules) throws AdvancedTypographicTableFormatException {
super(rules);
// find first non-null rule
Rule r0 = null;
for (int i = 1, n = rules.length; (r0 == null) && (i < n); i++) {
if (rules[i] != null) {
r0 = rules[i];
}
}
// enforce rule instance homogeneity
if (r0 != null) {
Class c = r0.getClass();
for (int i = 1, n = rules.length; i < n; i++) {
Rule r = rules[i];
if ((r != null) && !c.isInstance(r)) {
throw new AdvancedTypographicTableFormatException("rules[" + i + "] is not an instance of " + c.getName());
}
}
}
}
}
}