Base class for all advanced typographic glyph tables.
+ * + *Adapted from the Apache FOP Project.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public class AdvancedTypographicTable extends TTFTable { + + /** logging instance */ + private static final Log log = LogFactory.getLog(AdvancedTypographicTable.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 AdvancedTypographicTable gdef; + + // map from lookup specs to lists of strings, each of which identifies a lookup table (consisting of one or more subtables) + private Map/*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/*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/*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/*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/*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());
+ }
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableFormatException.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableFormatException.java
new file mode 100644
index 00000000000..6d7c05ddde4
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableFormatException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+/**
+ * Exception thrown when attempting to decode a truetype font file and a format + * constraint is violated.
+ * + * @author Glenn Adams + */ +public class AdvancedTypographicTableFormatException extends RuntimeException { + /** + * Instantiate ATT format exception. + */ + public AdvancedTypographicTableFormatException() { + super(); + } + /** + * Instantiate ATT format exception. + * @param message a message string + */ + public AdvancedTypographicTableFormatException(String message) { + super(message); + } + /** + * Instantiate ATT format exception. + * @param message a message string + * @param cause aThrowable that caused this exception
+ */
+ public AdvancedTypographicTableFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableReader.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableReader.java
new file mode 100644
index 00000000000..0b89c875e22
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/AdvancedTypographicTableReader.java
@@ -0,0 +1,3729 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.ttf.OpenTypeFont;
+import org.apache.fontbox.ttf.TTFDataStream;
+import org.apache.fontbox.ttf.TTFTable;
+
+/**
+ * OpenType Font (OTF) advanced typographic table reader.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public final class AdvancedTypographicTableReader { + + // logging state + private static Log log = LogFactory.getLog(AdvancedTypographicTableReader.class); + // instance state + private OpenTypeFont otf; // enclosing font instance + private TTFTable table; // table being constructed + private TTFDataStream data; // data stream + // transient parsing state + private transient Map/*AdvancedTypographicTableReader instance.
+ * @param otf enclosing font file (must be non-null)
+ * @param table table instance being constructed, will be one of Glyph{Definition,Substitution,Positioning}Table
+ * @param data font file reader (must be non-null)
+ */
+ public AdvancedTypographicTableReader(OpenTypeFont otf, TTFTable table, TTFDataStream data) {
+ assert otf != null;
+ assert table != null;
+ assert data != null;
+ this.otf = otf;
+ this.table = table;
+ this.data = data;
+ }
+
+ /**
+ * Read advanced typographic table.
+ * @throws AdvancedTypographicTableFormatException if ATT table has invalid format
+ */
+ public void read() throws AdvancedTypographicTableFormatException {
+ try {
+ if (table instanceof GlyphDefinitionTable)
+ readGDEF();
+ else if (table instanceof GlyphSubstitutionTable)
+ readGSUB();
+ else if (table instanceof GlyphPositioningTable)
+ readGPOS();
+ } catch (AdvancedTypographicTableFormatException e) {
+ resetATStateAll();
+ throw e;
+ } catch (IOException e) {
+ resetATStateAll();
+ throw new AdvancedTypographicTableFormatException(e.getMessage(), e);
+ } finally {
+ resetATState();
+ }
+ }
+
+ private void readLangSysTable(String tableTag, long langSysTable, String langSysTag)
+ throws IOException {
+ data.seek(langSysTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table: " + langSysTag);
+ }
+ // read lookup order (reorder) table offset
+ int lo = data.readUnsignedShort();
+ // read required feature index
+ int rf = data.readUnsignedShort();
+ String rfi;
+ if (rf != 65535) {
+ rfi = "f" + rf;
+ } else {
+ rfi = null;
+ }
+ // read (non-required) feature count
+ int nf = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table reorder table: " + lo);
+ log.debug(tableTag + " lang sys table required feature index: " + rf);
+ log.debug(tableTag + " lang sys table non-required feature count: " + nf);
+ }
+ // read (non-required) feature indices
+ int[] fia = new int[nf];
+ List fl = new java.util.ArrayList();
+ for (int i = 0; i < nf; i++) {
+ int fi = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys table non-required feature index: " + fi);
+ }
+ fia[i] = fi;
+ fl.add("f" + fi);
+ }
+ if (seLanguages == null) {
+ seLanguages = new java.util.LinkedHashMap();
+ }
+ seLanguages.put(langSysTag, new Object[] { rfi, fl });
+ }
+
+ private static String defaultTag = "dflt";
+
+ private void readScriptTable(String tableTag, long scriptTable, String scriptTag) throws IOException {
+ data.seek(scriptTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script table: " + scriptTag);
+ }
+ // read default language system table offset
+ int dl = data.readUnsignedShort();
+ String dt = defaultTag;
+ if (dl > 0) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " default lang sys tag: " + dt);
+ log.debug(tableTag + " default lang sys table offset: " + dl);
+ }
+ }
+ // read language system record count
+ int nl = data.readUnsignedShort();
+ List ll = new java.util.ArrayList();
+ if (nl > 0) {
+ String[] lta = new String[nl];
+ int[] loa = new int[nl];
+ // read language system records
+ for (int i = 0, n = nl; i < n; i++) {
+ String lt = data.readTag();
+ int lo = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys tag: " + lt);
+ log.debug(tableTag + " lang sys table offset: " + lo);
+ }
+ lta[i] = lt;
+ loa[i] = lo;
+ if (dl == lo) {
+ dl = 0;
+ dt = lt;
+ }
+ ll.add(lt);
+ }
+ // read non-default language system tables
+ for (int i = 0, n = nl; i < n; i++) {
+ readLangSysTable(tableTag, scriptTable + loa [ i ], lta [ i ]);
+ }
+ }
+ // read default language system table (if specified)
+ if (dl > 0) {
+ readLangSysTable(tableTag, scriptTable + dl, dt);
+ } else if (dt != null) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lang sys default: " + dt);
+ }
+ }
+ seScripts.put(scriptTag, new Object[] { dt, ll, seLanguages });
+ seLanguages = null;
+ }
+
+ private void readScriptList(String tableTag, long scriptList) throws IOException {
+ data.seek(scriptList);
+ // read script record count
+ int ns = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list record count: " + ns);
+ }
+ if (ns > 0) {
+ String[] sta = new String[ns];
+ int[] soa = new int[ns];
+ // read script records
+ for (int i = 0, n = ns; i < n; i++) {
+ String st = data.readTag();
+ int so = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script tag: " + st);
+ log.debug(tableTag + " script table offset: " + so);
+ }
+ sta[i] = st;
+ soa[i] = so;
+ }
+ // read script tables
+ for (int i = 0, n = ns; i < n; i++) {
+ seLanguages = null;
+ readScriptTable(tableTag, scriptList + soa [ i ], sta [ i ]);
+ }
+ }
+ }
+
+ private void readFeatureTable(String tableTag, long featureTable, String featureTag, int featureIndex) throws IOException {
+ data.seek(featureTable);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table: " + featureTag);
+ }
+ // read feature params offset
+ int po = data.readUnsignedShort();
+ // read lookup list indices count
+ int nl = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table parameters offset: " + po);
+ log.debug(tableTag + " feature table lookup list index count: " + nl);
+ }
+ // read lookup table indices
+ int[] lia = new int[nl];
+ List lul = new java.util.ArrayList();
+ for (int i = 0; i < nl; i++) {
+ int li = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature table lookup index: " + li);
+ }
+ lia[i] = li;
+ lul.add("lu" + li);
+ }
+ seFeatures.put("f" + featureIndex, new Object[] { featureTag, lul });
+ }
+
+ private void readFeatureList(String tableTag, long featureList) throws IOException {
+ data.seek(featureList);
+ // read feature record count
+ int nf = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature list record count: " + nf);
+ }
+ if (nf > 0) {
+ String[] fta = new String[nf];
+ int[] foa = new int[nf];
+ // read feature records
+ for (int i = 0, n = nf; i < n; i++) {
+ String ft = data.readTag();
+ int fo = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature tag: " + ft);
+ log.debug(tableTag + " feature table offset: " + fo);
+ }
+ fta[i] = ft;
+ foa[i] = fo;
+ }
+ // read feature tables
+ for (int i = 0, n = nf; i < n; i++) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " feature index: " + i);
+ }
+ readFeatureTable(tableTag, featureList + foa [ i ], fta [ i ], i);
+ }
+ }
+ }
+
+ static final class GDEFLookupType {
+ static final int GLYPH_CLASS = 1;
+ static final int ATTACHMENT_POINT = 2;
+ static final int LIGATURE_CARET = 3;
+ static final int MARK_ATTACHMENT = 4;
+ private GDEFLookupType() {
+ }
+ public static int getSubtableType(int lt) {
+ int st;
+ switch (lt) {
+ case GDEFLookupType.GLYPH_CLASS:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ break;
+ case GDEFLookupType.ATTACHMENT_POINT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ break;
+ case GDEFLookupType.LIGATURE_CARET:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ break;
+ case GDEFLookupType.MARK_ATTACHMENT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ break;
+ default:
+ st = -1;
+ break;
+ }
+ return st;
+ }
+ public static String toString(int type) {
+ String s;
+ switch (type) {
+ case GLYPH_CLASS:
+ s = "GlyphClass";
+ break;
+ case ATTACHMENT_POINT:
+ s = "AttachmentPoint";
+ break;
+ case LIGATURE_CARET:
+ s = "LigatureCaret";
+ break;
+ case MARK_ATTACHMENT:
+ s = "MarkAttachment";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class GSUBLookupType {
+ static final int SINGLE = 1;
+ static final int MULTIPLE = 2;
+ static final int ALTERNATE = 3;
+ static final int LIGATURE = 4;
+ static final int CONTEXTUAL = 5;
+ static final int CHAINED_CONTEXTUAL = 6;
+ static final int EXTENSION = 7;
+ static final int REVERSE_CHAINED_SINGLE = 8;
+ private GSUBLookupType() {
+ }
+ public static int getSubtableType(int lt) {
+ int st;
+ switch (lt) {
+ case GSUBLookupType.SINGLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE;
+ break;
+ case GSUBLookupType.MULTIPLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE;
+ break;
+ case GSUBLookupType.ALTERNATE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE;
+ break;
+ case GSUBLookupType.LIGATURE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE;
+ break;
+ case GSUBLookupType.CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL;
+ break;
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ break;
+ case GSUBLookupType.EXTENSION:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
+ break;
+ case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
+ break;
+ default:
+ st = -1;
+ break;
+ }
+ return st;
+ }
+ public static String toString(int type) {
+ String s;
+ switch (type) {
+ case SINGLE:
+ s = "Single";
+ break;
+ case MULTIPLE:
+ s = "Multiple";
+ break;
+ case ALTERNATE:
+ s = "Alternate";
+ break;
+ case LIGATURE:
+ s = "Ligature";
+ break;
+ case CONTEXTUAL:
+ s = "Contextual";
+ break;
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
+ break;
+ case EXTENSION:
+ s = "Extension";
+ break;
+ case REVERSE_CHAINED_SINGLE:
+ s = "ReverseChainedSingle";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class GPOSLookupType {
+ static final int SINGLE = 1;
+ static final int PAIR = 2;
+ static final int CURSIVE = 3;
+ static final int MARK_TO_BASE = 4;
+ static final int MARK_TO_LIGATURE = 5;
+ static final int MARK_TO_MARK = 6;
+ static final int CONTEXTUAL = 7;
+ static final int CHAINED_CONTEXTUAL = 8;
+ static final int EXTENSION = 9;
+ private GPOSLookupType() {
+ }
+ public static String toString(int type) {
+ String s;
+ switch (type) {
+ case SINGLE:
+ s = "Single";
+ break;
+ case PAIR:
+ s = "Pair";
+ break;
+ case CURSIVE:
+ s = "Cursive";
+ break;
+ case MARK_TO_BASE:
+ s = "MarkToBase";
+ break;
+ case MARK_TO_LIGATURE:
+ s = "MarkToLigature";
+ break;
+ case MARK_TO_MARK:
+ s = "MarkToMark";
+ break;
+ case CONTEXTUAL:
+ s = "Contextual";
+ break;
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
+ break;
+ case EXTENSION:
+ s = "Extension";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
+ static final class LookupFlag {
+ static final int RIGHT_TO_LEFT = 0x0001;
+ static final int IGNORE_BASE_GLYPHS = 0x0002;
+ static final int IGNORE_LIGATURE = 0x0004;
+ static final int IGNORE_MARKS = 0x0008;
+ static final int USE_MARK_FILTERING_SET = 0x0010;
+ static final int MARK_ATTACHMENT_TYPE = 0xFF00;
+ private LookupFlag() {
+ }
+ public static String toString(int flags) {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ if ((flags & RIGHT_TO_LEFT) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append('|');
+ }
+ sb.append("RightToLeft");
+ }
+ if ((flags & IGNORE_BASE_GLYPHS) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append('|');
+ }
+ sb.append("IgnoreBaseGlyphs");
+ }
+ if ((flags & IGNORE_LIGATURE) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append('|');
+ }
+ sb.append("IgnoreLigature");
+ }
+ if ((flags & IGNORE_MARKS) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append('|');
+ }
+ sb.append("IgnoreMarks");
+ }
+ if ((flags & USE_MARK_FILTERING_SET) != 0) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append('|');
+ }
+ sb.append("UseMarkFilteringSet");
+ }
+ if (sb.length() == 0) {
+ sb.append('-');
+ }
+ return sb.toString();
+ }
+ }
+
+ private GlyphCoverageTable readCoverageTableFormat1(String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ data.seek(tableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ int[] ga = new int[ng];
+ for (int i = 0, n = ng; i < n; i++) {
+ int g = data.readUnsignedShort();
+ ga[i] = g;
+ entries.add(Integer.valueOf(g));
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " glyphs: " + toString(ga));
+ }
+ return GlyphCoverageTable.createCoverageTable(entries);
+ }
+
+ private GlyphCoverageTable readCoverageTableFormat2(String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ data.seek(tableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read range record count
+ int nr = data.readUnsignedShort();
+ for (int i = 0, n = nr; i < n; i++) {
+ // read range start
+ int s = data.readUnsignedShort();
+ // read range end
+ int e = data.readUnsignedShort();
+ // read range coverage (mapping) index
+ int m = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m);
+ }
+ entries.add(new GlyphCoverageTable.MappingRange(s, e, m));
+ }
+ return GlyphCoverageTable.createCoverageTable(entries);
+ }
+
+ private GlyphCoverageTable readCoverageTable(String label, long tableOffset) throws IOException {
+ GlyphCoverageTable gct;
+ long cp = data.getCurrentPosition();
+ data.seek(tableOffset);
+ // read coverage table format
+ int cf = data.readUnsignedShort();
+ if (cf == 1) {
+ gct = readCoverageTableFormat1(label, tableOffset, cf);
+ } else if (cf == 2) {
+ gct = readCoverageTableFormat2(label, tableOffset, cf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported coverage table format: " + cf);
+ }
+ data.seek(cp);
+ return gct;
+ }
+
+ private GlyphClassTable readClassDefTableFormat1(String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ data.seek(tableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read start glyph
+ int sg = data.readUnsignedShort();
+ entries.add(Integer.valueOf(sg));
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // read glyph classes
+ int[] ca = new int[ng];
+ for (int i = 0, n = ng; i < n; i++) {
+ int gc = data.readUnsignedShort();
+ ca[i] = gc;
+ entries.add(Integer.valueOf(gc));
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " glyph classes: " + toString(ca));
+ }
+ return GlyphClassTable.createClassTable(entries);
+ }
+
+ private GlyphClassTable readClassDefTableFormat2(String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
+ data.seek(tableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read range record count
+ int nr = data.readUnsignedShort();
+ for (int i = 0, n = nr; i < n; i++) {
+ // read range start
+ int s = data.readUnsignedShort();
+ // read range end
+ int e = data.readUnsignedShort();
+ // read range glyph class (mapping) index
+ int m = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m);
+ }
+ entries.add(new GlyphClassTable.MappingRange(s, e, m));
+ }
+ return GlyphClassTable.createClassTable(entries);
+ }
+
+ private GlyphClassTable readClassDefTable(String label, long tableOffset) throws IOException {
+ GlyphClassTable gct;
+ long cp = data.getCurrentPosition();
+ data.seek(tableOffset);
+ // read class table format
+ int cf = data.readUnsignedShort();
+ if (cf == 1) {
+ gct = readClassDefTableFormat1(label, tableOffset, cf);
+ } else if (cf == 2) {
+ gct = readClassDefTableFormat2(label, tableOffset, cf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported class definition table format: " + cf);
+ }
+ data.seek(cp);
+ return gct;
+ }
+
+ private void readSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read delta glyph
+ int dg = data.readSignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)");
+ log.debug(tableTag + " single substitution coverage table offset: " + co);
+ log.debug(tableTag + " single substitution delta: " + dg);
+ }
+ // read coverage table
+ seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co);
+ seEntries.add(Integer.valueOf(dg));
+ }
+
+ private void readSingleSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)");
+ log.debug(tableTag + " single substitution coverage table offset: " + co);
+ log.debug(tableTag + " single substitution glyph count: " + ng);
+ }
+ // read coverage table
+ seMapping = readCoverageTable(tableTag + " single substitution coverage", subtableOffset + co);
+ // read glyph substitutions
+ int[] gsa = new int[ng];
+ for (int i = 0, n = ng; i < n; i++) {
+ int gs = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single substitution glyph[" + i + "]: " + gs);
+ }
+ gsa[i] = gs;
+ seEntries.add(Integer.valueOf(gs));
+ }
+ }
+
+ private int readSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readSingleSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported single substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readMultipleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read sequence count
+ int ns = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)");
+ log.debug(tableTag + " multiple substitution coverage table offset: " + co);
+ log.debug(tableTag + " multiple substitution sequence count: " + ns);
+ }
+ // read coverage table
+ seMapping = readCoverageTable(tableTag + " multiple substitution coverage", subtableOffset + co);
+ // read sequence table offsets
+ int[] soa = new int[ns];
+ for (int i = 0, n = ns; i < n; i++) {
+ soa[i] = data.readUnsignedShort();
+ }
+ // read sequence tables
+ int[][] gsa = new int [ ns ] [];
+ for (int i = 0, n = ns; i < n; i++) {
+ int so = soa[i];
+ int[] ga;
+ if (so > 0) {
+ data.seek(subtableOffset + so);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ ga = new int[ng];
+ for (int j = 0; j < ng; j++) {
+ ga[j] = data.readUnsignedShort();
+ }
+ } else {
+ ga = null;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString(ga));
+ }
+ gsa [ i ] = ga;
+ }
+ seEntries.add(gsa);
+ }
+
+ private int readMultipleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readMultipleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported multiple substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readAlternateSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read alternate set count
+ int ns = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)");
+ log.debug(tableTag + " alternate substitution coverage table offset: " + co);
+ log.debug(tableTag + " alternate substitution alternate set count: " + ns);
+ }
+ // read coverage table
+ seMapping = readCoverageTable(tableTag + " alternate substitution coverage", subtableOffset + co);
+ // read alternate set table offsets
+ int[] soa = new int[ns];
+ for (int i = 0, n = ns; i < n; i++) {
+ soa[i] = data.readUnsignedShort();
+ }
+ // read alternate set tables
+ for (int i = 0, n = ns; i < n; i++) {
+ int so = soa[i];
+ data.seek(subtableOffset + so);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ int[] ga = new int[ng];
+ for (int j = 0; j < ng; j++) {
+ int gs = data.readUnsignedShort();
+ ga[j] = gs;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString(ga));
+ }
+ seEntries.add(ga);
+ }
+ }
+
+ private int readAlternateSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readAlternateSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported alternate substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readLigatureSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read ligature set count
+ int ns = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)");
+ log.debug(tableTag + " ligature substitution coverage table offset: " + co);
+ log.debug(tableTag + " ligature substitution ligature set count: " + ns);
+ }
+ // read coverage table
+ seMapping = readCoverageTable(tableTag + " ligature substitution coverage", subtableOffset + co);
+ // read ligature set table offsets
+ int[] soa = new int[ns];
+ for (int i = 0, n = ns; i < n; i++) {
+ soa[i] = data.readUnsignedShort();
+ }
+ // read ligature set tables
+ for (int i = 0, n = ns; i < n; i++) {
+ int so = soa[i];
+ data.seek(subtableOffset + so);
+ // read ligature table count
+ int nl = data.readUnsignedShort();
+ int[] loa = new int[nl];
+ for (int j = 0; j < nl; j++) {
+ loa[j] = data.readUnsignedShort();
+ }
+ List ligs = new java.util.ArrayList();
+ for (int j = 0; j < nl; j++) {
+ int lo = loa[j];
+ data.seek(subtableOffset + so + lo);
+ // read ligature glyph id
+ int lg = data.readUnsignedShort();
+ // read ligature (input) component count
+ int nc = data.readUnsignedShort();
+ int[] ca = new int [ nc - 1 ];
+ // read ligature (input) component glyph ids
+ for (int k = 0; k < nc - 1; k++) {
+ ca[k] = data.readUnsignedShort();
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature substitution ligature set[" + i + "]: ligature(" + lg + "), components: " + toString(ca));
+ }
+ ligs.add(new GlyphSubstitutionTable.Ligature(lg, ca));
+ }
+ seEntries.add(new GlyphSubstitutionTable.LigatureSet(ligs));
+ }
+ }
+
+ private int readLigatureSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readLigatureSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported ligature substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private AdvancedTypographicTable.RuleLookup[] readRuleLookups(int numLookups, String header) throws IOException {
+ AdvancedTypographicTable.RuleLookup[] la = new AdvancedTypographicTable.RuleLookup [ numLookups ];
+ for (int i = 0, n = numLookups; i < n; i++) {
+ int sequenceIndex = data.readUnsignedShort();
+ int lookupIndex = data.readUnsignedShort();
+ la [ i ] = new AdvancedTypographicTable.RuleLookup(sequenceIndex, lookupIndex);
+ // dump info if debugging and header is non-null
+ if (log.isDebugEnabled() && (header != null)) {
+ log.debug(header + "lookup[" + i + "]: " + la[i]);
+ }
+ }
+ return la;
+ }
+
+ private void readContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read rule set count
+ int nrs = data.readUnsignedShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for (int i = 0; i < nrs; i++) {
+ rsoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)");
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co);
+ log.debug(tableTag + " contextual substitution rule set count: " + nrs);
+ for (int i = 0; i < nrs; i++) {
+ log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ nrs ];
+ String header = null;
+ for (int i = 0; i < nrs; i++) {
+ AdvancedTypographicTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if (rso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + rso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ AdvancedTypographicTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + rso + ro);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for (int k = 0, nk = glyphs.length; k < nk; k++) {
+ glyphs [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.GlyphSequenceRule(lookups, ng, glyphs);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(rsa);
+ }
+
+ private void readContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read class def table offset
+ int cdo = data.readUnsignedShort();
+ // read class rule set count
+ int ngc = data.readUnsignedShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for (int i = 0; i < ngc; i++) {
+ csoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)");
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co);
+ log.debug(tableTag + " contextual substitution class set count: " + ngc);
+ for (int i = 0; i < ngc; i++) {
+ log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " contextual substitution coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read class definition table
+ GlyphClassTable cdt;
+ if (cdo > 0) {
+ cdt = readClassDefTable(tableTag + " contextual substitution class definition", subtableOffset + cdo);
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ ngc ];
+ String header = null;
+ for (int i = 0; i < ngc; i++) {
+ int cso = csoa [ i ];
+ AdvancedTypographicTable.RuleSet rs;
+ if (cso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + cso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ int ro = roa [ j ];
+ AdvancedTypographicTable.ClassSequenceRule r;
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + cso + ro);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for (int k = 0, nk = classes.length; k < nk; k++) {
+ classes [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ClassSequenceRule(lookups, ng, classes);
+ } else {
+ assert ro > 0 : "unexpected null subclass rule offset";
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(cdt);
+ seEntries.add(Integer.valueOf(ngc));
+ seEntries.add(rsa);
+ }
+
+ private void readContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read glyph (input sequence length) count
+ int ng = data.readUnsignedShort();
+ // read substitution lookup count
+ int nl = data.readUnsignedShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for (int i = 0; i < ng; i++) {
+ gcoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)");
+ log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng);
+ log.debug(tableTag + " contextual substitution lookup count: " + nl);
+ for (int i = 0; i < ng; i++) {
+ log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i]);
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for (int i = 0; i < ng; i++) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if (gco > 0) {
+ gct = readCoverageTable(tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco);
+ } else {
+ gct = null;
+ }
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ // construct rule, rule set, and rule set array
+ AdvancedTypographicTable.Rule r = new AdvancedTypographicTable.CoverageSequenceRule(lookups, ng, gca);
+ AdvancedTypographicTable.RuleSet rs = new AdvancedTypographicTable.HomogeneousRuleSet(new AdvancedTypographicTable.Rule[] {r});
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet[] {rs};
+ // store results
+ assert (gca != null) && (gca.length > 0);
+ seMapping = gca[0];
+ seEntries.add(rsa);
+ }
+
+ private int readContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 3) {
+ readContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported contextual substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readChainedContextualSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read rule set count
+ int nrs = data.readUnsignedShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for (int i = 0; i < nrs; i++) {
+ rsoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)");
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co);
+ log.debug(tableTag + " chained contextual substitution rule set count: " + nrs);
+ for (int i = 0; i < nrs; i++) {
+ log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ nrs ];
+ String header = null;
+ for (int i = 0; i < nrs; i++) {
+ AdvancedTypographicTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if (rso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + rso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ AdvancedTypographicTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + rso + ro);
+ // read backtrack glyph count
+ int nbg = data.readUnsignedShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) {
+ backtrackGlyphs [ k ] = data.readUnsignedShort();
+ }
+ // read input glyph count
+ int nig = data.readUnsignedShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for (int k = 0, nk = glyphs.length; k < nk; k++) {
+ glyphs [ k ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph count
+ int nlg = data.readUnsignedShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) {
+ lookaheadGlyphs [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(rsa);
+ }
+
+ private void readChainedContextualSubTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read backtrack class def table offset
+ int bcdo = data.readUnsignedShort();
+ // read input class def table offset
+ int icdo = data.readUnsignedShort();
+ // read lookahead class def table offset
+ int lcdo = data.readUnsignedShort();
+ // read class set count
+ int ngc = data.readUnsignedShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for (int i = 0; i < ngc; i++) {
+ csoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)");
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co);
+ log.debug(tableTag + " chained contextual substitution class set count: " + ngc);
+ for (int i = 0; i < ngc; i++) {
+ log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " chained contextual substitution coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if (bcdo > 0) {
+ bcdt = readClassDefTable(tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo);
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if (icdo > 0) {
+ icdt = readClassDefTable(tableTag + " contextual substitution input class definition", subtableOffset + icdo);
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if (lcdo > 0) {
+ lcdt = readClassDefTable(tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo);
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ ngc ];
+ String header = null;
+ for (int i = 0; i < ngc; i++) {
+ int cso = csoa [ i ];
+ AdvancedTypographicTable.RuleSet rs;
+ if (cso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + cso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ int ro = roa [ j ];
+ AdvancedTypographicTable.ChainedClassSequenceRule r;
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + cso + ro);
+ // read backtrack glyph class count
+ int nbc = data.readUnsignedShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for (int k = 0, nk = backtrackClasses.length; k < nk; k++) {
+ backtrackClasses [ k ] = data.readUnsignedShort();
+ }
+ // read input glyph class count
+ int nic = data.readUnsignedShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for (int k = 0, nk = classes.length; k < nk; k++) {
+ classes [ k ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph class count
+ int nlc = data.readUnsignedShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) {
+ lookaheadClasses [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(icdt);
+ seEntries.add(bcdt);
+ seEntries.add(lcdt);
+ seEntries.add(Integer.valueOf(ngc));
+ seEntries.add(rsa);
+ }
+
+ private void readChainedContextualSubTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read backtrack glyph count
+ int nbg = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for (int i = 0; i < nbg; i++) {
+ bgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read input glyph count
+ int nig = data.readUnsignedShort();
+ // read input glyph coverage offsets
+ int[] igcoa = new int [ nig ];
+ for (int i = 0; i < nig; i++) {
+ igcoa [ i ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph count
+ int nlg = data.readUnsignedShort();
+ // read lookahead glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for (int i = 0; i < nlg; i++) {
+ lgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read substitution lookup count
+ int nl = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)");
+ log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg);
+ for (int i = 0; i < nbg; i++) {
+ log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual substitution input glyph count: " + nig);
+ for (int i = 0; i < nig; i++) {
+ log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg);
+ for (int i = 0; i < nlg; i++) {
+ log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual substitution lookup count: " + nl);
+ }
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for (int i = 0; i < nbg; i++) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if (bgco > 0) {
+ bgct = readCoverageTable(tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco);
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read input coverage tables
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for (int i = 0; i < nig; i++) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if (igco > 0) {
+ igct = readCoverageTable(tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco);
+ } else {
+ igct = null;
+ }
+ igca[i] = igct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for (int i = 0; i < nlg; i++) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if (lgco > 0) {
+ lgct = readCoverageTable(tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco);
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual substitution lookups: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ // construct rule, rule set, and rule set array
+ AdvancedTypographicTable.Rule r = new AdvancedTypographicTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca);
+ AdvancedTypographicTable.RuleSet rs = new AdvancedTypographicTable.HomogeneousRuleSet(new AdvancedTypographicTable.Rule[] {r});
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet[] {rs};
+ // store results
+ assert (igca != null) && (igca.length > 0);
+ seMapping = igca[0];
+ seEntries.add(rsa);
+ }
+
+ private int readChainedContextualSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readChainedContextualSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readChainedContextualSubTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 3) {
+ readChainedContextualSubTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported chained contextual substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readExtensionSubTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read extension lookup type
+ int lt = data.readUnsignedShort();
+ // read extension offset
+ long eo = data.readUnsignedInt();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension substitution subtable format: " + subtableFormat);
+ log.debug(tableTag + " extension substitution lookup type: " + lt);
+ log.debug(tableTag + " extension substitution lookup table offset: " + eo);
+ }
+ // read referenced subtable from extended offset
+ readGSUBSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo);
+ }
+
+ private int readExtensionSubTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readExtensionSubTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported extension substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readReverseChainedSingleSubTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read backtrack glyph count
+ int nbg = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for (int i = 0; i < nbg; i++) {
+ bgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph count
+ int nlg = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for (int i = 0; i < nlg; i++) {
+ lgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read substitution (output) glyph count
+ int ng = data.readUnsignedShort();
+ // read substitution (output) glyphs
+ int[] glyphs = new int [ ng ];
+ for (int i = 0, n = ng; i < n; i++) {
+ glyphs [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat);
+ log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co);
+ log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg);
+ for (int i = 0; i < nbg; i++) {
+ log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
+ }
+ log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg);
+ for (int i = 0; i < nlg; i++) {
+ log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
+ }
+ log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs));
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " reverse chained contextual substitution coverage", subtableOffset + co);
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for (int i = 0; i < nbg; i++) {
+ int bgco = bgcoa[i];
+ GlyphCoverageTable bgct;
+ if (bgco > 0) {
+ bgct = readCoverageTable(tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco);
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for (int i = 0; i < nlg; i++) {
+ int lgco = lgcoa[i];
+ GlyphCoverageTable lgct;
+ if (lgco > 0) {
+ lgct = readCoverageTable(tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco);
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(bgca);
+ seEntries.add(lgca);
+ seEntries.add(glyphs);
+ }
+
+ private int readReverseChainedSingleSubTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read substitution subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readReverseChainedSingleSubTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported reverse chained single substitution subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readGSUBSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ int subtableFormat = -1;
+ switch (lookupType) {
+ case GSUBLookupType.SINGLE:
+ subtableFormat = readSingleSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.MULTIPLE:
+ subtableFormat = readMultipleSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.ALTERNATE:
+ subtableFormat = readAlternateSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.LIGATURE:
+ subtableFormat = readLigatureSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.CONTEXTUAL:
+ subtableFormat = readContextualSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.REVERSE_CHAINED_SINGLE:
+ subtableFormat = readReverseChainedSingleSubTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GSUBLookupType.EXTENSION:
+ subtableFormat = readExtensionSubTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset);
+ break;
+ default:
+ break;
+ }
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_SUBSTITUTION, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat);
+ resetATSubState();
+ }
+
+ private GlyphPositioningTable.DeviceTable readPosDeviceTable(long subtableOffset, long deviceTableOffset) throws IOException {
+ long cp = data.getCurrentPosition();
+ data.seek(subtableOffset + deviceTableOffset);
+ // read start size
+ int ss = data.readUnsignedShort();
+ // read end size
+ int es = data.readUnsignedShort();
+ // read delta format
+ int df = data.readUnsignedShort();
+ int s1;
+ int m1;
+ int dm;
+ int dd;
+ int s2;
+ if (df == 1) {
+ s1 = 14;
+ m1 = 0x3;
+ dm = 1;
+ dd = 4;
+ s2 = 2;
+ } else if (df == 2) {
+ s1 = 12;
+ m1 = 0xF;
+ dm = 7;
+ dd = 16;
+ s2 = 4;
+ } else if (df == 3) {
+ s1 = 8;
+ m1 = 0xFF;
+ dm = 127;
+ dd = 256;
+ s2 = 8;
+ } else {
+ log.debug("unsupported device table delta format: " + df + ", ignoring device table");
+ return null;
+ }
+ // read deltas
+ int n = (es - ss) + 1;
+ if (n < 0) {
+ log.debug("invalid device table delta count: " + n + ", ignoring device table");
+ return null;
+ }
+ int[] da = new int [ n ];
+ for (int i = 0; (i < n) && (s2 > 0);) {
+ int p = data.readUnsignedShort();
+ for (int j = 0, k = 16 / s2; j < k; j++) {
+ int d = (p >> s1) & m1;
+ if (d > dm) {
+ d -= dd;
+ }
+ if (i < n) {
+ da [ i++ ] = d;
+ } else {
+ break;
+ }
+ p <<= s2;
+ }
+ }
+ data.seek(cp);
+ return new GlyphPositioningTable.DeviceTable(ss, es, da);
+ }
+
+ private GlyphPositioningTable.Value readPosValue(long subtableOffset, int valueFormat) throws IOException {
+ // XPlacement
+ int xp;
+ if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT) != 0) {
+ xp = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ } else {
+ xp = 0;
+ }
+ // YPlacement
+ int yp;
+ if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT) != 0) {
+ yp = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ } else {
+ yp = 0;
+ }
+ // XAdvance
+ int xa;
+ if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE) != 0) {
+ xa = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ } else {
+ xa = 0;
+ }
+ // YAdvance
+ int ya;
+ if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE) != 0) {
+ ya = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ } else {
+ ya = 0;
+ }
+ // XPlaDevice
+ GlyphPositioningTable.DeviceTable xpd;
+ if ((valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE) != 0) {
+ int xpdo = data.readUnsignedShort();
+ xpd = readPosDeviceTable(subtableOffset, xpdo);
+ } else {
+ xpd = null;
+ }
+ // YPlaDevice
+ GlyphPositioningTable.DeviceTable ypd;
+ if ((valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE) != 0) {
+ int ypdo = data.readUnsignedShort();
+ ypd = readPosDeviceTable(subtableOffset, ypdo);
+ } else {
+ ypd = null;
+ }
+ // XAdvDevice
+ GlyphPositioningTable.DeviceTable xad;
+ if ((valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE) != 0) {
+ int xado = data.readUnsignedShort();
+ xad = readPosDeviceTable(subtableOffset, xado);
+ } else {
+ xad = null;
+ }
+ // YAdvDevice
+ GlyphPositioningTable.DeviceTable yad;
+ if ((valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE) != 0) {
+ int yado = data.readUnsignedShort();
+ yad = readPosDeviceTable(subtableOffset, yado);
+ } else {
+ yad = null;
+ }
+ return new GlyphPositioningTable.Value(xp, yp, xa, ya, xpd, ypd, xad, yad);
+ }
+
+ private void readSinglePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read value format
+ int vf = data.readUnsignedShort();
+ // read value
+ GlyphPositioningTable.Value v = readPosValue(subtableOffset, vf);
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)");
+ log.debug(tableTag + " single positioning coverage table offset: " + co);
+ log.debug(tableTag + " single positioning value: " + v);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co);
+ // store results
+ seMapping = ct;
+ seEntries.add(v);
+ }
+
+ private void readSinglePosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read value format
+ int vf = data.readUnsignedShort();
+ // read value count
+ int nv = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)");
+ log.debug(tableTag + " single positioning coverage table offset: " + co);
+ log.debug(tableTag + " single positioning value count: " + nv);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " single positioning coverage", subtableOffset + co);
+ // read positioning values
+ GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv];
+ for (int i = 0, n = nv; i < n; i++) {
+ GlyphPositioningTable.Value pv = readPosValue(subtableOffset, vf);
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning value[" + i + "]: " + pv);
+ }
+ pva[i] = pv;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(pva);
+ }
+
+ private int readSinglePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positionining subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readSinglePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readSinglePosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported single positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private GlyphPositioningTable.PairValues readPosPairValues(long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException {
+ // read glyph (if present)
+ int glyph;
+ if (hasGlyph) {
+ glyph = data.readUnsignedShort();
+ } else {
+ glyph = 0;
+ }
+ // read first value (if present)
+ GlyphPositioningTable.Value v1;
+ if (vf1 != 0) {
+ v1 = readPosValue(subtableOffset, vf1);
+ } else {
+ v1 = null;
+ }
+ // read second value (if present)
+ GlyphPositioningTable.Value v2;
+ if (vf2 != 0) {
+ v2 = readPosValue(subtableOffset, vf2);
+ } else {
+ v2 = null;
+ }
+ return new GlyphPositioningTable.PairValues(glyph, v1, v2);
+ }
+
+ private GlyphPositioningTable.PairValues[] readPosPairSetTable(long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException {
+ String tableTag = "GPOS";
+ long cp = data.getCurrentPosition();
+ data.seek(subtableOffset + pairSetTableOffset);
+ // read pair values count
+ int npv = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table offset: " + pairSetTableOffset);
+ log.debug(tableTag + " pair set table values count: " + npv);
+ }
+ // read pair values
+ GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ];
+ for (int i = 0, n = npv; i < n; i++) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, true, vf1, vf2);
+ pva [ i ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "]: " + pv);
+ }
+ }
+ data.seek(cp);
+ return pva;
+ }
+
+ private void readPairPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read value format for first glyph
+ int vf1 = data.readUnsignedShort();
+ // read value format for second glyph
+ int vf2 = data.readUnsignedShort();
+ // read number (count) of pair sets
+ int nps = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)");
+ log.debug(tableTag + " pair positioning coverage table offset: " + co);
+ log.debug(tableTag + " pair positioning value format #1: " + vf1);
+ log.debug(tableTag + " pair positioning value format #2: " + vf2);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co);
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][];
+ for (int i = 0, n = nps; i < n; i++) {
+ // read pair set offset
+ int pso = data.readUnsignedShort();
+ // read pair set table at offset
+ pvm [ i ] = readPosPairSetTable(subtableOffset, pso, vf1, vf2);
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(pvm);
+ }
+
+ private void readPairPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read value format for first glyph
+ int vf1 = data.readUnsignedShort();
+ // read value format for second glyph
+ int vf2 = data.readUnsignedShort();
+ // read class def 1 offset
+ int cd1o = data.readUnsignedShort();
+ // read class def 2 offset
+ int cd2o = data.readUnsignedShort();
+ // read number (count) of classes in class def 1 table
+ int nc1 = data.readUnsignedShort();
+ // read number (count) of classes in class def 2 table
+ int nc2 = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)");
+ log.debug(tableTag + " pair positioning coverage table offset: " + co);
+ log.debug(tableTag + " pair positioning value format #1: " + vf1);
+ log.debug(tableTag + " pair positioning value format #2: " + vf2);
+ log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o);
+ log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o);
+ log.debug(tableTag + " pair positioning class #1 count: " + nc1);
+ log.debug(tableTag + " pair positioning class #2 count: " + nc2);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " pair positioning coverage", subtableOffset + co);
+ // read class definition table #1
+ GlyphClassTable cdt1 = readClassDefTable(tableTag + " pair positioning class definition #1", subtableOffset + cd1o);
+ // read class definition table #2
+ GlyphClassTable cdt2 = readClassDefTable(tableTag + " pair positioning class definition #2", subtableOffset + cd2o);
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ];
+ for (int i = 0; i < nc1; i++) {
+ for (int j = 0; j < nc2; j++) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues(subtableOffset, false, vf1, vf2);
+ pvm [ i ] [ j ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv);
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(cdt1);
+ seEntries.add(cdt2);
+ seEntries.add(Integer.valueOf(nc1));
+ seEntries.add(Integer.valueOf(nc2));
+ seEntries.add(pvm);
+ }
+
+ private int readPairPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readPairPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readPairPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported pair positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private GlyphPositioningTable.Anchor readPosAnchor(long anchorTableOffset) throws IOException {
+ GlyphPositioningTable.Anchor a;
+ long cp = data.getCurrentPosition();
+ data.seek(anchorTableOffset);
+ // read anchor table format
+ int af = data.readUnsignedShort();
+ if (af == 1) {
+ // read x coordinate
+ int x = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ // read y coordinate
+ int y = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ a = new GlyphPositioningTable.Anchor(x, y);
+ } else if (af == 2) {
+ // read x coordinate
+ int x = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ // read y coordinate
+ int y = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ // read anchor point index
+ int ap = data.readUnsignedShort();
+ a = new GlyphPositioningTable.Anchor(x, y, ap);
+ } else if (af == 3) {
+ // read x coordinate
+ int x = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ // read y coordinate
+ int y = otf.convertTTFUnit2PDFUnit(data.readSignedShort());
+ // read x device table offset
+ int xdo = data.readUnsignedShort();
+ // read y device table offset
+ int ydo = data.readUnsignedShort();
+ // read x device table (if present)
+ GlyphPositioningTable.DeviceTable xd;
+ if (xdo != 0) {
+ xd = readPosDeviceTable(cp, xdo);
+ } else {
+ xd = null;
+ }
+ // read y device table (if present)
+ GlyphPositioningTable.DeviceTable yd;
+ if (ydo != 0) {
+ yd = readPosDeviceTable(cp, ydo);
+ } else {
+ yd = null;
+ }
+ a = new GlyphPositioningTable.Anchor(x, y, xd, yd);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported positioning anchor format: " + af);
+ }
+ data.seek(cp);
+ return a;
+ }
+
+ private void readCursivePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read entry/exit count
+ int ec = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat);
+ log.debug(tableTag + " cursive positioning coverage table offset: " + co);
+ log.debug(tableTag + " cursive positioning entry/exit count: " + ec);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " cursive positioning coverage", subtableOffset + co);
+ // read entry/exit records
+ GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ];
+ for (int i = 0, n = ec; i < n; i++) {
+ // read entry anchor offset
+ int eno = data.readUnsignedShort();
+ // read exit anchor offset
+ int exo = data.readUnsignedShort();
+ // read entry anchor
+ GlyphPositioningTable.Anchor ena;
+ if (eno > 0) {
+ ena = readPosAnchor(subtableOffset + eno);
+ } else {
+ ena = null;
+ }
+ // read exit anchor
+ GlyphPositioningTable.Anchor exa;
+ if (exo > 0) {
+ exa = readPosAnchor(subtableOffset + exo);
+ } else {
+ exa = null;
+ }
+ aa [ (i * 2) + 0 ] = ena;
+ aa [ (i * 2) + 1 ] = exa;
+ if (log.isDebugEnabled()) {
+ if (ena != null) {
+ log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena);
+ }
+ if (exa != null) {
+ log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa);
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(aa);
+ }
+
+ private int readCursivePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readCursivePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported cursive positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readMarkToBasePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read mark coverage offset
+ int mco = data.readUnsignedShort();
+ // read base coverage offset
+ int bco = data.readUnsignedShort();
+ // read mark class count
+ int nmc = data.readUnsignedShort();
+ // read mark array offset
+ int mao = data.readUnsignedShort();
+ // read base array offset
+ int bao = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat);
+ log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco);
+ log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco);
+ log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc);
+ log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao);
+ log.debug(tableTag + " mark-to-base positioning base array offset: " + bao);
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco);
+ // read base coverage table
+ GlyphCoverageTable bct = readCoverageTable(tableTag + " mark-to-base positioning base coverage", subtableOffset + bco);
+ // read mark anchor array
+ // seek to mark array
+ data.seek(subtableOffset + mao);
+ // read mark count
+ int nm = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark count: " + nm);
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for (int i = 0; i < nm; i++) {
+ // read mark class
+ int mc = data.readUnsignedShort();
+ // read mark anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + mao + ao);
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if (a != null) {
+ ma = new GlyphPositioningTable.MarkAnchor(mc, a);
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma);
+ }
+
+ }
+ // read base anchor matrix
+ // seek to base array
+ data.seek(subtableOffset + bao);
+ // read base count
+ int nb = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base count: " + nb);
+ }
+ // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ];
+ for (int i = 0; i < nb; i++) {
+ for (int j = 0; j < nmc; j++) {
+ // read base anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + bao + ao);
+ } else {
+ a = null;
+ }
+ bam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add(bct);
+ seEntries.add(Integer.valueOf(nmc));
+ seEntries.add(maa);
+ seEntries.add(bam);
+ }
+
+ private int readMarkToBasePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readMarkToBasePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported mark-to-base positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readMarkToLigaturePosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read mark coverage offset
+ int mco = data.readUnsignedShort();
+ // read ligature coverage offset
+ int lco = data.readUnsignedShort();
+ // read mark class count
+ int nmc = data.readUnsignedShort();
+ // read mark array offset
+ int mao = data.readUnsignedShort();
+ // read ligature array offset
+ int lao = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat);
+ log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco);
+ log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco);
+ log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc);
+ log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao);
+ log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao);
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable(tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco);
+ // read ligature coverage table
+ GlyphCoverageTable lct = readCoverageTable(tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco);
+ // read mark anchor array
+ // seek to mark array
+ data.seek(subtableOffset + mao);
+ // read mark count
+ int nm = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm);
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for (int i = 0; i < nm; i++) {
+ // read mark class
+ int mc = data.readUnsignedShort();
+ // read mark anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + mao + ao);
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if (a != null) {
+ ma = new GlyphPositioningTable.MarkAnchor(mc, a);
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma);
+ }
+ }
+ // read ligature anchor matrix
+ // seek to ligature array
+ data.seek(subtableOffset + lao);
+ // read ligature count
+ int nl = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl);
+ }
+ // read ligature attach table offsets
+ int[] laoa = new int [ nl ];
+ for (int i = 0; i < nl; i++) {
+ laoa [ i ] = data.readUnsignedShort();
+ }
+ // iterate over ligature attach tables, recording maximum component count
+ int mxc = 0;
+ for (int i = 0; i < nl; i++) {
+ int lato = laoa [ i ];
+ data.seek(subtableOffset + lao + lato);
+ // read component count
+ int cc = data.readUnsignedShort();
+ if (cc > mxc) {
+ mxc = cc;
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc);
+ }
+ // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][];
+ for (int i = 0; i < nl; i++) {
+ int lato = laoa [ i ];
+ // seek to ligature attach table for ligature[i]
+ data.seek(subtableOffset + lao + lato);
+ // read component count
+ int cc = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ];
+ for (int j = 0; j < cc; j++) {
+ for (int k = 0; k < nmc; k++) {
+ // read ligature anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + lao + lato + ao);
+ } else {
+ a = null;
+ }
+ lcm [ j ] [ k ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a);
+ }
+ }
+ }
+ lam [ i ] = lcm;
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add(lct);
+ seEntries.add(Integer.valueOf(nmc));
+ seEntries.add(Integer.valueOf(mxc));
+ seEntries.add(maa);
+ seEntries.add(lam);
+ }
+
+ private int readMarkToLigaturePosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readMarkToLigaturePosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported mark-to-ligature positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readMarkToMarkPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read mark #1 coverage offset
+ int m1co = data.readUnsignedShort();
+ // read mark #2 coverage offset
+ int m2co = data.readUnsignedShort();
+ // read mark class count
+ int nmc = data.readUnsignedShort();
+ // read mark #1 array offset
+ int m1ao = data.readUnsignedShort();
+ // read mark #2 array offset
+ int m2ao = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat);
+ log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co);
+ log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co);
+ log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc);
+ log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao);
+ log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao);
+ }
+ // read mark #1 coverage table
+ GlyphCoverageTable mct1 = readCoverageTable(tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co);
+ // read mark #2 coverage table
+ GlyphCoverageTable mct2 = readCoverageTable(tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co);
+ // read mark #1 anchor array
+ // seek to mark array
+ data.seek(subtableOffset + m1ao);
+ // read mark count
+ int nm1 = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1);
+ }
+ // read mark anchor array, where i:{0...mark1Count}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ];
+ for (int i = 0; i < nm1; i++) {
+ // read mark class
+ int mc = data.readUnsignedShort();
+ // read mark anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + m1ao + ao);
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if (a != null) {
+ ma = new GlyphPositioningTable.MarkAnchor(mc, a);
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma);
+ }
+ }
+ // read mark #2 anchor matrix
+ // seek to mark #2 array
+ data.seek(subtableOffset + m2ao);
+ // read mark #2 count
+ int nm2 = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2);
+ }
+ // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ];
+ for (int i = 0; i < nm2; i++) {
+ for (int j = 0; j < nmc; j++) {
+ // read mark anchor offset
+ int ao = data.readUnsignedShort();
+ GlyphPositioningTable.Anchor a;
+ if (ao > 0) {
+ a = readPosAnchor(subtableOffset + m2ao + ao);
+ } else {
+ a = null;
+ }
+ mam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct1;
+ seEntries.add(mct2);
+ seEntries.add(Integer.valueOf(nmc));
+ seEntries.add(maa);
+ seEntries.add(mam);
+ }
+
+ private int readMarkToMarkPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readMarkToMarkPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported mark-to-mark positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read rule set count
+ int nrs = data.readUnsignedShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for (int i = 0; i < nrs; i++) {
+ rsoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)");
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co);
+ log.debug(tableTag + " contextual positioning rule set count: " + nrs);
+ for (int i = 0; i < nrs; i++) {
+ log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ nrs ];
+ String header = null;
+ for (int i = 0; i < nrs; i++) {
+ AdvancedTypographicTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if (rso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + rso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ AdvancedTypographicTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + rso + ro);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for (int k = 0, nk = glyphs.length; k < nk; k++) {
+ glyphs [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.GlyphSequenceRule(lookups, ng, glyphs);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(rsa);
+ }
+
+ private void readContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read class def table offset
+ int cdo = data.readUnsignedShort();
+ // read class rule set count
+ int ngc = data.readUnsignedShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for (int i = 0; i < ngc; i++) {
+ csoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)");
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co);
+ log.debug(tableTag + " contextual positioning class set count: " + ngc);
+ for (int i = 0; i < ngc; i++) {
+ log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " contextual positioning coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read class definition table
+ GlyphClassTable cdt;
+ if (cdo > 0) {
+ cdt = readClassDefTable(tableTag + " contextual positioning class definition", subtableOffset + cdo);
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ ngc ];
+ String header = null;
+ for (int i = 0; i < ngc; i++) {
+ int cso = csoa [ i ];
+ AdvancedTypographicTable.RuleSet rs;
+ if (cso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + cso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ int ro = roa [ j ];
+ AdvancedTypographicTable.ClassSequenceRule r;
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + cso + ro);
+ // read glyph count
+ int ng = data.readUnsignedShort();
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for (int k = 0, nk = classes.length; k < nk; k++) {
+ classes [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ClassSequenceRule(lookups, ng, classes);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(cdt);
+ seEntries.add(Integer.valueOf(ngc));
+ seEntries.add(rsa);
+ }
+
+ private void readContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read glyph (input sequence length) count
+ int ng = data.readUnsignedShort();
+ // read positioning lookup count
+ int nl = data.readUnsignedShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for (int i = 0; i < ng; i++) {
+ gcoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)");
+ log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng);
+ log.debug(tableTag + " contextual positioning lookup count: " + nl);
+ for (int i = 0; i < ng; i++) {
+ log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i]);
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for (int i = 0; i < ng; i++) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if (gco > 0) {
+ gct = readCoverageTable(tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i]);
+ } else {
+ gct = null;
+ }
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ // construct rule, rule set, and rule set array
+ AdvancedTypographicTable.Rule r = new AdvancedTypographicTable.CoverageSequenceRule(lookups, ng, gca);
+ AdvancedTypographicTable.RuleSet rs = new AdvancedTypographicTable.HomogeneousRuleSet(new AdvancedTypographicTable.Rule[] {r});
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet[] {rs};
+ // store results
+ assert (gca != null) && (gca.length > 0);
+ seMapping = gca[0];
+ seEntries.add(rsa);
+ }
+
+ private int readContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 3) {
+ readContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported contextual positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readChainedContextualPosTableFormat1(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read rule set count
+ int nrs = data.readUnsignedShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for (int i = 0; i < nrs; i++) {
+ rsoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)");
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co);
+ log.debug(tableTag + " chained contextual positioning rule set count: " + nrs);
+ for (int i = 0; i < nrs; i++) {
+ log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ nrs ];
+ String header = null;
+ for (int i = 0; i < nrs; i++) {
+ AdvancedTypographicTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if (rso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + rso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ AdvancedTypographicTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + rso + ro);
+ // read backtrack glyph count
+ int nbg = data.readUnsignedShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for (int k = 0, nk = backtrackGlyphs.length; k < nk; k++) {
+ backtrackGlyphs [ k ] = data.readUnsignedShort();
+ }
+ // read input glyph count
+ int nig = data.readUnsignedShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for (int k = 0, nk = glyphs.length; k < nk; k++) {
+ glyphs [ k ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph count
+ int nlg = data.readUnsignedShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for (int k = 0, nk = lookaheadGlyphs.length; k < nk; k++) {
+ lookaheadGlyphs [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ChainedGlyphSequenceRule(lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(rsa);
+ }
+
+ private void readChainedContextualPosTableFormat2(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read backtrack class def table offset
+ int bcdo = data.readUnsignedShort();
+ // read input class def table offset
+ int icdo = data.readUnsignedShort();
+ // read lookahead class def table offset
+ int lcdo = data.readUnsignedShort();
+ // read class set count
+ int ngc = data.readUnsignedShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for (int i = 0; i < ngc; i++) {
+ csoa [ i ] = data.readUnsignedShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)");
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co);
+ log.debug(tableTag + " chained contextual positioning class set count: " + ngc);
+ for (int i = 0; i < ngc; i++) {
+ log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if (co > 0) {
+ ct = readCoverageTable(tableTag + " chained contextual positioning coverage", subtableOffset + co);
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if (bcdo > 0) {
+ bcdt = readClassDefTable(tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo);
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if (icdo > 0) {
+ icdt = readClassDefTable(tableTag + " contextual positioning input class definition", subtableOffset + icdo);
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if (lcdo > 0) {
+ lcdt = readClassDefTable(tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo);
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet [ ngc ];
+ String header = null;
+ for (int i = 0; i < ngc; i++) {
+ int cso = csoa [ i ];
+ AdvancedTypographicTable.RuleSet rs;
+ if (cso > 0) {
+ // seek to rule set [ i ]
+ data.seek(subtableOffset + cso);
+ // read rule count
+ int nr = data.readUnsignedShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ AdvancedTypographicTable.Rule[] ra = new AdvancedTypographicTable.Rule [ nr ];
+ for (int j = 0; j < nr; j++) {
+ roa [ j ] = data.readUnsignedShort();
+ }
+ // read glyph sequence rules
+ for (int j = 0; j < nr; j++) {
+ AdvancedTypographicTable.ChainedClassSequenceRule r;
+ int ro = roa [ j ];
+ if (ro > 0) {
+ // seek to rule [ j ]
+ data.seek(subtableOffset + cso + ro);
+ // read backtrack glyph class count
+ int nbc = data.readUnsignedShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for (int k = 0, nk = backtrackClasses.length; k < nk; k++) {
+ backtrackClasses [ k ] = data.readUnsignedShort();
+ }
+ // read input glyph class count
+ int nic = data.readUnsignedShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for (int k = 0, nk = classes.length; k < nk; k++) {
+ classes [ k ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph class count
+ int nlc = data.readUnsignedShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for (int k = 0, nk = lookaheadClasses.length; k < nk; k++) {
+ lookaheadClasses [ k ] = data.readUnsignedShort();
+ }
+ // read rule lookup count
+ int nl = data.readUnsignedShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ r = new AdvancedTypographicTable.ChainedClassSequenceRule(lookups, nic, classes, backtrackClasses, lookaheadClasses);
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new AdvancedTypographicTable.HomogeneousRuleSet(ra);
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add(icdt);
+ seEntries.add(bcdt);
+ seEntries.add(lcdt);
+ seEntries.add(Integer.valueOf(ngc));
+ seEntries.add(rsa);
+ }
+
+ private void readChainedContextualPosTableFormat3(int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read backtrack glyph count
+ int nbg = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for (int i = 0; i < nbg; i++) {
+ bgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read input glyph count
+ int nig = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] igcoa = new int [ nig ];
+ for (int i = 0; i < nig; i++) {
+ igcoa [ i ] = data.readUnsignedShort();
+ }
+ // read lookahead glyph count
+ int nlg = data.readUnsignedShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for (int i = 0; i < nlg; i++) {
+ lgcoa [ i ] = data.readUnsignedShort();
+ }
+ // read positioning lookup count
+ int nl = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)");
+ log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg);
+ for (int i = 0; i < nbg; i++) {
+ log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual positioning input glyph count: " + nig);
+ for (int i = 0; i < nig; i++) {
+ log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg);
+ for (int i = 0; i < nlg; i++) {
+ log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i]);
+ }
+ log.debug(tableTag + " chained contextual positioning lookup count: " + nl);
+ }
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for (int i = 0; i < nbg; i++) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if (bgco > 0) {
+ bgct = readCoverageTable(tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco);
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read input coverage tables
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for (int i = 0; i < nig; i++) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if (igco > 0) {
+ igct = readCoverageTable(tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco);
+ } else {
+ igct = null;
+ }
+ igca[i] = igct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for (int i = 0; i < nlg; i++) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if (lgco > 0) {
+ lgct = readCoverageTable(tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco);
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual positioning lookups: ";
+ }
+ AdvancedTypographicTable.RuleLookup[] lookups = readRuleLookups(nl, header);
+ // construct rule, rule set, and rule set array
+ AdvancedTypographicTable.Rule r = new AdvancedTypographicTable.ChainedCoverageSequenceRule(lookups, nig, igca, bgca, lgca);
+ AdvancedTypographicTable.RuleSet rs = new AdvancedTypographicTable.HomogeneousRuleSet(new AdvancedTypographicTable.Rule[] {r});
+ AdvancedTypographicTable.RuleSet[] rsa = new AdvancedTypographicTable.RuleSet[] {rs};
+ // store results
+ assert (igca != null) && (igca.length > 0);
+ seMapping = igca[0];
+ seEntries.add(rsa);
+ }
+
+ private int readChainedContextualPosTable(int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readChainedContextualPosTableFormat1(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 2) {
+ readChainedContextualPosTableFormat2(lookupType, lookupFlags, subtableOffset, sf);
+ } else if (sf == 3) {
+ readChainedContextualPosTableFormat3(lookupType, lookupFlags, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported chained contextual positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readExtensionPosTableFormat1(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read extension lookup type
+ int lt = data.readUnsignedShort();
+ // read extension offset
+ long eo = data.readUnsignedInt();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension positioning subtable format: " + subtableFormat);
+ log.debug(tableTag + " extension positioning lookup type: " + lt);
+ log.debug(tableTag + " extension positioning lookup table offset: " + eo);
+ }
+ // read referenced subtable from extended offset
+ readGPOSSubtable(lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo);
+ }
+
+ private int readExtensionPosTable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read positioning subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readExtensionPosTableFormat1(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported extension positioning subtable format: " + sf);
+ }
+ return sf;
+ }
+
+ private void readGPOSSubtable(int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ int subtableFormat = -1;
+ switch (lookupType) {
+ case GPOSLookupType.SINGLE:
+ subtableFormat = readSinglePosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.PAIR:
+ subtableFormat = readPairPosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.CURSIVE:
+ subtableFormat = readCursivePosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.MARK_TO_BASE:
+ subtableFormat = readMarkToBasePosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.MARK_TO_LIGATURE:
+ subtableFormat = readMarkToLigaturePosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.MARK_TO_MARK:
+ subtableFormat = readMarkToMarkPosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.CONTEXTUAL:
+ subtableFormat = readContextualPosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualPosTable(lookupType, lookupFlags, subtableOffset);
+ break;
+ case GPOSLookupType.EXTENSION:
+ subtableFormat = readExtensionPosTable(lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset);
+ break;
+ default:
+ break;
+ }
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_POSITIONING, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableFormat);
+ resetATSubState();
+ }
+
+ private void readLookupTable(String tableTag, int lookupSequence, long lookupTable) throws IOException {
+ boolean isGSUB = tableTag.equals(GlyphSubstitutionTable.TAG);
+ boolean isGPOS = tableTag.equals(GlyphPositioningTable.TAG);
+ data.seek(lookupTable);
+ // read lookup type
+ int lt = data.readUnsignedShort();
+ // read lookup flags
+ int lf = data.readUnsignedShort();
+ // read sub-table count
+ int ns = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ String lts;
+ if (isGSUB) {
+ lts = GSUBLookupType.toString(lt);
+ } else if (isGPOS) {
+ lts = GPOSLookupType.toString(lt);
+ } else {
+ lts = "?";
+ }
+ log.debug(tableTag + " lookup table type: " + lt + " (" + lts + ")");
+ log.debug(tableTag + " lookup table flags: " + lf + " (" + LookupFlag.toString(lf) + ")");
+ log.debug(tableTag + " lookup table subtable count: " + ns);
+ }
+ // read subtable offsets
+ int[] soa = new int[ns];
+ for (int i = 0; i < ns; i++) {
+ int so = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table subtable offset: " + so);
+ }
+ soa[i] = so;
+ }
+ // read mark filtering set
+ if ((lf & LookupFlag.USE_MARK_FILTERING_SET) != 0) {
+ // read mark filtering set
+ int fs = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table mark filter set: " + fs);
+ }
+ }
+ // read subtables
+ for (int i = 0; i < ns; i++) {
+ int so = soa[i];
+ if (isGSUB) {
+ readGSUBSubtable(lt, lf, lookupSequence, i, lookupTable + so);
+ } else if (isGPOS) {
+ readGPOSSubtable(lt, lf, lookupSequence, i, lookupTable + so);
+ }
+ }
+ }
+
+ private void readLookupList(String tableTag, long lookupList) throws IOException {
+ data.seek(lookupList);
+ // read lookup record count
+ int nl = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup list record count: " + nl);
+ }
+ if (nl > 0) {
+ int[] loa = new int[nl];
+ // read lookup records
+ for (int i = 0, n = nl; i < n; i++) {
+ int lo = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup table offset: " + lo);
+ }
+ loa[i] = lo;
+ }
+ // read lookup tables
+ for (int i = 0, n = nl; i < n; i++) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " lookup index: " + i);
+ }
+ readLookupTable(tableTag, i, lookupList + loa [ i ]);
+ }
+ }
+ }
+
+ /**
+ * Read the common layout tables (used by GSUB and GPOS).
+ * @param tableTag tag of table being read
+ * @param scriptList offset to script list from beginning of font file
+ * @param featureList offset to feature list from beginning of font file
+ * @param lookupList offset to lookup list from beginning of font file
+ * @throws IOException In case of a I/O problem
+ */
+ private void readCommonLayoutTables(String tableTag, long scriptList, long featureList, long lookupList) throws IOException {
+ if (scriptList > 0) {
+ readScriptList(tableTag, scriptList);
+ }
+ if (featureList > 0) {
+ readFeatureList(tableTag, featureList);
+ }
+ if (lookupList > 0) {
+ readLookupList(tableTag, lookupList);
+ }
+ }
+
+ private void readGDEFClassDefTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ data.seek(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset);
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1);
+ resetATSubState();
+ }
+
+ private void readGDEFAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ data.seek(subtableOffset);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " attachment point coverage table offset: " + co);
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " attachment point coverage", subtableOffset + co);
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1);
+ resetATSubState();
+ }
+
+ private void readGDEFLigatureCaretTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ data.seek(subtableOffset);
+ // read coverage offset
+ int co = data.readUnsignedShort();
+ // read ligature glyph count
+ int nl = data.readUnsignedShort();
+ // read ligature glyph table offsets
+ int[] lgto = new int [ nl ];
+ for (int i = 0; i < nl; i++) {
+ lgto [ i ] = data.readUnsignedShort();
+ }
+
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature caret coverage table offset: " + co);
+ log.debug(tableTag + " ligature caret ligature glyph count: " + nl);
+ for (int i = 0; i < nl; i++) {
+ log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i]);
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable(tableTag + " ligature caret coverage", subtableOffset + co);
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1);
+ resetATSubState();
+ }
+
+ private void readGDEFMarkAttachmentTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initATSubState();
+ data.seek(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable(tableTag + " glyph class definition table", subtableOffset);
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1);
+ resetATSubState();
+ }
+
+ private void readGDEFMarkGlyphsTableFormat1(String tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
+ initATSubState();
+ data.seek(subtableOffset);
+ // skip over format (already known)
+ data.skip(2);
+ // read mark set class count
+ int nmc = data.readUnsignedShort();
+ long[] mso = new long [ nmc ];
+ // read mark set coverage offsets
+ for (int i = 0; i < nmc; i++) {
+ mso [ i ] = data.readUnsignedInt();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)");
+ log.debug(tableTag + " mark set class count: " + nmc);
+ for (int i = 0; i < nmc; i++) {
+ log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i]);
+ }
+ }
+ // read mark set coverage tables, one per class
+ GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc];
+ for (int i = 0; i < nmc; i++) {
+ msca[i] = readCoverageTable(tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i]);
+ }
+ // create combined class table from per-class coverage tables
+ GlyphClassTable ct = GlyphClassTable.createClassTable(Arrays.asList(msca));
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState(AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1);
+ resetATSubState();
+ }
+
+ private void readGDEFMarkGlyphsTable(String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ data.seek(subtableOffset);
+ // read mark set subtable format
+ int sf = data.readUnsignedShort();
+ if (sf == 1) {
+ readGDEFMarkGlyphsTableFormat1(tableTag, lookupSequence, subtableOffset, sf);
+ } else {
+ throw new AdvancedTypographicTableFormatException("unsupported mark glyph sets subtable format: " + sf);
+ }
+ }
+
+ /**
+ * Read the GDEF table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGDEF() throws IOException {
+ String tableTag = GlyphDefinitionTable.TAG;
+ // Initialize temporary state
+ initATState();
+ // Read glyph definition (GDEF) table
+ long version = data.readUnsignedInt();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
+ }
+ // glyph class definition table offset (may be null)
+ int cdo = data.readUnsignedShort();
+ // attach point list offset (may be null)
+ int apo = data.readUnsignedShort();
+ // ligature caret list offset (may be null)
+ int lco = data.readUnsignedShort();
+ // mark attach class definition table offset (may be null)
+ int mao = data.readUnsignedShort();
+ // mark glyph sets definition table offset (may be null)
+ int mgo;
+ if (version >= 0x00010002) {
+ mgo = data.readUnsignedShort();
+ } else {
+ mgo = 0;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " glyph class definition table offset: " + cdo);
+ log.debug(tableTag + " attachment point list offset: " + apo);
+ log.debug(tableTag + " ligature caret list offset: " + lco);
+ log.debug(tableTag + " mark attachment class definition table offset: " + mao);
+ log.debug(tableTag + " mark glyph set definitions table offset: " + mgo);
+ }
+ // initialize subtable sequence number
+ int seqno = 0;
+ // obtain offset to start of gdef table
+ long to = table.getOffset();
+ // (optionally) read glyph class definition subtable
+ if (cdo != 0) {
+ readGDEFClassDefTable(tableTag, seqno++, to + cdo);
+ }
+ // (optionally) read glyph attachment point subtable
+ if (apo != 0) {
+ readGDEFAttachmentTable(tableTag, seqno++, to + apo);
+ }
+ // (optionally) read ligature caret subtable
+ if (lco != 0) {
+ readGDEFLigatureCaretTable(tableTag, seqno++, to + lco);
+ }
+ // (optionally) read mark attachment class subtable
+ if (mao != 0) {
+ readGDEFMarkAttachmentTable(tableTag, seqno++, to + mao);
+ }
+ // (optionally) read mark glyph sets subtable
+ if (mgo != 0) {
+ readGDEFMarkGlyphsTable(tableTag, seqno++, to + mgo);
+ }
+ initializeGDEF();
+ }
+
+ /**
+ * Read the GSUB table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGSUB() throws IOException {
+ String tableTag = GlyphSubstitutionTable.TAG;
+ // Initialize temporary state
+ initATState();
+ // Read glyph substitution (GSUB) table
+ long version = data.readUnsignedInt();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
+ }
+ int slo = data.readUnsignedShort();
+ int flo = data.readUnsignedShort();
+ int llo = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list offset: " + slo);
+ log.debug(tableTag + " feature list offset: " + flo);
+ log.debug(tableTag + " lookup list offset: " + llo);
+ }
+ long to = table.getOffset();
+ readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo);
+ initializeGSUB();
+ }
+
+ /**
+ * Read the GPOS table.
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGPOS() throws IOException {
+ String tableTag = GlyphPositioningTable.TAG;
+ // Initialize temporary state
+ initATState();
+ // Read glyph positioning (GPOS) table
+ long version = data.readUnsignedInt();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + (version / 65536) + "." + (version % 65536));
+ }
+ int slo = data.readUnsignedShort();
+ int flo = data.readUnsignedShort();
+ int llo = data.readUnsignedShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " script list offset: " + slo);
+ log.debug(tableTag + " feature list offset: " + flo);
+ log.debug(tableTag + " lookup list offset: " + llo);
+ }
+ long to = table.getOffset();
+ readCommonLayoutTables(tableTag, to + slo, to + flo, to + llo);
+ initializeGPOS();
+ }
+
+ /**
+ * Initialize the (internal representation of the) GDEF table based on previously
+ * parsed state.
+ * @returns glyph definition table or null if insufficient or invalid state
+ */
+ private void initializeGDEF() {
+ List subtables;
+ if ((subtables = constructGDEFSubtables()) != null) {
+ if (subtables.size() > 0) {
+ if (table instanceof GlyphDefinitionTable)
+ ((GlyphDefinitionTable) table).initialize(subtables);
+ }
+ }
+ resetATState();
+ }
+
+ /**
+ * Initialize the (internal representation of the) GSUB table based on previously
+ * parsed state.
+ * @returns glyph substitution table or null if insufficient or invalid state
+ */
+ private void initializeGSUB() throws IOException {
+ Map lookups;
+ if ((lookups = constructLookups()) != null) {
+ List subtables;
+ if ((subtables = constructGSUBSubtables()) != null) {
+ if ((lookups.size() > 0) && (subtables.size() > 0)) {
+ if (table instanceof GlyphSubstitutionTable)
+ ((GlyphSubstitutionTable) table).initialize(otf.getGDEF(), lookups, subtables);
+ }
+ }
+ }
+ resetATState();
+ }
+
+ /**
+ * Initialize the (internal representation of the) GPOS table based on previously
+ * parsed state.
+ * @returns glyph positioning table or null if insufficient or invalid state
+ */
+ private void initializeGPOS() throws IOException {
+ Map lookups;
+ if ((lookups = constructLookups()) != null) {
+ List subtables;
+ if ((subtables = constructGPOSSubtables()) != null) {
+ if ((lookups.size() > 0) && (subtables.size() > 0)) {
+ if (table instanceof GlyphPositioningTable)
+ ((GlyphPositioningTable) table).initialize(otf.getGDEF(), lookups, subtables);
+ }
+ }
+ }
+ resetATState();
+ }
+
+ private void constructLookupsFeature(Map lookups, String st, String lt, String fid) {
+ Object[] fp = (Object[]) seFeatures.get(fid);
+ if (fp != null) {
+ assert fp.length == 2;
+ String ft = (String) fp[0]; // feature tag
+ List/*The GlyphClassMapping interface provides glyph identifier to class
+ * index mapping support.
Base class implementation of glyph class table.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public final class GlyphClassTable extends GlyphMappingTable implements GlyphClassMapping { + + /** empty mapping table */ + public static final int GLYPH_CLASS_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY; + + /** mapped mapping table */ + public static final int GLYPH_CLASS_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED; + + /** range based mapping table */ + public static final int GLYPH_CLASS_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE; + + /** empty mapping table */ + public static final int GLYPH_CLASS_TYPE_COVERAGE_SET = 3; + + private GlyphClassMapping cm; + + private GlyphClassTable(GlyphClassMapping cm) { + assert cm != null; + assert cm instanceof GlyphMappingTable; + this.cm = cm; + } + + /** {@inheritDoc} */ + public int getType() { + return ((GlyphMappingTable) cm) .getType(); + } + + /** {@inheritDoc} */ + public List getEntries() { + return ((GlyphMappingTable) cm) .getEntries(); + } + + /** {@inheritDoc} */ + public int getClassSize(int set) { + return cm.getClassSize(set); + } + + /** {@inheritDoc} */ + public int getClassIndex(int gid, int set) { + return cm.getClassIndex(gid, set); + } + + /** + * Create glyph class table. + * @param entries list of mapped or ranged class entries, or null or empty list + * @return a new covera table instance + */ + public static GlyphClassTable createClassTable(List entries) { + GlyphClassMapping cm; + if ((entries == null) || (entries.size() == 0)) { + cm = new EmptyClassTable(entries); + } else if (isMappedClass(entries)) { + cm = new MappedClassTable(entries); + } else if (isRangeClass(entries)) { + cm = new RangeClassTable(entries); + } else if (isCoverageSetClass(entries)) { + cm = new CoverageSetClassTable(entries); + } else { + cm = null; + } + assert cm != null : "unknown class type"; + return new GlyphClassTable(cm); + } + + private static boolean isMappedClass(List entries) { + if ((entries == null) || (entries.size() == 0)) { + return false; + } else { + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (!(o instanceof Integer)) { + return false; + } + } + return true; + } + } + + private static boolean isRangeClass(List entries) { + if ((entries == null) || (entries.size() == 0)) { + return false; + } else { + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (!(o instanceof MappingRange)) { + return false; + } + } + return true; + } + } + + private static boolean isCoverageSetClass(List entries) { + if ((entries == null) || (entries.size() == 0)) { + return false; + } else { + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (!(o instanceof GlyphCoverageTable)) { + return false; + } + } + return true; + } + } + + private static class EmptyClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping { + public EmptyClassTable(List entries) { + super(entries); + } + /** {@inheritDoc} */ + public int getClassSize(int set) { + return 0; + } + /** {@inheritDoc} */ + public int getClassIndex(int gid, int set) { + return -1; + } + } + + private static class MappedClassTable extends GlyphMappingTable.MappedMappingTable implements GlyphClassMapping { + private int firstGlyph; + private int[] gca; + private int gcMax = -1; + public MappedClassTable(List entries) { + populate(entries); + } + /** {@inheritDoc} */ + public List getEntries() { + List entries = new java.util.ArrayList(); + entries.add(Integer.valueOf(firstGlyph)); + if (gca != null) { + for (int i = 0, n = gca.length; i < n; i++) { + entries.add(Integer.valueOf(gca [ i ])); + } + } + return entries; + } + /** {@inheritDoc} */ + public int getMappingSize() { + return gcMax + 1; + } + /** {@inheritDoc} */ + public int getMappedIndex(int gid) { + int i = gid - firstGlyph; + if ((i >= 0) && (i < gca.length)) { + return gca [ i ]; + } else { + return -1; + } + } + /** {@inheritDoc} */ + public int getClassSize(int set) { + return getMappingSize(); + } + /** {@inheritDoc} */ + public int getClassIndex(int gid, int set) { + return getMappedIndex(gid); + } + private void populate(List entries) { + // obtain entries iterator + Iterator it = entries.iterator(); + // extract first glyph + int firstGlyph = 0; + if (it.hasNext()) { + Object o = it.next(); + if (o instanceof Integer) { + firstGlyph = ((Integer) o) .intValue(); + } else { + throw new AdvancedTypographicTableFormatException("illegal entry, first entry must be Integer denoting first glyph value, but is: " + o); + } + } + // extract glyph class array + int i = 0; + int n = entries.size() - 1; + int gcMax = -1; + int[] gca = new int [ n ]; + while (it.hasNext()) { + Object o = it.next(); + if (o instanceof Integer) { + int gc = ((Integer) o) .intValue(); + gca [ i++ ] = gc; + if (gc > gcMax) { + gcMax = gc; + } + } else { + throw new AdvancedTypographicTableFormatException("illegal mapping entry, must be Integer: " + o); + } + } + assert i == n; + assert this.gca == null; + this.firstGlyph = firstGlyph; + this.gca = gca; + this.gcMax = gcMax; + } + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("{ firstGlyph = " + firstGlyph + ", classes = {"); + for (int i = 0, n = gca.length; i < n; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(Integer.toString(gca [ i ])); + } + sb.append("} }"); + return sb.toString(); + } + } + + private static class RangeClassTable extends GlyphMappingTable.RangeMappingTable implements GlyphClassMapping { + public RangeClassTable(List entries) { + super(entries); + } + /** {@inheritDoc} */ + public int getMappedIndex(int gid, int s, int m) { + return m; + } + /** {@inheritDoc} */ + public int getClassSize(int set) { + return getMappingSize(); + } + /** {@inheritDoc} */ + public int getClassIndex(int gid, int set) { + return getMappedIndex(gid); + } + } + + private static class CoverageSetClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping { + public CoverageSetClassTable(List entries) { + throw new UnsupportedOperationException("coverage set class table not yet supported"); + } + /** {@inheritDoc} */ + public int getType() { + return GLYPH_CLASS_TYPE_COVERAGE_SET; + } + /** {@inheritDoc} */ + public int getClassSize(int set) { + return 0; + } + /** {@inheritDoc} */ + public int getClassIndex(int gid, int set) { + return -1; + } + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphCoverageMapping.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphCoverageMapping.java new file mode 100644 index 00000000000..b2133131d49 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphCoverageMapping.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +/** + *The GlyphCoverageMapping interface provides glyph identifier to coverage
+ * index mapping support.
.Base class implementation of glyph coverage table.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public final class GlyphCoverageTable extends GlyphMappingTable implements GlyphCoverageMapping { + + /* logging instance */ + private static final Log log = LogFactory.getLog(GlyphCoverageTable.class); + + /** empty mapping table */ + public static final int GLYPH_COVERAGE_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY; + + /** mapped mapping table */ + public static final int GLYPH_COVERAGE_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED; + + /** range based mapping table */ + public static final int GLYPH_COVERAGE_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE; + + private GlyphCoverageMapping cm; + + private GlyphCoverageTable(GlyphCoverageMapping cm) { + assert cm != null; + assert cm instanceof GlyphMappingTable; + this.cm = cm; + } + + /** {@inheritDoc} */ + public int getType() { + return ((GlyphMappingTable) cm) .getType(); + } + + /** {@inheritDoc} */ + public List getEntries() { + return ((GlyphMappingTable) cm) .getEntries(); + } + + /** {@inheritDoc} */ + public int getCoverageSize() { + return cm.getCoverageSize(); + } + + /** {@inheritDoc} */ + public int getCoverageIndex(int gid) { + return cm.getCoverageIndex(gid); + } + + /** + * Create glyph coverage table. + * @param entries list of mapped or ranged coverage entries, or null or empty list + * @return a new covera table instance + */ + public static GlyphCoverageTable createCoverageTable(List entries) { + GlyphCoverageMapping cm; + if ((entries == null) || (entries.size() == 0)) { + cm = new EmptyCoverageTable(entries); + } else if (isMappedCoverage(entries)) { + cm = new MappedCoverageTable(entries); + } else if (isRangeCoverage(entries)) { + cm = new RangeCoverageTable(entries); + } else { + cm = null; + } + assert cm != null : "unknown coverage type"; + return new GlyphCoverageTable(cm); + } + + private static boolean isMappedCoverage(List entries) { + if ((entries == null) || (entries.size() == 0)) { + return false; + } else { + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (!(o instanceof Integer)) { + return false; + } + } + return true; + } + } + + private static boolean isRangeCoverage(List entries) { + if ((entries == null) || (entries.size() == 0)) { + return false; + } else { + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (!(o instanceof MappingRange)) { + return false; + } + } + return true; + } + } + + private static class EmptyCoverageTable extends GlyphMappingTable.EmptyMappingTable implements GlyphCoverageMapping { + public EmptyCoverageTable(List entries) { + super(entries); + } + /** {@inheritDoc} */ + public int getCoverageSize() { + return 0; + } + /** {@inheritDoc} */ + public int getCoverageIndex(int gid) { + return -1; + } + } + + private static class MappedCoverageTable extends GlyphMappingTable.MappedMappingTable implements GlyphCoverageMapping { + private int[] map; + public MappedCoverageTable(List entries) { + populate(entries); + } + /** {@inheritDoc} */ + public List getEntries() { + List entries = new java.util.ArrayList(); + if (map != null) { + for (int i = 0, n = map.length; i < n; i++) { + entries.add(Integer.valueOf(map [ i ])); + } + } + return entries; + } + /** {@inheritDoc} */ + public int getMappingSize() { + return (map != null) ? map.length : 0; + } + public int getMappedIndex(int gid) { + int i; + if ((i = Arrays.binarySearch(map, gid)) >= 0) { + return i; + } else { + return -1; + } + } + /** {@inheritDoc} */ + public int getCoverageSize() { + return getMappingSize(); + } + /** {@inheritDoc} */ + public int getCoverageIndex(int gid) { + return getMappedIndex(gid); + } + private void populate(List entries) { + int i = 0; + int skipped = 0; + int n = entries.size(); + int gidMax = -1; + int[] map = new int [ n ]; + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (o instanceof Integer) { + int gid = ((Integer) o) .intValue(); + if ((gid >= 0) && (gid < 65536)) { + if (gid > gidMax) { + map [ i++ ] = gidMax = gid; + } else { + log.info("ignoring out of order or duplicate glyph index: " + gid); + skipped++; + } + } else { + throw new AdvancedTypographicTableFormatException("illegal glyph index: " + gid); + } + } else { + throw new AdvancedTypographicTableFormatException("illegal coverage entry, must be Integer: " + o); + } + } + assert (i + skipped) == n; + assert this.map == null; + this.map = map; + } + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('{'); + for (int i = 0, n = map.length; i < n; i++) { + if (i > 0) { + sb.append(','); + } + sb.append(Integer.toString(map [ i ])); + } + sb.append('}'); + return sb.toString(); + } + } + + private static class RangeCoverageTable extends GlyphMappingTable.RangeMappingTable implements GlyphCoverageMapping { + public RangeCoverageTable(List entries) { + super(entries); + } + /** {@inheritDoc} */ + public int getMappedIndex(int gid, int s, int m) { + return m + gid - s; + } + /** {@inheritDoc} */ + public int getCoverageSize() { + return getMappingSize(); + } + /** {@inheritDoc} */ + public int getCoverageIndex(int gid) { + return getMappedIndex(gid); + } + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinition.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinition.java new file mode 100644 index 00000000000..19e863e22d0 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinition.java @@ -0,0 +1,35 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +/** + *The GlyphDefinition interface is a marker interface implemented by a glyph definition
+ * subtable.
The GlyphDefinitionSubtable implements an abstract base of a glyph definition subtable,
+ * providing a default implementation of the GlyphDefinition interface.
GlyphDefinitionSubtable.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param mapping subtable coverage table
+ */
+ protected GlyphDefinitionSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping) {
+ super(id, sequence, flags, format, mapping);
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return AdvancedTypographicTable.GLYPH_TABLE_TYPE_DEFINITION;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphDefinitionTable.getLookupTypeName(getType());
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasDefinition(int gi) {
+ GlyphCoverageMapping cvm;
+ if ((cvm = getCoverage()) != null) {
+ if (cvm.getCoverageIndex(gi) >= 0) {
+ return true;
+ }
+ }
+ GlyphClassMapping clm;
+ if ((clm = getClasses()) != null) {
+ if (clm.getClassIndex(gi, 0) >= 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinitionTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinitionTable.java
new file mode 100644
index 00000000000..2e31707f689
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphDefinitionTable.java
@@ -0,0 +1,466 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.ttf.OpenTypeFont;
+import org.apache.fontbox.ttf.TTFDataStream;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.advanced.scripts.ScriptProcessor;
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+
+/**
+ * The GlyphDefinitionTable class is a glyph table that implements
+ * glyph definition functionality according to the OpenType GDEF table.
Adapted from the Apache FOP Project.
+ * + * @author Glenn Adams + */ +public class GlyphDefinitionTable extends AdvancedTypographicTable { + + /** logging instance */ + private static final Log log = LogFactory.getLog(GlyphDefinitionTable.class); + + /** tag that identifies this table type */ + public static final String TAG = "GDEF"; + + /** glyph class subtable type */ + public static final int GDEF_LOOKUP_TYPE_GLYPH_CLASS = 1; + /** attachment point subtable type */ + public static final int GDEF_LOOKUP_TYPE_ATTACHMENT_POINT = 2; + /** ligature caret subtable type */ + public static final int GDEF_LOOKUP_TYPE_LIGATURE_CARET = 3; + /** mark attachment subtable type */ + public static final int GDEF_LOOKUP_TYPE_MARK_ATTACHMENT = 4; + + /** pre-defined glyph class - base glyph */ + public static final int GLYPH_CLASS_BASE = 1; + /** pre-defined glyph class - ligature glyph */ + public static final int GLYPH_CLASS_LIGATURE = 2; + /** pre-defined glyph class - mark glyph */ + public static final int GLYPH_CLASS_MARK = 3; + /** pre-defined glyph class - component glyph */ + public static final int GLYPH_CLASS_COMPONENT = 4; + + /** singleton glyph class table */ + private GlyphClassSubtable gct; + /** singleton attachment point table */ + // private AttachmentPointSubtable apt; // NOT YET USED + /** singleton ligature caret table */ + // private LigatureCaretSubtable lct; // NOT YET USED + /** singleton mark attachment table */ + private MarkAttachmentSubtable mat; + + public GlyphDefinitionTable(OpenTypeFont otf) { + super(otf, null, new java.util.HashMap(0)); + } + + /** + * Initialize aGlyphDefinitionTable object using the specified subtables.
+ * @param subtables a list of identified subtables
+ */
+ public GlyphDefinitionTable initialize(List subtables) {
+ if ((subtables == null) || (subtables.size() == 0)) {
+ throw new AdvancedTypographicTableFormatException("subtables must be non-empty");
+ } else {
+ for (Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if (o instanceof GlyphDefinitionSubtable) {
+ addSubtable((GlyphSubtable) o);
+ } else {
+ throw new AdvancedTypographicTableFormatException("subtable must be a glyph definition subtable");
+ }
+ }
+ freezeSubtables();
+ return this;
+ }
+ }
+
+ @Override
+ protected void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
+ {
+ if (ttf instanceof OpenTypeFont) {
+ new AdvancedTypographicTableReader((OpenTypeFont) ttf, this, data).read();
+ this.initialized = true;
+ }
+ }
+
+ /**
+ * Reorder combining marks in glyph sequence so that they precede (within the sequence) the base
+ * character to which they are applied. N.B. In the case of LTR segments, marks are not reordered by this,
+ * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede
+ * their base glyph.
+ * @param gs an input glyph sequence
+ * @param widths associated advance widths (also reordered)
+ * @param gpa associated glyph position adjustments (also reordered)
+ * @param script a script identifier
+ * @param language a language identifier
+ * @return the reordered (output) glyph sequence
+ */
+ public GlyphSequence reorderCombiningMarks(GlyphSequence gs, int[] widths, int[][] gpa, String script, String language, Object[][] features) {
+ ScriptProcessor sp = ScriptProcessor.getInstance(script);
+ return sp.reorderCombiningMarks(this, gs, widths, gpa, script, language, features);
+ }
+
+ /** {@inheritDoc} */
+ protected void addSubtable(GlyphSubtable subtable) {
+ if (subtable instanceof GlyphClassSubtable) {
+ this.gct = (GlyphClassSubtable) subtable;
+ } else if (subtable instanceof AttachmentPointSubtable) {
+ // TODO - not yet used
+ // this.apt = (AttachmentPointSubtable) subtable;
+ } else if (subtable instanceof LigatureCaretSubtable) {
+ // TODO - not yet used
+ // this.lct = (LigatureCaretSubtable) subtable;
+ } else if (subtable instanceof MarkAttachmentSubtable) {
+ this.mat = (MarkAttachmentSubtable) subtable;
+ } else {
+ throw new UnsupportedOperationException("unsupported glyph definition subtable type: " + subtable);
+ }
+ }
+
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public boolean isGlyphClass(int gid, int gc) {
+ if (gct != null) {
+ return gct.isGlyphClass(gid, gc);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine glyph class.
+ * @param gid a glyph identifier (index)
+ * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ */
+ public int getGlyphClass(int gid) {
+ if (gct != null) {
+ return gct.getGlyphClass(gid);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Determine if glyph belongs to (font specific) mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @param mac a (font specific) mark attachment class
+ * @return true if glyph belongs to specified mark attachment class
+ */
+ public boolean isMarkAttachClass(int gid, int mac) {
+ if (mat != null) {
+ return mat.isMarkAttachClass(gid, mac);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @return a non-negative mark attachment class, or -1 if no class defined
+ */
+ public int getMarkAttachClass(int gid) {
+ if (mat != null) {
+ return mat.getMarkAttachClass(gid);
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName(String name) {
+ int t;
+ String s = name.toLowerCase();
+ if ("glyphclass".equals(s)) {
+ t = GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ } else if ("attachmentpoint".equals(s)) {
+ t = GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ } else if ("ligaturecaret".equals(s)) {
+ t = GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ } else if ("markattachment".equals(s)) {
+ t = GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName(int type) {
+ String tn = null;
+ switch (type) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ tn = "glyphclass";
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ tn = "attachmentpoint";
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ tn = "ligaturecaret";
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ tn = "markattachment";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a definition subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags (must be zero)
+ * @param format subtable format
+ * @param mapping subtable mapping table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ GlyphSubtable st = null;
+ switch (type) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ st = GlyphClassSubtable.create(id, sequence, flags, format, mapping, entries);
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ st = AttachmentPointSubtable.create(id, sequence, flags, format, mapping, entries);
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ st = LigatureCaretSubtable.create(id, sequence, flags, format, mapping, entries);
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ st = MarkAttachmentSubtable.create(id, sequence, flags, format, mapping, entries);
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ private abstract static class GlyphClassSubtable extends GlyphDefinitionSubtable {
+ GlyphClassSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ }
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public abstract boolean isGlyphClass(int gid, int gc);
+ /**
+ * Determine glyph class.
+ * @param gid a glyph identifier (index)
+ * @return a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ */
+ public abstract int getGlyphClass(int gid);
+ static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ if (format == 1) {
+ return new GlyphClassSubtableFormat1(id, sequence, flags, format, mapping, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class GlyphClassSubtableFormat1 extends GlyphClassSubtable {
+ GlyphClassSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping, entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof GlyphClassSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean isGlyphClass(int gid, int gc) {
+ GlyphClassMapping cm = getClasses();
+ if (cm != null) {
+ return cm.getClassIndex(gid, 0) == gc;
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getGlyphClass(int gid) {
+ GlyphClassMapping cm = getClasses();
+ if (cm != null) {
+ return cm.getClassIndex(gid, 0);
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ private abstract static class AttachmentPointSubtable extends GlyphDefinitionSubtable {
+ AttachmentPointSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ }
+ static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ if (format == 1) {
+ return new AttachmentPointSubtableFormat1(id, sequence, flags, format, mapping, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class AttachmentPointSubtableFormat1 extends AttachmentPointSubtable {
+ AttachmentPointSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping, entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof AttachmentPointSubtable;
+ }
+ }
+
+ private abstract static class LigatureCaretSubtable extends GlyphDefinitionSubtable {
+ LigatureCaretSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ }
+ static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ if (format == 1) {
+ return new LigatureCaretSubtableFormat1(id, sequence, flags, format, mapping, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class LigatureCaretSubtableFormat1 extends LigatureCaretSubtable {
+ LigatureCaretSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping, entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof LigatureCaretSubtable;
+ }
+ }
+
+ private abstract static class MarkAttachmentSubtable extends GlyphDefinitionSubtable {
+ MarkAttachmentSubtable(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ }
+ /**
+ * Determine if glyph belongs to (font specific) mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @param mac a (font specific) mark attachment class
+ * @return true if glyph belongs to specified mark attachment class
+ */
+ public abstract boolean isMarkAttachClass(int gid, int mac);
+ /**
+ * Determine mark attachment class.
+ * @param gid a glyph identifier (index)
+ * @return a non-negative mark attachment class, or -1 if no class defined
+ */
+ public abstract int getMarkAttachClass(int gid);
+ static GlyphDefinitionSubtable create(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ if (format == 1) {
+ return new MarkAttachmentSubtableFormat1(id, sequence, flags, format, mapping, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkAttachmentSubtableFormat1 extends MarkAttachmentSubtable {
+ MarkAttachmentSubtableFormat1(String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries) {
+ super(id, sequence, flags, format, mapping, entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof MarkAttachmentSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean isMarkAttachClass(int gid, int mac) {
+ GlyphClassMapping cm = getClasses();
+ if (cm != null) {
+ return cm.getClassIndex(gid, 0) == mac;
+ } else {
+ return false;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getMarkAttachClass(int gid) {
+ GlyphClassMapping cm = getClasses();
+ if (cm != null) {
+ return cm.getClassIndex(gid, 0);
+ } else {
+ return -1;
+ }
+ }
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphMappingTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphMappingTable.java
new file mode 100644
index 00000000000..f12f3a5b263
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphMappingTable.java
@@ -0,0 +1,322 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Base class implementation of glyph mapping table. This base + * class maps glyph indices to arbitrary integers (mappping indices), and + * is used to implement both glyph coverage and glyph class maps.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public class GlyphMappingTable { + + /** empty mapping table */ + public static final int GLYPH_MAPPING_TYPE_EMPTY = 0; + + /** mapped mapping table */ + public static final int GLYPH_MAPPING_TYPE_MAPPED = 1; + + /** range based mapping table */ + public static final int GLYPH_MAPPING_TYPE_RANGE = 2; + + /** + * Obtain mapping type. + * @return mapping format type + */ + public int getType() { + return -1; + } + + /** + * Obtain mapping entries. + * @return list of mapping entries + */ + public List getEntries() { + return null; + } + + /** + * Obtain size of mapping table, i.e., ciMax + 1, where ciMax is the maximum + * mapping index. + * @return size of mapping table + */ + public int getMappingSize() { + return 0; + } + + /** + * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of + * the mapping table. + * @param gid glyph identifier (code) + * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table + */ + public int getMappedIndex(int gid) { + return -1; + } + + /** empty mapping table base class */ + protected static class EmptyMappingTable extends GlyphMappingTable { + /** + * Construct empty mapping table. + */ + public EmptyMappingTable() { + this ((List) null); + } + /** + * Construct empty mapping table with entries (ignored). + * @param entries list of entries (ignored) + */ + public EmptyMappingTable(List entries) { + } + /** {@inheritDoc} */ + public int getType() { + return GLYPH_MAPPING_TYPE_EMPTY; + } + /** {@inheritDoc} */ + public List getEntries() { + return new java.util.ArrayList(); + } + /** {@inheritDoc} */ + public int getMappingSize() { + return 0; + } + /** {@inheritDoc} */ + public int getMappedIndex(int gid) { + return -1; + } + } + + /** mapped mapping table base class */ + protected static class MappedMappingTable extends GlyphMappingTable { + /** + * Construct mapped mapping table. + */ + public MappedMappingTable() { + } + /** {@inheritDoc} */ + public int getType() { + return GLYPH_MAPPING_TYPE_MAPPED; + } + } + + /** range mapping table base class */ + protected abstract static class RangeMappingTable extends GlyphMappingTable { + private int[] sa; // array of range (inclusive) starts + private int[] ea; // array of range (inclusive) ends + private int[] ma; // array of range mapped values + private int miMax = -1; + /** + * Construct range mapping table. + * @param entries of mapping ranges + */ + public RangeMappingTable(List entries) { + populate(entries); + } + /** {@inheritDoc} */ + public int getType() { + return GLYPH_MAPPING_TYPE_RANGE; + } + /** {@inheritDoc} */ + public List getEntries() { + List entries = new java.util.ArrayList(); + if (sa != null) { + for (int i = 0, n = sa.length; i < n; i++) { + entries.add(new MappingRange(sa [ i ], ea [ i ], ma [ i ])); + } + } + return entries; + } + /** {@inheritDoc} */ + public int getMappingSize() { + return miMax + 1; + } + /** {@inheritDoc} */ + public int getMappedIndex(int gid) { + int i; + int mi; + if ((i = Arrays.binarySearch(sa, gid)) >= 0) { + mi = getMappedIndex(gid, sa [ i ], ma [ i ]); // matches start of (some) range + } else if ((i = -(i + 1)) == 0) { + mi = -1; // precedes first range + } else if (gid > ea [ --i ]) { + mi = -1; // follows preceding (or last) range + } else { + mi = getMappedIndex(gid, sa [ i ], ma [ i ]); // intersects (some) range + } + return mi; + } + /** + * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of + * the mapping table. + * @param gid glyph identifier (code) + * @param s start of range + * @param m mapping value + * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table + */ + public abstract int getMappedIndex(int gid, int s, int m); + private void populate(List entries) { + int i = 0; + int n = entries.size(); + int gidMax = -1; + int miMax = -1; + int[] sa = new int [ n ]; + int[] ea = new int [ n ]; + int[] ma = new int [ n ]; + for (Iterator it = entries.iterator(); it.hasNext();) { + Object o = it.next(); + if (o instanceof MappingRange) { + MappingRange r = (MappingRange) o; + int gs = r.getStart(); + int ge = r.getEnd(); + int mi = r.getIndex(); + if ((gs < 0) || (gs > 65535)) { + throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: bad start index"); + } else if ((ge < 0) || (ge > 65535)) { + throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: bad end index"); + } else if (gs > ge) { + throw new AdvancedTypographicTableFormatException("illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index"); + } else if (gs < gidMax) { + throw new AdvancedTypographicTableFormatException("out of order glyph range: [" + gs + "," + ge + "]"); + } else if (mi < 0) { + throw new AdvancedTypographicTableFormatException("illegal mapping index: " + mi); + } else { + int miLast; + sa [ i ] = gs; + ea [ i ] = gidMax = ge; + ma [ i ] = mi; + if ((miLast = mi + (ge - gs)) > miMax) { + miMax = miLast; + } + i++; + } + } else { + throw new AdvancedTypographicTableFormatException("illegal mapping entry, must be Integer: " + o); + } + } + assert i == n; + assert this.sa == null; + assert this.ea == null; + assert this.ma == null; + this.sa = sa; + this.ea = ea; + this.ma = ma; + this.miMax = miMax; + } + /** {@inheritDoc} */ + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append('{'); + for (int i = 0, n = sa.length; i < n; i++) { + if (i > 0) { + sb.append(','); + } + sb.append('['); + sb.append(Integer.toString(sa [ i ])); + sb.append(Integer.toString(ea [ i ])); + sb.append("]:"); + sb.append(Integer.toString(ma [ i ])); + } + sb.append('}'); + return sb.toString(); + } + } + + /** + * TheMappingRange class encapsulates a glyph [start,end] range and
+ * a mapping index.
+ */
+ public static class MappingRange {
+
+ private final int gidStart; // first glyph in range (inclusive)
+ private final int gidEnd; // last glyph in range (inclusive)
+ private final int index; // mapping index;
+
+ /**
+ * Instantiate a mapping range.
+ */
+ public MappingRange() {
+ this (0, 0, 0);
+ }
+
+ /**
+ * Instantiate a specific mapping range.
+ * @param gidStart start of range
+ * @param gidEnd end of range
+ * @param index mapping index
+ */
+ public MappingRange(int gidStart, int gidEnd, int index) {
+ if ((gidStart < 0) || (gidEnd < 0) || (index < 0)) {
+ throw new AdvancedTypographicTableFormatException();
+ } else if (gidStart > gidEnd) {
+ throw new AdvancedTypographicTableFormatException();
+ } else {
+ this.gidStart = gidStart;
+ this.gidEnd = gidEnd;
+ this.index = index;
+ }
+ }
+
+ /** @return start of range */
+ public int getStart() {
+ return gidStart;
+ }
+
+ /** @return end of range */
+ public int getEnd() {
+ return gidEnd;
+ }
+
+ /** @return mapping index */
+ public int getIndex() {
+ return index;
+ }
+
+ /** @return interval as a pair of integers */
+ public int[] getInterval() {
+ return new int[] { gidStart, gidEnd };
+ }
+
+ /**
+ * Obtain interval, filled into first two elements of specified array, or returning new array.
+ * @param interval an array of length two or greater or null
+ * @return interval as a pair of integers, filled into specified array
+ */
+ public int[] getInterval(int[] interval) {
+ if ((interval == null) || (interval.length != 2)) {
+ throw new IllegalArgumentException();
+ } else {
+ interval[0] = gidStart;
+ interval[1] = gidEnd;
+ }
+ return interval;
+ }
+
+ /** @return length of interval */
+ public int getLength() {
+ return gidStart - gidEnd;
+ }
+
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioning.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioning.java
new file mode 100644
index 00000000000..c279ce5875c
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioning.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+/**
+ * The GlyphPositioning interface is implemented by a glyph positioning subtable
+ * that supports the determination of glyph positioning information based on script and
+ * language of the corresponding character content.
The GlyphPositioningState implements an state object used during glyph positioning
+ * processing.
nig input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by nig are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply(AdvancedTypographicTable.RuleLookup[] lookups, int nig) {
+ if ((lookups != null) && (lookups.length > 0)) {
+ // apply each rule lookup to extracted input glyph array
+ for (int i = 0, n = lookups.length; i < n; i++) {
+ AdvancedTypographicTable.RuleLookup l = lookups [ i ];
+ if (l != null) {
+ AdvancedTypographicTable.LookupTable lt = l.getLookup();
+ if (lt != null) {
+ // perform positioning on a copy of previous state
+ GlyphPositioningState ps = new GlyphPositioningState(this);
+ // apply lookup table positioning
+ if (lt.position(ps, l.getSequenceIndex())) {
+ setAdjusted(true);
+ }
+ }
+ }
+ }
+ consume(nig);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph.
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ }
+
+ /**
+ * Set adjusted state, used to record effect of non-zero adjustment.
+ * @param adjusted true if to set adjusted state, otherwise false to
+ * clear adjusted state
+ */
+ public void setAdjusted(boolean adjusted) {
+ this.adjusted = adjusted;
+ }
+
+ /**
+ * Get adjusted state.
+ * @return adjusted true if some non-zero adjustment occurred and
+ * was recorded by {@link #setAdjusted}; otherwise, false.
+ */
+ public boolean getAdjusted() {
+ return adjusted;
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningSubtable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningSubtable.java
new file mode 100644
index 00000000000..28c393ff28d
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningSubtable.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+import org.apache.fontbox.ttf.advanced.util.ScriptContextTester;
+
+/**
+ * The GlyphPositioningSubtable implements an abstract base of a glyph subtable,
+ * providing a default implementation of the GlyphPositioning interface.
GlyphPositioningSubtable.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ */
+ protected GlyphPositioningSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage) {
+ super(id, sequence, flags, format, coverage);
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return AdvancedTypographicTable.GLYPH_TABLE_TYPE_POSITIONING;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphPositioningTable.getLookupTypeName(getType());
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof GlyphPositioningSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean position(GlyphPositioningState ps) {
+ return false;
+ }
+
+ /**
+ * Apply positioning using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (no adjustments and advance).
+ * If sequenceIndex is non-negative, then apply subtables only when current position
+ * matches sequenceIndex in relation to the starting position. Furthermore, upon
+ * successful application at sequenceIndex, then discontinue processing the remaining
+ * @param ps positioning state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return true if a non-zero adjustment occurred
+ */
+ public static final boolean position(GlyphPositioningState ps, GlyphPositioningSubtable[] sta, int sequenceIndex) {
+ int sequenceStart = ps.getPosition();
+ boolean appliedOneShot = false;
+ while (ps.hasNext()) {
+ boolean applied = false;
+ if (!appliedOneShot && ps.maybeApplicable()) {
+ for (int i = 0, n = sta.length; !applied && (i < n); i++) {
+ if (sequenceIndex < 0) {
+ applied = ps.apply(sta [ i ]);
+ } else if (ps.getPosition() == (sequenceStart + sequenceIndex)) {
+ applied = ps.apply(sta [ i ]);
+ if (applied) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if (!applied || !ps.didConsume()) {
+ ps.applyDefault();
+ }
+ ps.next();
+ }
+ return ps.getAdjusted();
+ }
+
+ /**
+ * Apply positioning.
+ * @param gs input glyph sequence
+ * @param script tag
+ * @param language tag
+ * @param feature tag
+ * @param fontSize the font size
+ * @param sta subtable array
+ * @param widths array
+ * @param adjustments array (receives output adjustments)
+ * @param sct script context tester
+ * @return true if a non-zero adjustment occurred
+ */
+ public static final boolean position(GlyphSequence gs, String script, String language, String feature, int fontSize, GlyphPositioningSubtable[] sta, int[] widths, int[][] adjustments, ScriptContextTester sct) {
+ synchronized (STATE) {
+ return position(STATE.reset(gs, script, language, feature, fontSize, widths, adjustments, sct), sta, -1);
+ }
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningTable.java
new file mode 100644
index 00000000000..1a121bc1c31
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphPositioningTable.java
@@ -0,0 +1,2313 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.ttf.OpenTypeFont;
+import org.apache.fontbox.ttf.TTFDataStream;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.advanced.scripts.ScriptProcessor;
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+import org.apache.fontbox.ttf.advanced.util.GlyphTester;
+
+/**
+ * The GlyphPositioningTable class is a glyph table that implements
+ * GlyphPositioning functionality.
Adapted from the Apache FOP Project.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public class GlyphPositioningTable extends AdvancedTypographicTable { + + /** logging instance */ + private static final Log log = LogFactory.getLog(GlyphPositioningTable.class); + + /** tag that identifies this table type */ + public static final String TAG = "GPOS"; + + /** single positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_SINGLE = 1; + /** multiple positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_PAIR = 2; + /** cursive positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_CURSIVE = 3; + /** mark to base positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_MARK_TO_BASE = 4; + /** mark to ligature positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE = 5; + /** mark to mark positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_MARK_TO_MARK = 6; + /** contextual positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_CONTEXTUAL = 7; + /** chained contextual positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 8; + /** extension positioning subtable type */ + public static final int GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9; + + public GlyphPositioningTable(OpenTypeFont otf) { + super(otf, null, new java.util.HashMap(0)); + } + + /** + * Initialize thisGlyphPositioningTable object using the specified lookups
+ * and subtables.
+ * @param gdef glyph definition table that applies
+ * @param lookups a map of lookup specifications to subtable identifier strings
+ * @param subtables a list of identified subtables
+ */
+ public GlyphPositioningTable initialize(GlyphDefinitionTable gdef, Map lookups, List subtables) {
+ initialize(lookups);
+ if ((subtables == null) || (subtables.size() == 0)) {
+ throw new AdvancedTypographicTableFormatException("subtables must be non-empty");
+ } else {
+ for (Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if (o instanceof GlyphPositioningSubtable) {
+ addSubtable((GlyphSubtable) o);
+ } else {
+ throw new AdvancedTypographicTableFormatException("subtable must be a glyph positioning subtable");
+ }
+ }
+ freezeSubtables();
+ return this;
+ }
+ }
+
+ @Override
+ protected void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
+ {
+ if (ttf instanceof OpenTypeFont) {
+ new AdvancedTypographicTableReader((OpenTypeFont) ttf, this, data).read();
+ this.initialized = true;
+ }
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName(String name) {
+ int t;
+ String s = name.toLowerCase();
+ if ("single".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_SINGLE;
+ } else if ("pair".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_PAIR;
+ } else if ("cursive".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_CURSIVE;
+ } else if ("marktobase".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+ } else if ("marktoligature".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+ } else if ("marktomark".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+ } else if ("contextual".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ } else if ("chainedcontextual".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ } else if ("extensionpositioning".equals(s)) {
+ t = GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName(int type) {
+ String tn;
+ switch (type) {
+ case GPOS_LOOKUP_TYPE_SINGLE:
+ tn = "single";
+ break;
+ case GPOS_LOOKUP_TYPE_PAIR:
+ tn = "pair";
+ break;
+ case GPOS_LOOKUP_TYPE_CURSIVE:
+ tn = "cursive";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+ tn = "marktobase";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+ tn = "marktoligature";
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+ tn = "marktomark";
+ break;
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ tn = "contextual";
+ break;
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ tn = "chainedcontextual";
+ break;
+ case GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
+ tn = "extensionpositioning";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a positioning subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ GlyphSubtable st = null;
+ switch (type) {
+ case GPOS_LOOKUP_TYPE_SINGLE:
+ st = SingleSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_PAIR:
+ st = PairSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_CURSIVE:
+ st = CursiveSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+ st = MarkToBaseSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+ st = MarkToLigatureSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+ st = MarkToMarkSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ st = ContextualSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ st = ChainedContextualSubtable.create(id, sequence, flags, format, coverage, entries);
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ /**
+ * Create a positioning subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage list of coverage table entries
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable(int type, String id, int sequence, int flags, int format, List coverage, List entries) {
+ return createSubtable(type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable(coverage), entries);
+ }
+
+ /**
+ * Perform positioning processing using all matching lookups.
+ * @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
+ * @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
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position(GlyphSequence gs, String script, String language, Object[][] features, int fontSize, int[] widths, int[][] adjustments) {
+ Map/*ci1 and entry anchor for second
+ * glyph with coverage index ci2.
+ * @param ci1 coverage index of first glyph (may be negative)
+ * @param ci2 coverage index of second glyph (may be negative)
+ * @return array of two anchors or null if either coverage index is negative or corresponding anchor is
+ * missing, where the first entry is the exit anchor of the first glyph and the second entry is the
+ * entry anchor of the second glyph
+ */
+ public abstract Anchor[] getExitEntryAnchors(int ci1, int ci2);
+ static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ if (format == 1) {
+ return new CursiveSubtableFormat1(id, sequence, flags, format, coverage, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class CursiveSubtableFormat1 extends CursiveSubtable {
+ private Anchor[] aa; // anchor array, where even entries are entry anchors, and odd entries are exit anchors
+ CursiveSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage, entries);
+ populate(entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if (aa != null) {
+ List entries = new ArrayList(1);
+ entries.add(aa);
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor[] getExitEntryAnchors(int ci1, int ci2) {
+ if ((ci1 >= 0) && (ci2 >= 0)) {
+ int ai1 = (ci1 * 2) + 1; // ci1 denotes glyph with exit anchor
+ int ai2 = (ci2 * 2) + 0; // ci2 denotes glyph with entry anchor
+ if ((aa != null) && (ai1 < aa.length) && (ai2 < aa.length)) {
+ Anchor exa = aa [ ai1 ];
+ Anchor ena = aa [ ai2 ];
+ if ((exa != null) && (ena != null)) {
+ return new Anchor[] { exa, ena };
+ }
+ }
+ }
+ return null;
+ }
+ private void populate(List entries) {
+ if (entries == null) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
+ } else if (entries.size() != 1) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 1 entry");
+ } else {
+ Object o;
+ if (((o = entries.get(0)) == null) || !(o instanceof Anchor[])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, first (and only) entry must be a Anchor[], but is: " + ((o != null) ? o.getClass() : null));
+ } else if ((((Anchor[]) o) .length % 2) != 0) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, Anchor[] array must have an even number of entries, but has: " + ((Anchor[]) o) .length);
+ } else {
+ aa = (Anchor[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToBaseSubtable extends GlyphPositioningSubtable {
+ MarkToBaseSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof MarkToBaseSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position(GlyphPositioningState ps) {
+ boolean applied = false;
+ int giMark = ps.getGlyph();
+ int ciMark;
+ if ((ciMark = getCoverageIndex(giMark)) >= 0) {
+ MarkAnchor ma = getMarkAnchor(ciMark, giMark);
+ if (ma != null) {
+ for (int i = 0, n = ps.getPosition(); i < n; i++) {
+ int gi = ps.getGlyph(-(i + 1));
+ if (ps.isMark(gi)) {
+ continue;
+ } else {
+ Anchor a = getBaseAnchor(gi, ma.getMarkClass());
+ if (a != null) {
+ Value v = a.getAlignmentAdjustment(ma);
+ // start experimental fix for END OF AYAH in Lateef/Scheherazade
+ int[] aa = ps.getAdjustment();
+ if (aa[2] == 0) {
+ v.adjust(0, 0, -ps.getWidth(giMark), 0);
+ }
+ // end experimental fix for END OF AYAH in Lateef/Scheherazade
+ if (ps.adjust(v)) {
+ ps.setAdjusted(true);
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ break;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor(int ciMark, int giMark);
+ /**
+ * Obtain anchor associated with base glyph index and mark class.
+ * @param giBase input glyph index of base glyph
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getBaseAnchor(int giBase, int markClass);
+ static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ if (format == 1) {
+ return new MarkToBaseSubtableFormat1(id, sequence, flags, format, coverage, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToBaseSubtableFormat1 extends MarkToBaseSubtable {
+ private GlyphCoverageTable bct; // base coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][] bam; // base anchor matrix, ordered by base coverage index, then by mark class
+ MarkToBaseSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage, entries);
+ populate(entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ((bct != null) && (maa != null) && (nmc > 0) && (bam != null)) {
+ List entries = new ArrayList(4);
+ entries.add(bct);
+ entries.add(Integer.valueOf(nmc));
+ entries.add(maa);
+ entries.add(bam);
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor(int ciMark, int giMark) {
+ if ((maa != null) && (ciMark < maa.length)) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getBaseAnchor(int giBase, int markClass) {
+ int ciBase;
+ if ((bct != null) && ((ciBase = bct.getCoverageIndex(giBase)) >= 0)) {
+ if ((bam != null) && (ciBase < bam.length)) {
+ Anchor[] ba = bam [ ciBase ];
+ if ((ba != null) && (markClass < ba.length)) {
+ return ba [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate(List entries) {
+ if (entries == null) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
+ } else if (entries.size() != 4) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries");
+ } else {
+ Object o;
+ if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ bct = (GlyphCoverageTable) o;
+ }
+ if (((o = entries.get(1)) == null) || !(o instanceof Integer)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ bam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToLigatureSubtable extends GlyphPositioningSubtable {
+ MarkToLigatureSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof MarkToLigatureSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position(GlyphPositioningState ps) {
+ boolean applied = false;
+ int giMark = ps.getGlyph();
+ int ciMark;
+ if ((ciMark = getCoverageIndex(giMark)) >= 0) {
+ MarkAnchor ma = getMarkAnchor(ciMark, giMark);
+ int mxc = getMaxComponentCount();
+ if (ma != null) {
+ for (int i = 0, n = ps.getPosition(); i < n; i++) {
+ int gi = ps.getGlyph(-(i + 1));
+ if (ps.isMark(gi)) {
+ continue;
+ } else {
+ Anchor a = getLigatureAnchor(gi, mxc, i, ma.getMarkClass());
+ if (a != null) {
+ if (ps.adjust(a.getAlignmentAdjustment(ma))) {
+ ps.setAdjusted(true);
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ break;
+ }
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor(int ciMark, int giMark);
+ /**
+ * Obtain maximum component count.
+ * @return maximum component count (>=0)
+ */
+ public abstract int getMaxComponentCount();
+ /**
+ * Obtain anchor associated with ligature glyph index and mark class.
+ * @param giLig input glyph index of ligature glyph
+ * @param maxComponents maximum component count
+ * @param component component number (0...maxComponents-1)
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getLigatureAnchor(int giLig, int maxComponents, int component, int markClass);
+ static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ if (format == 1) {
+ return new MarkToLigatureSubtableFormat1(id, sequence, flags, format, coverage, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToLigatureSubtableFormat1 extends MarkToLigatureSubtable {
+ private GlyphCoverageTable lct; // ligature coverage table
+ private int nmc; // mark class count
+ private int mxc; // maximum ligature component count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][][] lam; // ligature anchor matrix, ordered by ligature coverage index, then ligature component, then mark class
+ MarkToLigatureSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage, entries);
+ populate(entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if (lam != null) {
+ List entries = new ArrayList(5);
+ entries.add(lct);
+ entries.add(Integer.valueOf(nmc));
+ entries.add(Integer.valueOf(mxc));
+ entries.add(maa);
+ entries.add(lam);
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor(int ciMark, int giMark) {
+ if ((maa != null) && (ciMark < maa.length)) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getMaxComponentCount() {
+ return mxc;
+ }
+ /** {@inheritDoc} */
+ public Anchor getLigatureAnchor(int giLig, int maxComponents, int component, int markClass) {
+ int ciLig;
+ if ((lct != null) && ((ciLig = lct.getCoverageIndex(giLig)) >= 0)) {
+ if ((lam != null) && (ciLig < lam.length)) {
+ Anchor[][] lcm = lam [ ciLig ];
+ if (component < maxComponents) {
+ Anchor[] la = lcm [ component ];
+ if ((la != null) && (markClass < la.length)) {
+ return la [ markClass ];
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate(List entries) {
+ if (entries == null) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
+ } else if (entries.size() != 5) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 5 entries");
+ } else {
+ Object o;
+ if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ lct = (GlyphCoverageTable) o;
+ }
+ if (((o = entries.get(1)) == null) || !(o instanceof Integer)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if (((o = entries.get(2)) == null) || !(o instanceof Integer)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ mxc = ((Integer)(o)).intValue();
+ }
+ if (((o = entries.get(3)) == null) || !(o instanceof MarkAnchor[])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if (((o = entries.get(4)) == null) || !(o instanceof Anchor[][][])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be a Anchor[][][], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ lam = (Anchor[][][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToMarkSubtable extends GlyphPositioningSubtable {
+ MarkToMarkSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof MarkToMarkSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position(GlyphPositioningState ps) {
+ boolean applied = false;
+ int giMark1 = ps.getGlyph();
+ int ciMark1;
+ if ((ciMark1 = getCoverageIndex(giMark1)) >= 0) {
+ MarkAnchor ma = getMark1Anchor(ciMark1, giMark1);
+ if (ma != null) {
+ if (ps.hasPrev()) {
+ Anchor a = getMark2Anchor(ps.getGlyph(-1), ma.getMarkClass());
+ if (a != null) {
+ if (ps.adjust(a.getAlignmentAdjustment(ma))) {
+ ps.setAdjusted(true);
+ }
+ }
+ ps.consume(1);
+ applied = true;
+ }
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain mark 1 anchor associated with mark 1 coverage index.
+ * @param ciMark1 mark 1 coverage index
+ * @param giMark1 input glyph index of mark 1 glyph
+ * @return mark 1 anchor or null if none applies
+ */
+ public abstract MarkAnchor getMark1Anchor(int ciMark1, int giMark1);
+ /**
+ * Obtain anchor associated with mark 2 glyph index and mark 1 class.
+ * @param giMark2 input glyph index of mark 2 glyph
+ * @param markClass class number of mark 1 glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getMark2Anchor(int giBase, int markClass);
+ static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ if (format == 1) {
+ return new MarkToMarkSubtableFormat1(id, sequence, flags, format, coverage, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToMarkSubtableFormat1 extends MarkToMarkSubtable {
+ private GlyphCoverageTable mct2; // mark 2 coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark1 anchor array, ordered by mark1 coverage index
+ private Anchor[][] mam; // mark2 anchor matrix, ordered by mark2 coverage index, then by mark1 class
+ MarkToMarkSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage, entries);
+ populate(entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ((mct2 != null) && (maa != null) && (nmc > 0) && (mam != null)) {
+ List entries = new ArrayList(4);
+ entries.add(mct2);
+ entries.add(Integer.valueOf(nmc));
+ entries.add(maa);
+ entries.add(mam);
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMark1Anchor(int ciMark1, int giMark1) {
+ if ((maa != null) && (ciMark1 < maa.length)) {
+ return maa [ ciMark1 ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getMark2Anchor(int giMark2, int markClass) {
+ int ciMark2;
+ if ((mct2 != null) && ((ciMark2 = mct2.getCoverageIndex(giMark2)) >= 0)) {
+ if ((mam != null) && (ciMark2 < mam.length)) {
+ Anchor[] ma = mam [ ciMark2 ];
+ if ((ma != null) && (markClass < ma.length)) {
+ return ma [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate(List entries) {
+ if (entries == null) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null");
+ } else if (entries.size() != 4) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 4 entries");
+ } else {
+ Object o;
+ if (((o = entries.get(0)) == null) || !(o instanceof GlyphCoverageTable)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphCoverageTable, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ mct2 = (GlyphCoverageTable) o;
+ }
+ if (((o = entries.get(1)) == null) || !(o instanceof Integer)) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if (((o = entries.get(2)) == null) || !(o instanceof MarkAnchor[])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be a MarkAnchor[], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if (((o = entries.get(3)) == null) || !(o instanceof Anchor[][])) {
+ throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be a Anchor[][], but is: " + ((o != null) ? o.getClass() : null));
+ } else {
+ mam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ContextualSubtable extends GlyphPositioningSubtable {
+ ContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage);
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof ContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position(GlyphPositioningState ps) {
+ boolean applied = false;
+ int gi = ps.getGlyph();
+ int ci;
+ if ((ci = getCoverageIndex(gi)) >= 0) {
+ int[] rv = new int[1];
+ RuleLookup[] la = getLookups(ci, gi, ps, rv);
+ if (la != null) {
+ ps.apply(la, rv[0]);
+ applied = true;
+ }
+ }
+ return applied;
+ }
+ /**
+ * Obtain rule lookups set associated current input glyph context.
+ * @param ci coverage index of glyph at current position
+ * @param gi glyph index of glyph at current position
+ * @param ps glyph positioning state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups(int ci, int gi, GlyphPositioningState ps, int[] rv);
+ static GlyphPositioningSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ if (format == 1) {
+ return new ContextualSubtableFormat1(id, sequence, flags, format, coverage, entries);
+ } else if (format == 2) {
+ return new ContextualSubtableFormat2(id, sequence, flags, format, coverage, entries);
+ } else if (format == 3) {
+ return new ContextualSubtableFormat3(id, sequence, flags, format, coverage, entries);
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat1 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) {
+ super(id, sequence, flags, format, coverage, entries);
+ populate(entries);
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if (rsa != null) {
+ List entries = new ArrayList(1);
+ entries.add(rsa);
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences(Map/*DeviceTable class implements a positioning device table record, comprising
+ * adjustments to be made to scaled design units according to the scaled size.
+ */
+ public static class DeviceTable {
+
+ private final int startSize;
+ private final int endSize;
+ private final int[] deltas;
+
+ /**
+ * Instantiate a DeviceTable.
+ * @param startSize the
+ * @param endSize the ending (scaled) size
+ * @param deltas adjustments for each scaled size
+ */
+ public DeviceTable(int startSize, int endSize, int[] deltas) {
+ assert startSize >= 0;
+ assert startSize <= endSize;
+ assert deltas != null;
+ assert deltas.length == (endSize - startSize) + 1;
+ this.startSize = startSize;
+ this.endSize = endSize;
+ this.deltas = deltas;
+ }
+
+ /** @return the start size */
+ public int getStartSize() {
+ return startSize;
+ }
+
+ /** @return the end size */
+ public int getEndSize() {
+ return endSize;
+ }
+
+ /** @return the deltas */
+ public int[] getDeltas() {
+ return deltas;
+ }
+
+ /**
+ * Find device adjustment.
+ * @param fontSize the font size to search for
+ * @return an adjustment if font size matches an entry
+ */
+ public int findAdjustment(int fontSize) {
+ // [TODO] at present, assumes that 1 device unit equals one point
+ int fs = fontSize / 1000;
+ if (fs < startSize) {
+ return 0;
+ } else if (fs <= endSize) {
+ return deltas [ fs - startSize ] * 1000;
+ } else {
+ return 0;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ start = " + startSize + ", end = " + endSize + ", deltas = " + Arrays.toString(deltas) + "}";
+ }
+
+ }
+
+ /**
+ * The Value class implements a positioning value record, comprising placement
+ * and advancement information in X and Y axes, and optionally including device data used to
+ * perform device (grid-fitted) specific fine grain adjustments.
+ */
+ public static class Value {
+
+ /** X_PLACEMENT value format flag */
+ public static final int X_PLACEMENT = 0x0001;
+ /** Y_PLACEMENT value format flag */
+ public static final int Y_PLACEMENT = 0x0002;
+ /** X_ADVANCE value format flag */
+ public static final int X_ADVANCE = 0x0004;
+ /** Y_ADVANCE value format flag */
+ public static final int Y_ADVANCE = 0x0008;
+ /** X_PLACEMENT_DEVICE value format flag */
+ public static final int X_PLACEMENT_DEVICE = 0x0010;
+ /** Y_PLACEMENT_DEVICE value format flag */
+ public static final int Y_PLACEMENT_DEVICE = 0x0020;
+ /** X_ADVANCE_DEVICE value format flag */
+ public static final int X_ADVANCE_DEVICE = 0x0040;
+ /** Y_ADVANCE_DEVICE value format flag */
+ public static final int Y_ADVANCE_DEVICE = 0x0080;
+
+ /** X_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_X_PLACEMENT = 0;
+ /** Y_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_Y_PLACEMENT = 1;
+ /** X_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_X_ADVANCE = 2;
+ /** Y_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_Y_ADVANCE = 3;
+
+ private int xPlacement; // x placement
+ private int yPlacement; // y placement
+ private int xAdvance; // x advance
+ private int yAdvance; // y advance
+ private final DeviceTable xPlaDevice; // x placement device table
+ private final DeviceTable yPlaDevice; // y placement device table
+ private final DeviceTable xAdvDevice; // x advance device table
+ private final DeviceTable yAdvDevice; // x advance device table
+
+ /**
+ * Instantiate a Value.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ * @param xPlaDevice the x placement device table or null
+ * @param yPlaDevice the y placement device table or null
+ * @param xAdvDevice the x advance device table or null
+ * @param yAdvDevice the y advance device table or null
+ */
+ public Value(int xPlacement, int yPlacement, int xAdvance, int yAdvance, DeviceTable xPlaDevice, DeviceTable yPlaDevice, DeviceTable xAdvDevice, DeviceTable yAdvDevice) {
+ this.xPlacement = xPlacement;
+ this.yPlacement = yPlacement;
+ this.xAdvance = xAdvance;
+ this.yAdvance = yAdvance;
+ this.xPlaDevice = xPlaDevice;
+ this.yPlaDevice = yPlaDevice;
+ this.xAdvDevice = xAdvDevice;
+ this.yAdvDevice = yAdvDevice;
+ }
+
+ /** @return the x placement */
+ public int getXPlacement() {
+ return xPlacement;
+ }
+
+ /** @return the y placement */
+ public int getYPlacement() {
+ return yPlacement;
+ }
+
+ /** @return the x advance */
+ public int getXAdvance() {
+ return xAdvance;
+ }
+
+ /** @return the y advance */
+ public int getYAdvance() {
+ return yAdvance;
+ }
+
+ /** @return the x placement device table */
+ public DeviceTable getXPlaDevice() {
+ return xPlaDevice;
+ }
+
+ /** @return the y placement device table */
+ public DeviceTable getYPlaDevice() {
+ return yPlaDevice;
+ }
+
+ /** @return the x advance device table */
+ public DeviceTable getXAdvDevice() {
+ return xAdvDevice;
+ }
+
+ /** @return the y advance device table */
+ public DeviceTable getYAdvDevice() {
+ return yAdvDevice;
+ }
+
+ /**
+ * Apply value to specific adjustments to without use of device table adjustments.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ */
+ public void adjust(int xPlacement, int yPlacement, int xAdvance, int yAdvance) {
+ this.xPlacement += xPlacement;
+ this.yPlacement += yPlacement;
+ this.xAdvance += xAdvance;
+ this.yAdvance += yAdvance;
+ }
+
+ /**
+ * Apply value to adjustments using font size for device table adjustments.
+ * @param adjustments array of four integers containing X,Y placement and X,Y advance adjustments
+ * @param fontSize font size for device table adjustments
+ * @return true if some adjustment was made
+ */
+ public boolean adjust(int[] adjustments, int fontSize) {
+ boolean adjust = false;
+ int dv;
+ if ((dv = xPlacement) != 0) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ((dv = yPlacement) != 0) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ((dv = xAdvance) != 0) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if ((dv = yAdvance) != 0) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if (fontSize != 0) {
+ DeviceTable dt;
+ if ((dt = xPlaDevice) != null) {
+ if ((dv = dt.findAdjustment(fontSize)) != 0) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ((dt = yPlaDevice) != null) {
+ if ((dv = dt.findAdjustment(fontSize)) != 0) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ((dt = xAdvDevice) != null) {
+ if ((dv = dt.findAdjustment(fontSize)) != 0) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ if ((dt = yAdvDevice) != null) {
+ if ((dv = dt.findAdjustment(fontSize)) != 0) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ }
+ return adjust;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append("{ ");
+ if (xPlacement != 0) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xPlacement = " + xPlacement);
+ }
+ if (yPlacement != 0) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("yPlacement = " + yPlacement);
+ }
+ if (xAdvance != 0) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xAdvance = " + xAdvance);
+ }
+ if (yAdvance != 0) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("yAdvance = " + yAdvance);
+ }
+ if (xPlaDevice != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xPlaDevice = " + xPlaDevice);
+ }
+ if (yPlaDevice != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xPlaDevice = " + yPlaDevice);
+ }
+ if (xAdvDevice != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xAdvDevice = " + xAdvDevice);
+ }
+ if (yAdvDevice != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("xAdvDevice = " + yAdvDevice);
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The PairValues class implements a pair value record, comprising a glyph id (or zero)
+ * and two optional positioning values.
+ */
+ public static class PairValues {
+
+ private final int glyph; // glyph id (or 0)
+ private final Value value1; // value for first glyph in pair (or null)
+ private final Value value2; // value for second glyph in pair (or null)
+
+ /**
+ * Instantiate a PairValues.
+ * @param glyph the glyph id (or zero)
+ * @param value1 the value of the first glyph in pair (or null)
+ * @param value2 the value of the second glyph in pair (or null)
+ */
+ public PairValues(int glyph, Value value1, Value value2) {
+ assert glyph >= 0;
+ this.glyph = glyph;
+ this.value1 = value1;
+ this.value2 = value2;
+ }
+
+ /** @return the glyph id */
+ public int getGlyph() {
+ return glyph;
+ }
+
+ /** @return the first value */
+ public Value getValue1() {
+ return value1;
+ }
+
+ /** @return the second value */
+ public Value getValue2() {
+ return value2;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append("{ ");
+ if (glyph != 0) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("glyph = " + glyph);
+ }
+ if (value1 != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("value1 = " + value1);
+ }
+ if (value2 != null) {
+ if (!first) {
+ sb.append(", ");
+ } else {
+ first = false;
+ }
+ sb.append("value2 = " + value2);
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The Anchor class implements a anchor record, comprising an X,Y coordinate pair,
+ * an optional anchor point index (or -1), and optional X or Y device tables (or null if absent).
+ */
+ public static class Anchor {
+
+ private final int x; // xCoordinate (in design units)
+ private final int y; // yCoordinate (in design units)
+ private final int anchorPoint; // anchor point index (or -1)
+ private final DeviceTable xDevice; // x device table
+ private final DeviceTable yDevice; // y device table
+
+ /**
+ * Instantiate an Anchor (format 1).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ */
+ public Anchor(int x, int y) {
+ this (x, y, -1, null, null);
+ }
+
+ /**
+ * Instantiate an Anchor (format 2).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param anchorPoint anchor index (or -1)
+ */
+ public Anchor(int x, int y, int anchorPoint) {
+ this (x, y, anchorPoint, null, null);
+ }
+
+ /**
+ * Instantiate an Anchor (format 3).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param xDevice the x device table (or null if not present)
+ * @param yDevice the y device table (or null if not present)
+ */
+ public Anchor(int x, int y, DeviceTable xDevice, DeviceTable yDevice) {
+ this (x, y, -1, xDevice, yDevice);
+ }
+
+ /**
+ * Instantiate an Anchor based on an existing anchor.
+ * @param a the existing anchor
+ */
+ protected Anchor(Anchor a) {
+ this (a.x, a.y, a.anchorPoint, a.xDevice, a.yDevice);
+ }
+
+ private Anchor(int x, int y, int anchorPoint, DeviceTable xDevice, DeviceTable yDevice) {
+ assert (anchorPoint >= 0) || (anchorPoint == -1);
+ this.x = x;
+ this.y = y;
+ this.anchorPoint = anchorPoint;
+ this.xDevice = xDevice;
+ this.yDevice = yDevice;
+ }
+
+ /** @return the x coordinate */
+ public int getX() {
+ return x;
+ }
+
+ /** @return the y coordinate */
+ public int getY() {
+ return y;
+ }
+
+ /** @return the anchor point index (or -1 if not specified) */
+ public int getAnchorPoint() {
+ return anchorPoint;
+ }
+
+ /** @return the x device table (or null if not specified) */
+ public DeviceTable getXDevice() {
+ return xDevice;
+ }
+
+ /** @return the y device table (or null if not specified) */
+ public DeviceTable getYDevice() {
+ return yDevice;
+ }
+
+ /**
+ * Obtain adjustment value required to align the specified anchor
+ * with this anchor.
+ * @param a the anchor to align
+ * @return the adjustment value needed to effect alignment
+ */
+ public Value getAlignmentAdjustment(Anchor a) {
+ assert a != null;
+ // TODO - handle anchor point
+ // TODO - handle device tables
+ return new Value(x - a.x, y - a.y, 0, 0, null, null, null, null);
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ [" + x + "," + y + "]");
+ if (anchorPoint != -1) {
+ sb.append(", anchorPoint = " + anchorPoint);
+ }
+ if (xDevice != null) {
+ sb.append(", xDevice = " + xDevice);
+ }
+ if (yDevice != null) {
+ sb.append(", yDevice = " + yDevice);
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The MarkAnchor class is a subclass of the Anchor class, adding a mark
+ * class designation.
+ */
+ public static class MarkAnchor extends Anchor {
+
+ private final int markClass; // mark class
+
+ /**
+ * Instantiate a MarkAnchor
+ * @param markClass the mark class
+ * @param a the underlying anchor (whose fields are copied)
+ */
+ public MarkAnchor(int markClass, Anchor a) {
+ super(a);
+ this.markClass = markClass;
+ }
+
+ /** @return the mark class */
+ public int getMarkClass() {
+ return markClass;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ markClass = " + markClass + ", anchor = " + super.toString() + " }";
+ }
+
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphProcessingState.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphProcessingState.java
new file mode 100644
index 00000000000..89fbf5eec4e
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphProcessingState.java
@@ -0,0 +1,1195 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fontbox.ttf.advanced.util.CharAssociation;
+import org.apache.fontbox.ttf.advanced.util.GlyphContextTester;
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+import org.apache.fontbox.ttf.advanced.util.GlyphTester;
+import org.apache.fontbox.ttf.advanced.util.ScriptContextTester;
+
+/**
+ * The GlyphProcessingState implements a common, base state object used during glyph substitution
+ * and positioning processing.
count glyphs remain in
+ * input sequence.
+ * @param count of glyphs to test
+ * @return true if at least count glyphs are available
+ */
+ public boolean hasNext(int count) {
+ return (index + count) <= indexLast;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., add the consuemd count to the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed.
+ * @return the new (updated) position index
+ */
+ public int next() {
+ if (index < indexLast) {
+ // force consumption of at least one input glyph
+ if (consumed == 0) {
+ consumed = 1;
+ }
+ index += consumed;
+ consumed = 0;
+ if (index > indexLast) {
+ index = indexLast;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Determine if at least one backtrack (previous) glyph is present
+ * in input sequence.
+ * @return true if one or more glyph remains
+ */
+ public boolean hasPrev() {
+ return hasPrev(1);
+ }
+
+ /**
+ * Determine if at least count backtrack (previous) glyphs
+ * are present in input sequence.
+ * @param count of glyphs to test
+ * @return true if at least count glyphs are available
+ */
+ public boolean hasPrev(int count) {
+ return (index - count) >= 0;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., subtract the consuemd count from the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed. This method is used to traverse an input
+ * glyph sequence in reverse order.
+ * @return the new (updated) position index
+ */
+ public int prev() {
+ if (index > 0) {
+ // force consumption of at least one input glyph
+ if (consumed == 0) {
+ consumed = 1;
+ }
+ index -= consumed;
+ consumed = 0;
+ if (index < 0) {
+ index = 0;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Record the consumption of count glyphs such that
+ * this consumption never exceeds the number of glyphs in the input glyph
+ * sequence.
+ * @param count of glyphs to consume
+ * @return newly adjusted consumption count
+ * @throws IndexOutOfBoundsException if count would cause consumption
+ * to exceed count of glyphs in input glyph sequence
+ */
+ public int consume(int count) throws IndexOutOfBoundsException {
+ if ((consumed + count) <= indexLast) {
+ consumed += count;
+ return consumed;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Determine if any consumption has occurred.
+ * @return true if consumption count is greater than zero
+ */
+ public boolean didConsume() {
+ return consumed > 0;
+ }
+
+ /**
+ * Obtain reference to input glyph sequence, which must not be modified.
+ * @return input glyph sequence
+ */
+ public GlyphSequence getInput() {
+ return igs;
+ }
+
+ /**
+ * Obtain glyph at specified offset from current position.
+ * @param offset from current position
+ * @return glyph at specified offset from current position
+ * @throws IndexOutOfBoundsException if no glyph available at offset
+ */
+ public int getGlyph(int offset) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ((i >= 0) && (i < indexLast)) {
+ return igs.getGlyph(i);
+ } else {
+ throw new IndexOutOfBoundsException("attempting index at " + i);
+ }
+ }
+
+ /**
+ * Obtain glyph at current position.
+ * @return glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int getGlyph() throws IndexOutOfBoundsException {
+ return getGlyph(0);
+ }
+
+ /**
+ * Set (replace) glyph at specified offset from current position.
+ * @param offset from current position
+ * @param glyph to set at specified offset from current position
+ * @throws IndexOutOfBoundsException if specified offset is not valid position
+ */
+ public void setGlyph(int offset, int glyph) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ((i >= 0) && (i < indexLast)) {
+ igs.setGlyph(i, glyph);
+ } else {
+ throw new IndexOutOfBoundsException("attempting index at " + i);
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at specified offset from current position.
+ * @param offset from current position
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if offset results in an invalid index into input glyph sequence
+ */
+ public CharAssociation getAssociation(int offset) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ((i >= 0) && (i < indexLast)) {
+ return igs.getAssociation(i);
+ } else {
+ throw new IndexOutOfBoundsException("attempting index at " + i);
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at current position.
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public CharAssociation getAssociation() throws IndexOutOfBoundsException {
+ return getAssociation(0);
+ }
+
+ /**
+ * Obtain count glyphs starting at specified offset from current position. If
+ * reverseOrder is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts) throws IndexOutOfBoundsException {
+ if (count < 0) {
+ count = getGlyphsAvailable(offset, reverseOrder, ignoreTester) [ 0 ];
+ }
+ int start = index + offset;
+ if (start < 0) {
+ throw new IndexOutOfBoundsException("will attempt index at " + start);
+ } else if (!reverseOrder && ((start + count) > indexLast)) {
+ throw new IndexOutOfBoundsException("will attempt index at " + (start + count));
+ } else if (reverseOrder && ((start + 1) < count)) {
+ throw new IndexOutOfBoundsException("will attempt index at " + (start - count));
+ }
+ if (glyphs == null) {
+ glyphs = new int [ count ];
+ } else if (glyphs.length != count) {
+ throw new IllegalArgumentException("glyphs array is non-null, but its length (" + glyphs.length + "), is not equal to count (" + count + ")");
+ }
+ if (!reverseOrder) {
+ return getGlyphsForward(start, count, ignoreTester, glyphs, counts);
+ } else {
+ return getGlyphsReverse(start, count, ignoreTester, glyphs, counts);
+ }
+ }
+
+ private int[] getGlyphsForward(int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for (int i = start, n = indexLast; (i < n) && (counted < count); i++) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) {
+ glyphs [ counted++ ] = gi;
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ((counts != null) && (counts.length > 1)) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ private int[] getGlyphsReverse(int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for (int i = start; (i >= 0) && (counted < count); i--) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) {
+ glyphs [ counted++ ] = gi;
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ((counts != null) && (counts.length > 1)) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ /**
+ * Obtain count glyphs starting at specified offset from current position. If
+ * offset is negative, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs(int offset, int count, int[] glyphs, int[] counts) throws IndexOutOfBoundsException {
+ return getGlyphs(offset, count, offset < 0, ignoreDefault, glyphs, counts);
+ }
+
+ /**
+ * Obtain all glyphs starting from current position to end of input glyph sequence.
+ * @return array of available glyphs
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int[] getGlyphs() throws IndexOutOfBoundsException {
+ return getGlyphs(0, indexLast - index, false, null, null, null);
+ }
+
+ /**
+ * Obtain count ignored glyphs starting at specified offset from current position. If
+ * reverseOrder is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts) throws IndexOutOfBoundsException {
+ return getGlyphs(offset, count, reverseOrder, new NotGlyphTester(ignoreTester), glyphs, counts);
+ }
+
+ /**
+ * Obtain count ignored glyphs starting at specified offset from current position. If offset is
+ * negative, then fetch in reverse order.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs(int offset, int count) throws IndexOutOfBoundsException {
+ return getIgnoredGlyphs(offset, count, offset < 0, ignoreDefault, null, null);
+ }
+
+ /**
+ * Determine if glyph at specified offset from current position is ignored. If offset is
+ * negative, then test in reverse order.
+ * @param offset from current position
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @return true if glyph is ignored
+ * @throws IndexOutOfBoundsException if offset results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean isIgnoredGlyph(int offset, GlyphTester ignoreTester) throws IndexOutOfBoundsException {
+ return (ignoreTester != null) && ignoreTester.test(getGlyph(offset), getLookupFlags());
+ }
+
+ /**
+ * Determine if glyph at specified offset from current position is ignored. If offset is
+ * negative, then test in reverse order.
+ * @param offset from current position
+ * @return true if glyph is ignored
+ * @throws IndexOutOfBoundsException if offset results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean isIgnoredGlyph(int offset) throws IndexOutOfBoundsException {
+ return isIgnoredGlyph(offset, ignoreDefault);
+ }
+
+ /**
+ * Determine if glyph at current position is ignored.
+ * @return true if glyph is ignored
+ * @throws IndexOutOfBoundsException if offset results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean isIgnoredGlyph() throws IndexOutOfBoundsException {
+ return isIgnoredGlyph(getPosition());
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * reverseOrder is true, then search backwards in input glyph sequence.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs to count (or null, in which case none are ignored)
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable(int offset, boolean reverseOrder, GlyphTester ignoreTester) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ((start < 0) || (start > indexLast)) {
+ return new int[] { 0, 0 };
+ } else if (!reverseOrder) {
+ return getGlyphsAvailableForward(start, ignoreTester);
+ } else {
+ return getGlyphsAvailableReverse(start, ignoreTester);
+ }
+ }
+
+ private int[] getGlyphsAvailableForward(int start, GlyphTester ignoreTester) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if (ignoreTester == null) {
+ counted = indexLast - start;
+ } else {
+ for (int i = start, n = indexLast; i < n; i++) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if (ignoreTester.test(gi, getLookupFlags())) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ private int[] getGlyphsAvailableReverse(int start, GlyphTester ignoreTester) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if (ignoreTester == null) {
+ counted = start + 1;
+ } else {
+ for (int i = start; i >= 0; i--) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if (ignoreTester.test(gi, getLookupFlags())) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * reverseOrder is true, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable(int offset, boolean reverseOrder) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable(offset, reverseOrder, ignoreDefault);
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable(int offset) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable(offset, offset < 0);
+ }
+
+ /**
+ * Obtain count character associations of glyphs starting at specified offset from current position. If
+ * reverseOrder is true, then associations are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public CharAssociation[] getAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
+ throws IndexOutOfBoundsException {
+ if (count < 0) {
+ count = getGlyphsAvailable(offset, reverseOrder, ignoreTester) [ 0 ];
+ }
+ int start = index + offset;
+ if (start < 0) {
+ throw new IndexOutOfBoundsException("will attempt index at " + start);
+ } else if (!reverseOrder && ((start + count) > indexLast)) {
+ throw new IndexOutOfBoundsException("will attempt index at " + (start + count));
+ } else if (reverseOrder && ((start + 1) < count)) {
+ throw new IndexOutOfBoundsException("will attempt index at " + (start - count));
+ }
+ if (associations == null) {
+ associations = new CharAssociation [ count ];
+ } else if (associations.length != count) {
+ throw new IllegalArgumentException("associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")");
+ }
+ if (!reverseOrder) {
+ return getAssociationsForward(start, count, ignoreTester, associations, counts);
+ } else {
+ return getAssociationsReverse(start, count, ignoreTester, associations, counts);
+ }
+ }
+
+ private CharAssociation[] getAssociationsForward(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for (int i = start, n = indexLast, k = 0; i < n; i++) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) {
+ if (k < count) {
+ associations [ k++ ] = getAssociation(i - index);
+ counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ((counts != null) && (counts.length > 1)) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ private CharAssociation[] getAssociationsReverse(int start, int count, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for (int i = start, k = 0; i >= 0; i--) {
+ int gi = getGlyph(i - index);
+ if (gi == 65535) {
+ ignored++;
+ } else {
+ if ((ignoreTester == null) || !ignoreTester.test(gi, getLookupFlags())) {
+ if (k < count) {
+ associations [ k++ ] = getAssociation(i - index);
+ counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ((counts != null) && (counts.length > 1)) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ /**
+ * Obtain count character associations of glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public CharAssociation[] getAssociations(int offset, int count) throws IndexOutOfBoundsException {
+ return getAssociations(offset, count, offset < 0, ignoreDefault, null, null);
+ }
+
+ /**
+ * Obtain count character associations of ignored glyphs starting at specified offset from current position. If
+ * reverseOrder is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public CharAssociation[] getIgnoredAssociations(int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, CharAssociation[] associations, int[] counts)
+ throws IndexOutOfBoundsException {
+ return getAssociations(offset, count, reverseOrder, new NotGlyphTester(ignoreTester), associations, counts);
+ }
+
+ /**
+ * Obtain count character associations of ignored glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public CharAssociation[] getIgnoredAssociations(int offset, int count) throws IndexOutOfBoundsException {
+ return getIgnoredAssociations(offset, count, offset < 0, ignoreDefault, null, null);
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length count glyphs with a subsequence of the sequence gs starting from the specified
+ * offset gsOffset of length gsCount glyphs.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @param gsOffset offset of first glyph in replacement sequence
+ * @param gsCount count of glyphs in replacement sequence starting at gsOffset
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput(int offset, int count, GlyphSequence gs, int gsOffset, int gsCount) throws IndexOutOfBoundsException {
+ int nig = (igs != null) ? igs.getGlyphCount() : 0;
+ int position = getPosition() + offset;
+ if (position < 0) {
+ position = 0;
+ } else if (position > nig) {
+ position = nig;
+ }
+ if ((count < 0) || ((position + count) > nig)) {
+ count = nig - position;
+ }
+ int nrg = (gs != null) ? gs.getGlyphCount() : 0;
+ if (gsOffset < 0) {
+ gsOffset = 0;
+ } else if (gsOffset > nrg) {
+ gsOffset = nrg;
+ }
+ if ((gsCount < 0) || ((gsOffset + gsCount) > nrg)) {
+ gsCount = nrg - gsOffset;
+ }
+ int ng = nig + gsCount - count;
+ IntBuffer gb = IntBuffer.allocate(ng);
+ List al = new ArrayList(ng);
+ for (int i = 0, n = position; i < n; i++) {
+ gb.put(igs.getGlyph(i));
+ al.add(igs.getAssociation(i));
+ }
+ for (int i = gsOffset, n = gsOffset + gsCount; i < n; i++) {
+ gb.put(gs.getGlyph(i));
+ al.add(gs.getAssociation(i));
+ }
+ for (int i = position + count, n = nig; i < n; i++) {
+ gb.put(igs.getGlyph(i));
+ al.add(igs.getAssociation(i));
+ }
+ gb.flip();
+ if (igs.compareGlyphs(gb) != 0) {
+ this.igs = new GlyphSequence(igs.getCharacters(), gb, al);
+ this.indexLast = gb.limit();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length count glyphs with all glyphs in the replacement sequence gs.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput(int offset, int count, GlyphSequence gs) throws IndexOutOfBoundsException {
+ return replaceInput(offset, count, gs, 0, gs.getGlyphCount());
+ }
+
+ /**
+ * Erase glyphs in input glyph sequence starting at specified offset from current position, where each glyph
+ * in the specified glyphs array is matched, one at a time, and when a (forward searching) match is found
+ * in the input glyph sequence, the matching glyph is replaced with the glyph index 65535.
+ * @param offset from current position
+ * @param glyphs array of glyphs to erase
+ * @return the number of glyphs erased, which may be less than the number of specified glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int erase(int offset, int[] glyphs) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ((start < 0) || (start > indexLast)) {
+ throw new IndexOutOfBoundsException("will attempt index at " + start);
+ } else {
+ int erased = 0;
+ for (int i = start - index, n = indexLast - start; i < n; i++) {
+ int gi = getGlyph(i);
+ if (gi == glyphs [ erased ]) {
+ setGlyph(i, 65535);
+ erased++;
+ }
+ }
+ return erased;
+ }
+ }
+
+ /**
+ * Determine if is possible that the current input sequence satisfies a script specific
+ * context testing predicate. If no predicate applies, then application is always possible.
+ * @return true if no script specific context tester applies or if a specified tester returns
+ * true for the current input sequence context
+ */
+ public boolean maybeApplicable() {
+ if (gct == null) {
+ return true;
+ } else {
+ return gct.test(script, language, feature, igs, index, getLookupFlags());
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one glyph.
+ */
+ public void applyDefault() {
+ consumed += 1;
+ }
+
+ /**
+ * Determine if specified glyph is a base glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+ */
+ public boolean isBase(int gi) {
+ if (gdef != null) {
+ return gdef.isGlyphClass(gi, GlyphDefinitionTable.GLYPH_CLASS_BASE);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored base glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+ */
+ public boolean isIgnoredBase(int gi, int flags) {
+ return ((flags & GlyphSubtable.LF_IGNORE_BASE) != 0) && isBase(gi);
+ }
+
+ /**
+ * Determine if specified glyph is an ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isLigature(int gi) {
+ if (gdef != null) {
+ return gdef.isGlyphClass(gi, GlyphDefinitionTable.GLYPH_CLASS_LIGATURE);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isIgnoredLigature(int gi, int flags) {
+ return ((flags & GlyphSubtable.LF_IGNORE_LIGATURE) != 0) && isLigature(gi);
+ }
+
+ /**
+ * Determine if specified glyph is a mark glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a mark glyph; otherwise, false
+ */
+ public boolean isMark(int gi) {
+ if (gdef != null) {
+ return gdef.isGlyphClass(gi, GlyphDefinitionTable.GLYPH_CLASS_MARK);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is an ignored ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @param flags that apply to lookup in scope
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isIgnoredMark(int gi, int flags) {
+ if ((flags & GlyphSubtable.LF_IGNORE_MARK) != 0) {
+ return isMark(gi);
+ } else if ((flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE) != 0) {
+ int lac = (flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE) >> 8;
+ int gac = gdef.getMarkAttachClass(gi);
+ return (gac != lac);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getIgnoreTester(int flags) {
+ if ((flags & GlyphSubtable.LF_IGNORE_BASE) != 0) {
+ if ((flags & (GlyphSubtable.LF_IGNORE_LIGATURE | GlyphSubtable.LF_IGNORE_MARK)) == 0) {
+ return ignoreBase;
+ } else {
+ return getCombinedIgnoreTester(flags);
+ }
+ }
+ if ((flags & GlyphSubtable.LF_IGNORE_LIGATURE) != 0) {
+ if ((flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_MARK)) == 0) {
+ return ignoreLigature;
+ } else {
+ return getCombinedIgnoreTester(flags);
+ }
+ }
+ if ((flags & GlyphSubtable.LF_IGNORE_MARK) != 0) {
+ if ((flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_LIGATURE)) == 0) {
+ return ignoreMark;
+ } else {
+ return getCombinedIgnoreTester(flags);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified multiple (combined) lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getCombinedIgnoreTester(int flags) {
+ GlyphTester[] gta = new GlyphTester [ 3 ];
+ int ngt = 0;
+ if ((flags & GlyphSubtable.LF_IGNORE_BASE) != 0) {
+ gta [ ngt++ ] = ignoreBase;
+ }
+ if ((flags & GlyphSubtable.LF_IGNORE_LIGATURE) != 0) {
+ gta [ ngt++ ] = ignoreLigature;
+ }
+ if ((flags & GlyphSubtable.LF_IGNORE_MARK) != 0) {
+ gta [ ngt++ ] = ignoreMark;
+ }
+ return getCombinedOrTester(gta, ngt);
+ }
+
+ /**
+ * Obtain an combined OR glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined OR glyph tester
+ */
+ public GlyphTester getCombinedOrTester(GlyphTester[] gta, int ngt) {
+ if (ngt > 0) {
+ return new CombinedOrGlyphTester(gta, ngt);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain an combined AND glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined AND glyph tester
+ */
+ public GlyphTester getCombinedAndTester(GlyphTester[] gta, int ngt) {
+ if (ngt > 0) {
+ return new CombinedAndGlyphTester(gta, ngt);
+ } else {
+ return null;
+ }
+ }
+
+ /** combined OR glyph tester */
+ private static class CombinedOrGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedOrGlyphTester(GlyphTester[] gta, int ngt) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test(int gi, int flags) {
+ for (int i = 0, n = ngt; i < n; i++) {
+ GlyphTester gt = gta [ i ];
+ if (gt != null) {
+ if (gt.test(gi, flags)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /** combined AND glyph tester */
+ private static class CombinedAndGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedAndGlyphTester(GlyphTester[] gta, int ngt) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test(int gi, int flags) {
+ for (int i = 0, n = ngt; i < n; i++) {
+ GlyphTester gt = gta [ i ];
+ if (gt != null) {
+ if (!gt.test(gi, flags)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /** NOT glyph tester */
+ private static class NotGlyphTester implements GlyphTester {
+ private GlyphTester gt;
+ NotGlyphTester(GlyphTester gt) {
+ this.gt = gt;
+ }
+ /** {@inheritDoc} */
+ public boolean test(int gi, int flags) {
+ if (gt != null) {
+ if (gt.test(gi, flags)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitution.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitution.java
new file mode 100644
index 00000000000..1137efe5e90
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitution.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+/**
+ * The GlyphSubstitution interface is implemented by a glyph substitution subtable
+ * that supports the determination of glyph substitution information based on script and
+ * language of the corresponding character content.
The GlyphSubstitutionState implements an state object used during glyph substitution
+ * processing.
nig input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by nig are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply(AdvancedTypographicTable.RuleLookup[] lookups, int nig) {
+ // int nbg = index;
+ int nlg = indexLast - (index + nig);
+ int nog = 0;
+ if ((lookups != null) && (lookups.length > 0)) {
+ // apply each rule lookup to extracted input glyph array
+ for (int i = 0, n = lookups.length; i < n; i++) {
+ AdvancedTypographicTable.RuleLookup l = lookups [ i ];
+ if (l != null) {
+ AdvancedTypographicTable.LookupTable lt = l.getLookup();
+ if (lt != null) {
+ // perform substitution on a copy of previous state
+ GlyphSubstitutionState ss = new GlyphSubstitutionState(this);
+ // apply lookup table substitutions
+ GlyphSequence gs = lt.substitute(ss, l.getSequenceIndex());
+ // replace current input sequence starting at current position with result
+ if (replaceInput(0, -1, gs)) {
+ nog = gs.getGlyphCount() - nlg;
+ }
+ }
+ }
+ }
+ // output glyphs and associations
+ putGlyphs(getGlyphs(0, nog, false, null, null, null), getAssociations(0, nog, false, null, null, null), null);
+ // consume replaced input glyphs
+ consume(nog);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph,
+ * writing that glyph (and its association) to the output glyphs (and associations).
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ int gi = getGlyph();
+ if (gi != 65535) {
+ putGlyph(gi, getAssociation(), null);
+ }
+ }
+
+ private static IntBuffer growBuffer(IntBuffer ib) {
+ int capacity = ib.capacity();
+ int capacityNew = capacity * 2;
+ IntBuffer ibNew = IntBuffer.allocate(capacityNew);
+ ib.rewind();
+ return ibNew.put(ib);
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionSubtable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionSubtable.java
new file mode 100644
index 00000000000..8b02909d1b9
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionSubtable.java
@@ -0,0 +1,124 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+import org.apache.fontbox.ttf.advanced.util.ScriptContextTester;
+
+/**
+ * The GlyphSubstitutionSubtable implements an abstract base of a glyph substitution subtable,
+ * providing a default implementation of the GlyphSubstitution interface.
GlyphSubstitutionSubtable.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage subtable coverage table
+ */
+ protected GlyphSubstitutionSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage) {
+ super(id, sequence, flags, format, coverage);
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return AdvancedTypographicTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphSubstitutionTable.getLookupTypeName(getType());
+ }
+
+ /** {@inheritDoc} */
+ public boolean isCompatible(GlyphSubtable subtable) {
+ return subtable instanceof GlyphSubstitutionSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean substitute(GlyphSubstitutionState ss) {
+ return false;
+ }
+
+ /**
+ * Apply substitutions using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (copy input glyph and advance).
+ * If sequenceIndex is non-negative, then apply subtables only when current position
+ * matches sequenceIndex in relation to the starting position. Furthermore, upon
+ * successful application at sequenceIndex, then apply default action for all remaining
+ * glyphs in input sequence.
+ * @param ss substitution state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return output glyph sequence
+ */
+ public static final GlyphSequence substitute(GlyphSubstitutionState ss, GlyphSubstitutionSubtable[] sta, int sequenceIndex) {
+ int sequenceStart = ss.getPosition();
+ boolean appliedOneShot = false;
+ while (ss.hasNext()) {
+ boolean applied = false;
+ if (!appliedOneShot && ss.maybeApplicable()) {
+ for (int i = 0, n = sta.length; !applied && (i < n); i++) {
+ if (sequenceIndex < 0) {
+ applied = ss.apply(sta [ i ]);
+ } else if (ss.getPosition() == (sequenceStart + sequenceIndex)) {
+ applied = ss.apply(sta [ i ]);
+ if (applied) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if (!applied || !ss.didConsume()) {
+ ss.applyDefault();
+ }
+ ss.next();
+ }
+ return ss.getOutput();
+ }
+
+ /**
+ * Apply substitutions.
+ * @param gs input glyph sequence
+ * @param script tag
+ * @param language tag
+ * @param feature tag
+ * @param sta subtable array
+ * @param sct script context tester
+ * @return output glyph sequence
+ */
+ public static final GlyphSequence substitute(GlyphSequence gs, String script, String language, String feature, GlyphSubstitutionSubtable[] sta, ScriptContextTester sct) {
+ synchronized (STATE) {
+ return substitute(STATE.reset(gs, script, language, feature, sct), sta, -1);
+ }
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionTable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionTable.java
new file mode 100644
index 00000000000..b3f1f8f7cdf
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubstitutionTable.java
@@ -0,0 +1,1503 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fontbox.ttf.OpenTypeFont;
+import org.apache.fontbox.ttf.TTFDataStream;
+import org.apache.fontbox.ttf.TrueTypeFont;
+import org.apache.fontbox.ttf.advanced.scripts.ScriptProcessor;
+import org.apache.fontbox.ttf.advanced.util.CharAssociation;
+import org.apache.fontbox.ttf.advanced.util.GlyphSequence;
+import org.apache.fontbox.ttf.advanced.util.GlyphTester;
+
+/**
+ * The GlyphSubstitutionTable class is a glyph table that implements
+ * GlyphSubstitution functionality.
Adapted from the Apache FOP Project.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public class GlyphSubstitutionTable extends AdvancedTypographicTable { + + /** logging instance */ + private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class); + + /** tag that identifies this table type */ + public static final String TAG = "GSUB"; + + /** single substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_SINGLE = 1; + /** multiple substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_MULTIPLE = 2; + /** alternate substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3; + /** ligature substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4; + /** contextual substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_CONTEXTUAL = 5; + /** chained contextual substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 6; + /** extension substitution substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION = 7; + /** reverse chained contextual single substitution subtable type */ + public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE = 8; + + public GlyphSubstitutionTable(OpenTypeFont otf) { + super(otf, null, new java.util.HashMap(0)); + } + + /** + * Initialize thisGlyphSubstitutionTable object using the specified lookups
+ * and subtables.
+ * @param gdef glyph definition table that applies
+ * @param lookups a map of lookup specifications to subtable identifier strings
+ * @param subtables a list of identified subtables
+ */
+ public GlyphSubstitutionTable initialize(GlyphDefinitionTable gdef, Map lookups, List subtables) {
+ initialize(lookups);
+ if ((subtables == null) || (subtables.size() == 0)) {
+ throw new AdvancedTypographicTableFormatException("subtables must be non-empty");
+ } else {
+ for (Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if (o instanceof GlyphSubstitutionSubtable) {
+ addSubtable((GlyphSubtable) o);
+ } else {
+ throw new AdvancedTypographicTableFormatException("subtable must be a glyph substitution subtable");
+ }
+ }
+ freezeSubtables();
+ return this;
+ }
+ }
+
+ @Override
+ protected void read(TrueTypeFont ttf, TTFDataStream data) throws IOException
+ {
+ if (ttf instanceof OpenTypeFont) {
+ new AdvancedTypographicTableReader((OpenTypeFont) ttf, this, data).read();
+ this.initialized = true;
+ }
+ }
+
+ /**
+ * Perform substitution processing using all matching lookups.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param features parameterized features
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute(GlyphSequence gs, String script, String language, Object[][] features) {
+ GlyphSequence ogs;
+ Map/*Ligature class implements a ligature lookup result in terms of
+ * a ligature glyph (code) and the N+1... components that comprise the ligature,
+ * where the Nth component was consumed in the coverage table lookup mapping to
+ * this ligature instance.
+ */
+ public static class Ligature {
+
+ private final int ligature; // (resulting) ligature glyph
+ private final int[] components; // component glyph codes (note that first component is implied)
+
+ /**
+ * Instantiate a ligature.
+ * @param ligature glyph id
+ * @param components sequence of N+1... component glyph (or character) identifiers
+ */
+ public Ligature(int ligature, int[] components) {
+ if ((ligature < 0) || (ligature > 65535)) {
+ throw new AdvancedTypographicTableFormatException("invalid ligature glyph index: " + ligature);
+ } else if (components == null) {
+ throw new AdvancedTypographicTableFormatException("invalid ligature components, must be non-null array");
+ } else {
+ for (int i = 0, n = components.length; i < n; i++) {
+ int gc = components [ i ];
+ if ((gc < 0) || (gc > 65535)) {
+ throw new AdvancedTypographicTableFormatException("invalid component glyph index: " + gc);
+ }
+ }
+ this.ligature = ligature;
+ this.components = components;
+ }
+ }
+
+ /** @return ligature glyph id */
+ public int getLigature() {
+ return ligature;
+ }
+
+ /** @return array of N+1... components */
+ public int[] getComponents() {
+ return components;
+ }
+
+ /** @return components count */
+ public int getNumComponents() {
+ return components.length;
+ }
+
+ /**
+ * Determine if input sequence at offset matches ligature's components.
+ * @param glyphs array of glyph components to match (including first, implied glyph)
+ * @return true if matches
+ */
+ public boolean matchesComponents(int[] glyphs) {
+ if (glyphs.length < (components.length + 1)) {
+ return false;
+ } else {
+ for (int i = 0, n = components.length; i < n; i++) {
+ if (glyphs [ i + 1 ] != components [ i ]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{components={");
+ for (int i = 0, n = components.length; i < n; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(Integer.toString(components[i]));
+ }
+ sb.append("},ligature=");
+ sb.append(Integer.toString(ligature));
+ sb.append("}");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The LigatureSet class implements a set of ligatures.
+ */
+ public static class LigatureSet {
+
+ private final Ligature[] ligatures; // set of ligatures all of which share the first (implied) component
+ private final int maxComponents; // maximum number of components (including first)
+
+ /**
+ * Instantiate a set of ligatures.
+ * @param ligatures collection of ligatures
+ */
+ public LigatureSet(List ligatures) {
+ this ((Ligature[]) ligatures.toArray(new Ligature [ ligatures.size() ]));
+ }
+
+ /**
+ * Instantiate a set of ligatures.
+ * @param ligatures array of ligatures
+ */
+ public LigatureSet(Ligature[] ligatures) {
+ if (ligatures == null) {
+ throw new AdvancedTypographicTableFormatException("invalid ligatures, must be non-null array");
+ } else {
+ this.ligatures = ligatures;
+ int ncMax = -1;
+ for (int i = 0, n = ligatures.length; i < n; i++) {
+ Ligature l = ligatures [ i ];
+ int nc = l.getNumComponents() + 1;
+ if (nc > ncMax) {
+ ncMax = nc;
+ }
+ }
+ maxComponents = ncMax;
+ }
+ }
+
+ /** @return array of ligatures in this ligature set */
+ public Ligature[] getLigatures() {
+ return ligatures;
+ }
+
+ /** @return count of ligatures in this ligature set */
+ public int getNumLigatures() {
+ return ligatures.length;
+ }
+
+ /** @return maximum number of components in one ligature (including first component) */
+ public int getMaxComponents() {
+ return maxComponents;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ligs={");
+ for (int i = 0, n = ligatures.length; i < n; i++) {
+ if (i > 0) {
+ sb.append(',');
+ }
+ sb.append(ligatures[i]);
+ }
+ sb.append("}}");
+ return sb.toString();
+ }
+
+ }
+
+}
+
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubtable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubtable.java
new file mode 100644
index 00000000000..3dbb8d09d10
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/GlyphSubtable.java
@@ -0,0 +1,310 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The GlyphSubtable implements an abstract glyph subtable that
+ * encapsulates identification, type, format, and coverage information.
null, then
+ * clear and remove weak reference.
+ * @param table the table or null
+ * @throws IllegalStateException if table is already set to non-null
+ */
+ public synchronized void setTable(AdvancedTypographicTable table) throws IllegalStateException {
+ WeakReference r = this.table;
+ if (table == null) {
+ this.table = null;
+ if (r != null) {
+ r.clear();
+ }
+ } else if (r == null) {
+ this.table = new WeakReference(table);
+ } else {
+ throw new IllegalStateException("table already set");
+ }
+ }
+
+ /**
+ * 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/*Exception thrown during when attempting to map glyphs to associated characters + * in the case that the associated characters do not represent a compact interval.
+ * + * @author Glenn Adams + */ +public class IncompatibleSubtableException extends RuntimeException { + /** + * Instantiate incompatible subtable exception + */ + public IncompatibleSubtableException() { + super(); + } + /** + * Instantiate incompatible subtable exception + * @param message a message string + */ + public IncompatibleSubtableException(String message) { + super(message); + } +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFLanguage.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFLanguage.java new file mode 100644 index 00000000000..ffd9924fe60 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFLanguage.java @@ -0,0 +1,432 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +/** + *Language system tags defined by OTF specification. Note that this set and their + * values do not correspond with ISO639* or any other language registry.
+ * + * @author Glenn Adams + */ +public final class OTFLanguage { + public static final String ABAZA = "ABA"; + public static final String ABKHAZIAN = "ABK"; + public static final String ADYGHE = "ADY"; + public static final String AFRIKAANS = "AFK"; + public static final String AFAR = "AFR"; + public static final String AGAW = "AGW"; + public static final String ALSATIAN = "ALS"; + public static final String ALTAI = "ALT"; + public static final String AMHARIC = "AMH"; + public static final String PHONETIC_AMERICANIST = "APPH"; + public static final String ARABIC = "ARA"; + public static final String AARI = "ARI"; + public static final String ARAKANESE = "ARK"; + public static final String ASSAMESE = "ASM"; + public static final String ATHAPASKAN = "ATH"; + public static final String AVAR = "AVR"; + public static final String AWADHI = "AWA"; + public static final String AYMARA = "AYM"; + public static final String AZERI = "AZE"; + public static final String BADAGA = "BAD"; + public static final String BAGHELKHANDI = "BAG"; + public static final String BALKAR = "BAL"; + public static final String BAULE = "BAU"; + public static final String BERBER = "BBR"; + public static final String BENCH = "BCH"; + public static final String BIBLE_CREE = "BCR"; + public static final String BELARUSSIAN = "BEL"; + public static final String BEMBA = "BEM"; + public static final String BENGALI = "BEN"; + public static final String BULGARIAN = "BGR"; + public static final String BHILI = "BHI"; + public static final String BHOJPURI = "BHO"; + public static final String BIKOL = "BIK"; + public static final String BILEN = "BIL"; + public static final String BLACKFOOT = "BKF"; + public static final String BALOCHI = "BLI"; + public static final String BALANTE = "BLN"; + public static final String BALTI = "BLT"; + public static final String BAMBARA = "BMB"; + public static final String BAMILEKE = "BML"; + public static final String BOSNIAN = "BOS"; + public static final String BRETON = "BRE"; + public static final String BRAHUI = "BRH"; + public static final String BRAJ_BHASHA = "BRI"; + public static final String BURMESE = "BRM"; + public static final String BASHKIR = "BSH"; + public static final String BETI = "BTI"; + public static final String CATALAN = "CAT"; + public static final String CEBUANO = "CEB"; + public static final String CHECHEN = "CHE"; + public static final String CHAHA_GURAGE = "CHG"; + public static final String CHATTISGARHI = "CHH"; + public static final String CHICHEWA = "CHI"; + public static final String CHUKCHI = "CHK"; + public static final String CHIPEWYAN = "CHP"; + public static final String CHEROKEE = "CHR"; + public static final String CHUVASH = "CHU"; + public static final String COMORIAN = "CMR"; + public static final String COPTIC = "COP"; + public static final String CORSICAN = "COS"; + public static final String CREE = "CRE"; + public static final String CARRIER = "CRR"; + public static final String CRIMEAN_TATAR = "CRT"; + public static final String CHURCH_SLAVONIC = "CSL"; + public static final String CZECH = "CSY"; + public static final String DANISH = "DAN"; + public static final String DARGWA = "DAR"; + public static final String WOODS_CREE = "DCR"; + public static final String GERMAN = "DEU"; + public static final String DEFAULT = "dflt"; + public static final String DOGRI = "DGR"; + public static final String DHIVEHI_DEPRECATED = "DHV"; + public static final String DHIVEHI = "DIV"; + public static final String DJERMA = "DJR"; + public static final String DANGME = "DNG"; + public static final String DINKA = "DNK"; + public static final String DARI = "DRI"; + public static final String DUNGAN = "DUN"; + public static final String DZONGKHA = "DZN"; + public static final String EBIRA = "EBI"; + public static final String EASTERN_CREE = "ECR"; + public static final String EDO = "EDO"; + public static final String EFIK = "EFI"; + public static final String GREEK = "ELL"; + public static final String ENGLISH = "ENG"; + public static final String ERZYA = "ERZ"; + public static final String SPANISH = "ESP"; + public static final String ESTONIAN = "ETI"; + public static final String BASQUE = "EUQ"; + public static final String EVENKI = "EVK"; + public static final String EVEN = "EVN"; + public static final String EWE = "EWE"; + public static final String FRENCH_ANTILLEAN = "FAN"; + public static final String FARSI = "FAR"; + public static final String FINNISH = "FIN"; + public static final String FIJIAN = "FJI"; + public static final String FLEMISH = "FLE"; + public static final String FOREST_NENETS = "FNE"; + public static final String FON = "FON"; + public static final String FAROESE = "FOS"; + public static final String FRENCH = "FRA"; + public static final String FRISIAN = "FRI"; + public static final String FRIULIAN = "FRL"; + public static final String FUTA = "FTA"; + public static final String FULANI = "FUL"; + public static final String GA = "GAD"; + public static final String GAELIC = "GAE"; + public static final String GAGAUZ = "GAG"; + public static final String GALICIAN = "GAL"; + public static final String GARSHUNI = "GAR"; + public static final String GARHWALI = "GAW"; + public static final String GEEZ = "GEZ"; + public static final String GILYAK = "GIL"; + public static final String GUMUZ = "GMZ"; + public static final String GONDI = "GON"; + public static final String GREENLANDIC = "GRN"; + public static final String GARO = "GRO"; + public static final String GUARANI = "GUA"; + public static final String GUJARATI = "GUJ"; + public static final String HAITIAN = "HAI"; + public static final String HALAM = "HAL"; + public static final String HARAUTI = "HAR"; + public static final String HAUSA = "HAU"; + public static final String HAWAIIN = "HAW"; + public static final String HAMMER_BANNA = "HBN"; + public static final String HILIGAYNON = "HIL"; + public static final String HINDI = "HIN"; + public static final String HIGH_MARI = "HMA"; + public static final String HINDKO = "HND"; + public static final String HO = "HO"; + public static final String HARARI = "HRI"; + public static final String CROATIAN = "HRV"; + public static final String HUNGARIAN = "HUN"; + public static final String ARMENIAN = "HYE"; + public static final String IGBO = "IBO"; + public static final String IJO = "IJO"; + public static final String ILOKANO = "ILO"; + public static final String INDONESIAN = "IND"; + public static final String INGUSH = "ING"; + public static final String INUKTITUT = "INU"; + public static final String PHONETIC_IPA = "IPPH"; + public static final String IRISH = "IRI"; + public static final String IRISH_TRADITIONAL = "IRT"; + public static final String ICELANDIC = "ISL"; + public static final String INARI_SAMI = "ISM"; + public static final String ITALIAN = "ITA"; + public static final String HEBREW = "IWR"; + public static final String JAVANESE = "JAV"; + public static final String YIDDISH = "JII"; + public static final String JAPANESE = "JAN"; + public static final String JUDEZMO = "JUD"; + public static final String JULA = "JUL"; + public static final String KABARDIAN = "KAB"; + public static final String KACHCHI = "KAC"; + public static final String KALENJIN = "KAL"; + public static final String KANNADA = "KAN"; + public static final String KARACHAY = "KAR"; + public static final String GEORGIAN = "KAT"; + public static final String KAZAKH = "KAZ"; + public static final String KEBENA = "KEB"; + public static final String KHUTSURI_GEORGIAN = "KGE"; + public static final String KHAKASS = "KHA"; + public static final String KHANTY_KAZIM = "KHK"; + public static final String KHMER = "KHM"; + public static final String KHANTY_SHURISHKAR = "KHS"; + public static final String KHANTY_VAKHI = "KHV"; + public static final String KHOWAR = "KHW"; + public static final String KIKUYU = "KIK"; + public static final String KIRGHIZ = "KIR"; + public static final String KISII = "KIS"; + public static final String KOKNI = "KKN"; + public static final String KALMYK = "KLM"; + public static final String KAMBA = "KMB"; + public static final String KUMAONI = "KMN"; + public static final String KOMO = "KMO"; + public static final String KOMSO = "KMS"; + public static final String KANURI = "KNR"; + public static final String KODAGU = "KOD"; + public static final String KOREAN_OLD_HANGUL = "KOH"; + public static final String KONKANI = "KOK"; + public static final String KIKONGO = "KON"; + public static final String KOMI_PERMYAK = "KOP"; + public static final String KOREAN = "KOR"; + public static final String KOMI_ZYRIAN = "KOZ"; + public static final String KPELLE = "KPL"; + public static final String KRIO = "KRI"; + public static final String KARAKALPAK = "KRK"; + public static final String KARELIAN = "KRL"; + public static final String KARAIM = "KRM"; + public static final String KAREN = "KRN"; + public static final String KOORETE = "KRT"; + public static final String KASHMIRI = "KSH"; + public static final String KHASI = "KSI"; + public static final String KILDIN_SAMI = "KSM"; + public static final String KUI = "KUI"; + public static final String KULVI = "KUL"; + public static final String KUMYK = "KUM"; + public static final String KURDISH = "KUR"; + public static final String KURUKH = "KUU"; + public static final String KUY = "KUY"; + public static final String KORYAK = "KYK"; + public static final String LADIN = "LAD"; + public static final String LAHULI = "LAH"; + public static final String LAK = "LAK"; + public static final String LAMBANI = "LAM"; + public static final String LAO = "LAO"; + public static final String LATIN = "LAT"; + public static final String LAZ = "LAZ"; + public static final String L_CREE = "LCR"; + public static final String LADAKHI = "LDK"; + public static final String LEZGI = "LEZ"; + public static final String LINGALA = "LIN"; + public static final String LOW_MARI = "LMA"; + public static final String LIMBU = "LMB"; + public static final String LOMWE = "LMW"; + public static final String LOWER_SORBIAN = "LSB"; + public static final String LULE_SAMI = "LSM"; + public static final String LITHUANIAN = "LTH"; + public static final String LUXEMBOURGISH = "LTZ"; + public static final String LUBA = "LUB"; + public static final String LUGANDA = "LUG"; + public static final String LUHYA = "LUH"; + public static final String LUO = "LUO"; + public static final String LATVIAN = "LVI"; + public static final String MAJANG = "MAJ"; + public static final String MAKUA = "MAK"; + public static final String MALAYALAM_TRADITIONAL = "MAL"; + public static final String MANSI = "MAN"; + public static final String MAPUDUNGUN = "MAP"; + public static final String MARATHI = "MAR"; + public static final String MARWARI = "MAW"; + public static final String MBUNDU = "MBN"; + public static final String MANCHU = "MCH"; + public static final String MOOSE_CREE = "MCR"; + public static final String MENDE = "MDE"; + public static final String MEEN = "MEN"; + public static final String MIZO = "MIZ"; + public static final String MACEDONIAN = "MKD"; + public static final String MALE = "MLE"; + public static final String MALAGASY = "MLG"; + public static final String MALINKE = "MLN"; + public static final String MALAYALAM_REFORMED = "MLR"; + public static final String MALAY = "MLY"; + public static final String MANDINKA = "MND"; + public static final String MONGOLIAN = "MNG"; + public static final String MANIPURI = "MNI"; + public static final String MANINKA = "MNK"; + public static final String MANX_GAELIC = "MNX"; + public static final String MOHAWK = "MOH"; + public static final String MOKSHA = "MOK"; + public static final String MOLDAVIAN = "MOL"; + public static final String MON = "MON"; + public static final String MOROCCAN = "MOR"; + public static final String MAORI = "MRI"; + public static final String MAITHILI = "MTH"; + public static final String MALTESE = "MTS"; + public static final String MUNDARI = "MUN"; + public static final String NAGA_ASSAMESE = "NAG"; + public static final String NANAI = "NAN"; + public static final String NASKAPI = "NAS"; + public static final String N_CREE = "NCR"; + public static final String NDEBELE = "NDB"; + public static final String NDONGA = "NDG"; + public static final String NEPALI = "NEP"; + public static final String NEWARI = "NEW"; + public static final String NAGARI = "NGR"; + public static final String NORWAY_HOUSE_CREE = "NHC"; + public static final String NISI = "NIS"; + public static final String NIUEAN = "NIU"; + public static final String NKOLE = "NKL"; + public static final String NKO = "NKO"; + public static final String DUTCH = "NLD"; + public static final String NOGAI = "NOG"; + public static final String NORWEGIAN = "NOR"; + public static final String NORTHERN_SAMI = "NSM"; + public static final String NORTHERN_TAI = "NTA"; + public static final String ESPERANTO = "NTO"; + public static final String NYNORSK = "NYN"; + public static final String OCCITAN = "OCI"; + public static final String OJI_CREE = "OCR"; + public static final String OJIBWAY = "OJB"; + public static final String ORIYA = "ORI"; + public static final String OROMO = "ORO"; + public static final String OSSETIAN = "OSS"; + public static final String PALESTINIAN_ARAMAIC = "PAA"; + public static final String PALI = "PAL"; + public static final String PUNJABI = "PAN"; + public static final String PALPA = "PAP"; + public static final String PASHTO = "PAS"; + public static final String POLYTONIC_GREEK = "PGR"; + public static final String FILIPINO = "PIL"; + public static final String PALAUNG = "PLG"; + public static final String POLISH = "PLK"; + public static final String PROVENCAL = "PRO"; + public static final String PORTUGUESE = "PTG"; + public static final String CHIN = "QIN"; + public static final String RAJASTHANI = "RAJ"; + public static final String R_CREE = "RCR"; + public static final String RUSSIAN_BURIAT = "RBU"; + public static final String RIANG = "RIA"; + public static final String RHAETO_ROMANIC = "RMS"; + public static final String ROMANIAN = "ROM"; + public static final String ROMANY = "ROY"; + public static final String RUSYN = "RSY"; + public static final String RUANDA = "RUA"; + public static final String RUSSIAN = "RUS"; + public static final String SADRI = "SAD"; + public static final String SANSKRIT = "SAN"; + public static final String SANTALI = "SAT"; + public static final String SAYISI = "SAY"; + public static final String SEKOTA = "SEK"; + public static final String SELKUP = "SEL"; + public static final String SANGO = "SGO"; + public static final String SHAN = "SHN"; + public static final String SIBE = "SIB"; + public static final String SIDAMO = "SID"; + public static final String SILTE_GURAGE = "SIG"; + public static final String SKOLT_SAMI = "SKS"; + public static final String SLOVAK = "SKY"; + public static final String SLAVEY = "SLA"; + public static final String SLOVENIAN = "SLV"; + public static final String SOMALI = "SML"; + public static final String SAMOAN = "SMO"; + public static final String SENA = "SNA"; + public static final String SINDHI = "SND"; + public static final String SINHALESE = "SNH"; + public static final String SONINKE = "SNK"; + public static final String SODO_GURAGE = "SOG"; + public static final String SOTHO = "SOT"; + public static final String ALBANIAN = "SQI"; + public static final String SERBIAN = "SRB"; + public static final String SARAIKI = "SRK"; + public static final String SERER = "SRR"; + public static final String SOUTH_SLAVEY = "SSL"; + public static final String SOUTHERN_SAMI = "SSM"; + public static final String SURI = "SUR"; + public static final String SVAN = "SVA"; + public static final String SWEDISH = "SVE"; + public static final String SWADAYA_ARAMAIC = "SWA"; + public static final String SWAHILI = "SWK"; + public static final String SWAZI = "SWZ"; + public static final String SUTU = "SXT"; + public static final String SYRIAC = "SYR"; + public static final String TABASARAN = "TAB"; + public static final String TAJIKI = "TAJ"; + public static final String TAMIL = "TAM"; + public static final String TATAR = "TAT"; + public static final String TH_CREE = "TCR"; + public static final String TELUGU = "TEL"; + public static final String TONGAN = "TGN"; + public static final String TIGRE = "TGR"; + public static final String TIGRINYA = "TGY"; + public static final String THAI = "THA"; + public static final String TAHITIAN = "THT"; + public static final String TIBETAN = "TIB"; + public static final String TURKMEN = "TKM"; + public static final String TEMNE = "TMN"; + public static final String TSWANA = "TNA"; + public static final String TUNDRA_NENETS = "TNE"; + public static final String TONGA = "TNG"; + public static final String TODO = "TOD"; + public static final String TURKISH = "TRK"; + public static final String TSONGA = "TSG"; + public static final String TUROYO_ARAMAIC = "TUA"; + public static final String TULU = "TUL"; + public static final String TUVIN = "TUV"; + public static final String TWI = "TWI"; + public static final String UDMURT = "UDM"; + public static final String UKRAINIAN = "UKR"; + public static final String URDU = "URD"; + public static final String UPPER_SORBIAN = "USB"; + public static final String UYGHUR = "UYG"; + public static final String UZBEK = "UZB"; + public static final String VENDA = "VEN"; + public static final String VIETNAMESE = "VIT"; + public static final String WA = "WA"; + public static final String WAGDI = "WAG"; + public static final String WEST_CREE = "WCR"; + public static final String WELSH = "WEL"; + public static final String WILDCARD = "*"; + public static final String WOLOF = "WLF"; + public static final String TAI_LUE = "XBD"; + public static final String XHOSA = "XHS"; + public static final String SAKHA = "YAK"; + public static final String YORUBA = "YBA"; + public static final String Y_CREE = "YCR"; + public static final String YI_CLASSIC = "YIC"; + public static final String YI_MODERN = "YIM"; + public static final String CHINESE_HONG_KONG_SAR = "ZHH"; + public static final String CHINESE_PHONETIC = "ZHP"; + public static final String CHINESE_SIMPLIFIED = "ZHS"; + public static final String CHINESE_TRADITIONAL = "ZHT"; + public static final String ZANDE = "ZND"; + public static final String ZULU = "ZUL"; + + public static boolean isDefault(String language) { + return (language != null) && language.equals(DEFAULT); + } + + public static boolean isWildCard(String language) { + return (language != null) && language.equals(WILDCARD); + } + + private OTFLanguage() { + } +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFScript.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFScript.java new file mode 100644 index 00000000000..7a373dc2831 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/OTFScript.java @@ -0,0 +1,152 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +/** + *Script tags defined by OTF specification. Note that this set and their + * values do not correspond with ISO 15924 or Unicode Script names.
+ * + * @author Glenn Adams + */ +public final class OTFScript { + public static final String ARABIC = "arab"; + public static final String ARMENIAN = "armn"; + public static final String AVESTAN = "avst"; + public static final String BALINESE = "bali"; + public static final String BAMUM = "bamu"; + public static final String BATAK = "batk"; + public static final String BENGALI = "beng"; + public static final String BENGALI_V2 = "bng2"; + public static final String BOPOMOFO = "bopo"; + public static final String BRAILLE = "brai"; + public static final String BRAHMI = "brah"; + public static final String BUGINESE = "bugi"; + public static final String BUHID = "buhd"; + public static final String BYZANTINE_MUSIC = "byzm"; + public static final String CANADIAN_SYLLABICS = "cans"; + public static final String CARIAN = "cari"; + public static final String CHAKMA = "cakm"; + public static final String CHAM = "cham"; + public static final String CHEROKEE = "cher"; + public static final String CJK_IDEOGRAPHIC = "hani"; + public static final String COPTIC = "copt"; + public static final String CYPRIOT_SYLLABARY = "cprt"; + public static final String CYRILLIC = "cyrl"; + public static final String DEFAULT = "DFLT"; + public static final String DESERET = "dsrt"; + public static final String DEVANAGARI = "deva"; + public static final String DEVANAGARI_V2 = "dev2"; + public static final String EGYPTIAN_HEIROGLYPHS = "egyp"; + public static final String ETHIOPIC = "ethi"; + public static final String GEORGIAN = "geor"; + public static final String GLAGOLITIC = "glag"; + public static final String GOTHIC = "goth"; + public static final String GREEK = "grek"; + public static final String GUJARATI = "gujr"; + public static final String GUJARATI_V2 = "gjr2"; + public static final String GURMUKHI = "guru"; + public static final String GURMUKHI_V2 = "gur2"; + public static final String HANGUL = "hang"; + public static final String HANGUL_JAMO = "jamo"; + public static final String HANUNOO = "hano"; + public static final String HEBREW = "hebr"; + public static final String HIRAGANA = "kana"; + public static final String IMPERIAL_ARAMAIC = "armi"; + public static final String INSCRIPTIONAL_PAHLAVI = "phli"; + public static final String INSCRIPTIONAL_PARTHIAN = "prti"; + public static final String JAVANESE = "java"; + public static final String KAITHI = "kthi"; + public static final String KANNADA = "knda"; + public static final String KANNADA_V2 = "knd2"; + public static final String KATAKANA = "kana"; + public static final String KAYAH_LI = "kali"; + public static final String KHAROSTHI = "khar"; + public static final String KHMER = "khmr"; + public static final String LAO = "lao"; + public static final String LATIN = "latn"; + public static final String LEPCHA = "lepc"; + public static final String LIMBU = "limb"; + public static final String LINEAR_B = "linb"; + public static final String LISU = "lisu"; + public static final String LYCIAN = "lyci"; + public static final String LYDIAN = "lydi"; + public static final String MALAYALAM = "mlym"; + public static final String MALAYALAM_V2 = "mlm2"; + public static final String MANDAIC = "mand"; + public static final String MATHEMATICAL_ALPHANUMERIC_SYMBOLS = "math"; + public static final String MEITEI = "mtei"; + public static final String MEROITIC_CURSIVE = "merc"; + public static final String MEROITIC_HIEROGLYPHS = "mero"; + public static final String MONGOLIAN = "mong"; + public static final String MUSICAL_SYMBOLS = "musc"; + public static final String MYANMAR = "mymr"; + public static final String NEW_TAI_LUE = "talu"; + public static final String NKO = "nko"; + public static final String OGHAM = "ogam"; + public static final String OL_CHIKI = "olck"; + public static final String OLD_ITALIC = "ital"; + public static final String OLD_PERSIAN_CUNEIFORM = "xpeo"; + public static final String OLD_SOUTH_ARABIAN = "sarb"; + public static final String OLD_TURKIC = "orkh"; + public static final String ORIYA = "orya"; + public static final String ORIYA_V2 = "ory2"; + public static final String OSMANYA = "osma"; + public static final String PHAGS_PA = "phag"; + public static final String PHOENICIAN = "phnx"; + public static final String REJANG = "rjng"; + public static final String RUNIC = "runr"; + public static final String SAMARITAN = "samr"; + public static final String SAURASHTRA = "saur"; + public static final String SHARADA = "shrd"; + public static final String SHAVIAN = "shaw"; + public static final String SINHALA = "sinh"; + public static final String SORA_SOMPENG = "sora"; + public static final String SUMERO_AKKADIAN_CUNEIFORM = "xsux"; + public static final String SUNDANESE = "sund"; + public static final String SYLOTI_NAGRI = "sylo"; + public static final String SYRIAC = "syrc"; + public static final String TAGALOG = "tglg"; + public static final String TAGBANWA = "tagb"; + public static final String TAI_LE = "tale"; + public static final String TAI_THAM = "lana"; + public static final String TAI_VIET = "tavt"; + public static final String TAKRI = "takr"; + public static final String TAMIL = "taml"; + public static final String TAMIL_V2 = "tml2"; + public static final String TELUGU = "telu"; + public static final String TELUGU_V2 = "tel2"; + public static final String THAANA = "thaa"; + public static final String THAI = "thai"; + public static final String TIBETAN = "tibt"; + public static final String TIFINAGH = "tfng"; + public static final String UGARITIC_CUNEIFORM = "ugar"; + public static final String VAI = "vai"; + public static final String WILDCARD = "*"; + public static final String YI = "yi"; + + public static boolean isDefault(String script) { + return (script != null) && script.equals(DEFAULT); + } + + public static boolean isWildCard(String script) { + return (script != null) && script.equals(DEFAULT); + } + + private OTFScript() { + } +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Positionable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Positionable.java new file mode 100644 index 00000000000..8ecc09d6c6b --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Positionable.java @@ -0,0 +1,55 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +/** + *Optional interface which indicates that glyph positioning is supported and, if supported, + * can perform positioning.
+ * + * @author Glenn Adams + */ +public interface Positionable { + + /** + * Determines if font performs glyph positioning. + * @return true if performs positioning + */ + boolean performsPositioning(); + + /** + * Perform glyph positioning. + * @param cs character sequence to map to position offsets (advancement adjustments) + * @param script a script identifier + * @param language a language identifier + * @param fontSize font size + * @return 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, or null if no non-zero adjustment applies + */ + int[][] performPositioning(CharSequence cs, String script, String language, int fontSize); + + /** + * Perform glyph positioning using an implied font size. + * @param cs character sequence to map to position offsets (advancement adjustments) + * @param script a script identifier + * @param language a language identifier + * @return 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, or null if no non-zero adjustment applies + */ + int[][] performPositioning(CharSequence cs, String script, String language); + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Substitutable.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Substitutable.java new file mode 100644 index 00000000000..ce71cb4e209 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/Substitutable.java @@ -0,0 +1,65 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced; + +import java.util.List; + +/** + *Optional interface which indicates that glyph substitution is supported and, if supported, + * can perform substitution.
+ * + * @author Glenn Adams + */ +public interface Substitutable { + + /** + * Determines if font performs glyph substitution. + * @return true if performs substitution. + */ + boolean performsSubstitution(); + + /** + * Perform substitutions on characters to effect glyph substitution. If some substitution is performed, it + * entails mapping from one or more input characters denoting textual character information to one or more + * output character codes denoting glyphs in this font, where the output character codes may make use of + * private character code values that have significance only for this font. + * @param cs character sequence to map to output font encoding character sequence + * @param script a script identifier + * @param language a language identifier + * @param associations optional list to receive list of character associations + * @param retainControls if true, then retain control characters and their glyph mappings, otherwise remove + * @return output sequence (represented as a character sequence, where each character in the returned sequence + * denotes "font characters", i.e., character codes that map directly (1-1) to their associated glyphs + */ + CharSequence performSubstitution(CharSequence cs, String script, String language, List associations, boolean retainControls); + + /** + * Reorder combining marks in character sequence so that they precede (within the sequence) the base + * character to which they are applied. N.B. In the case of LTR segments, marks are not reordered by this, + * method since when the segment is reversed by BIDI processing, marks are automatically reordered to precede + * their base character. + * @param cs character sequence within which combining marks to be reordered + * @param gpa associated glyph position adjustments (also reordered) + * @param script a script identifier + * @param language a language identifier + * @param associations optional list of associations to be reordered + * @return output sequence containing reordered "font characters" + */ + CharSequence reorderCombiningMarks(CharSequence cs, int[][] gpa, String script, String language, List associations); + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiClass.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiClass.java new file mode 100644 index 00000000000..affd88c5a09 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiClass.java @@ -0,0 +1,267 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced.bidi; + +import java.util.Arrays; + +// CSOFF: LineLengthCheck + +/* + * !!! THIS IS A GENERATED FILE !!! + * If updates to the source are needed, then: + * - apply the necessary modifications to + * 'src/codegen/unicode/java/org/apache/fop/complexscripts/bidi/GenerateBidiClass.java' + * - run 'ant codegen-unicode', which will generate a new BidiClass.java + * in 'src/java/org/apache/fop/complexscripts/bidi' + * - commit BOTH changed files + */ + +/** Bidirectional class utilities. */ +public final class BidiClass { + +private BidiClass() { +} + +private static byte[] bcL1 = { +15,15,15,15,15,15,15,15,15,17,16,17,18,16,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,17,18,19,19,11,11,11,19,19,19, +19,19,10,13,10,13,13,9,9,9,9,9,9,9,9,9,9,13,19,19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19, +19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,19,19,19,15,15,15,15,15,15,16,15,15,15,15,15,15,15,15,15,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,13,19,11,11,11,11,19,19,19,19,1,19,19,15,19,19,11,11,9,9,19,1,19,19,19,9,1, +19,19,19,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, +1,1,19,1,1,1,1,1,1,1,1 +}; + +private static byte[] bcR1 = { +4,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14, +14,14,14,14,14,4,14,4,14,14,4,14,14,4,14,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, +4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,12,12,12,12,5,5,19,19,5,11,11,5,13,5,19,19,14,14,14,14,14,14,14,14,14,14,14,5,5,5,5,5,5,5,5, +5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,14,14,14,14,14,14,14, +14,14,14,14,14,14,14,12,12,12,12,12,12,12,12,12,12,11,12,12,5,5,5,14,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, +5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, +5,5,5,5,5,5,5,5,5,5,5,5,5,14,14,14,14,14,14,14,12,19,14,14,14,14,14,14,5,5,14,14,19,14,14,14,14,5,5,9,9,9,9,9,9,9,9,9,9,5, +5,5,5,5,5 +}; + +private static int[] bcS1 = { +256,443,444,448,452,660,661,688,697,699,706,710,720,722,736,741,748,749,750,751,768,880,884,885,886,890,891,894,900,902,903, +904,908,910,931,1014,1015,1154,1155,1160,1162,1329,1369,1370,1377,1417,1418,1792,1806,1807,1808,1809,1810,1840,1867,1869,1958, +1969,1970,1984,1994,2027,2036,2038,2039,2042,2043,2048,2070,2074,2075,2084,2085,2088,2089,2094,2096,2111,2112,2137,2140,2142, +2143,2304,2307,2308,2362,2363,2364,2365,2366,2369,2377,2381,2382,2384,2385,2392,2402,2404,2406,2416,2417,2418,2425,2433,2434, +2437,2447,2451,2474,2482,2486,2492,2493,2494,2497,2503,2507,2509,2510,2519,2524,2527,2530,2534,2544,2546,2548,2554,2555,2561, +2563,2565,2575,2579,2602,2610,2613,2616,2620,2622,2625,2631,2635,2641,2649,2654,2662,2672,2674,2677,2689,2691,2693,2703,2707, +2730,2738,2741,2748,2749,2750,2753,2759,2761,2763,2765,2768,2784,2786,2790,2801,2817,2818,2821,2831,2835,2858,2866,2869,2876, +2877,2878,2879,2880,2881,2887,2891,2893,2902,2903,2908,2911,2914,2918,2928,2929,2930,2946,2947,2949,2958,2962,2969,2972,2974, +2979,2984,2990,3006,3008,3009,3014,3018,3021,3024,3031,3046,3056,3059,3065,3066,3073,3077,3086,3090,3114,3125,3133,3134,3137, +3142,3146,3157,3160,3168,3170,3174,3192,3199,3202,3205,3214,3218,3242,3253,3260,3261,3262,3263,3264,3270,3271,3274,3276,3285, +3294,3296,3298,3302,3313,3330,3333,3342,3346,3389,3390,3393,3398,3402,3405,3406,3415,3424,3426,3430,3440,3449,3450,3458,3461, +3482,3507,3517,3520,3530,3535,3538,3542,3544,3570,3572,3585,3633,3634,3636,3647,3648,3654,3655,3663,3664,3674,3713,3716,3719, +3722,3725,3732,3737,3745,3749,3751,3754,3757,3761,3762,3764,3771,3773,3776,3782,3784,3792,3804,3840,3841,3844,3859,3864,3866, +3872,3882,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3902,3904,3913,3953,3967,3968,3973,3974,3976,3981,3993,4030,4038, +4039,4046,4048,4053,4057,4096,4139,4141,4145,4146,4152,4153,4155,4157,4159,4160,4170,4176,4182,4184,4186,4190,4193,4194,4197, +4199,4206,4209,4213,4226,4227,4229,4231,4237,4238,4239,4240,4250,4253,4254,4256,4304,4347,4348,4352,4682,4688,4696,4698,4704, +4746,4752,4786,4792,4800,4802,4808,4824,4882,4888,4957,4960,4961,4969,4992,5008,5024,5120,5121,5741,5743,5760,5761,5787,5788, +5792,5867,5870,5888,5902,5906,5920,5938,5941,5952,5970,5984,5998,6002,6016,6068,6070,6071,6078,6086,6087,6089,6100,6103,6104, +6107,6108,6109,6112,6128,6144,6150,6151,6155,6158,6160,6176,6211,6212,6272,6313,6314,6320,6400,6432,6435,6439,6441,6448,6450, +6451,6457,6464,6468,6470,6480,6512,6528,6576,6593,6600,6608,6618,6622,6656,6679,6681,6686,6688,6741,6742,6743,6744,6752,6753, +6754,6755,6757,6765,6771,6783,6784,6800,6816,6823,6824,6912,6916,6917,6964,6965,6966,6971,6972,6973,6978,6979,6981,6992,7002, +7009,7019,7028,7040,7042,7043,7073,7074,7078,7080,7082,7086,7088,7104,7142,7143,7144,7146,7149,7150,7151,7154,7164,7168,7204, +7212,7220,7222,7227,7232,7245,7248,7258,7288,7294,7376,7379,7380,7393,7394,7401,7405,7406,7410,7424,7468,7522,7544,7545,7579, +7616,7676,7680,7960,7968,8008,8016,8025,8027,8029,8031,8064,8118,8125,8126,8127,8130,8134,8141,8144,8150,8157,8160,8173,8178, +8182,8189,8192,8203,8206,8207,8208,8214,8216,8217,8218,8219,8221,8222,8223,8224,8232,8233,8234,8235,8236,8237,8238,8239,8240, +8245,8249,8250,8251,8255,8257,8260,8261,8262,8263,8274,8275,8276,8277,8287,8288,8293,8298,8304,8305,8308,8314,8316,8317,8318, +8319,8320,8330,8332,8333,8334,8336,8352,8400,8413,8417,8418,8421,8448,8450,8451,8455,8456,8458,8468,8469,8470,8472,8473,8478, +8484,8485,8486,8487,8488,8489,8490,8494,8495,8501,8505,8506,8508,8512,8517,8522,8523,8524,8526,8527,8528,8544,8579,8581,8585, +8592,8597,8602,8604,8608,8609,8611,8612,8614,8615,8622,8623,8654,8656,8658,8659,8660,8661,8692,8722,8723,8724,8960,8968,8972, +8992,8994,9001,9002,9003,9014,9083,9084,9085,9109,9110,9115,9140,9180,9186,9216,9280,9312,9352,9372,9450,9472,9655,9656,9665, +9666,9720,9728,9839,9840,9900,9901,9985,10088,10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100,10101, +10102,10132,10176,10181,10182,10183,10188,10190,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10224,10240,10496, +10627,10628,10629,10630,10631,10632,10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646,10647, +10648,10649,10712,10713,10714,10715,10716,10748,10749,10750,11008,11056,11077,11079,11088,11264,11312,11360,11389,11390,11493, +11499,11503,11513,11517,11518,11520,11568,11631,11632,11647,11648,11680,11688,11696,11704,11712,11720,11728,11736,11744,11776, +11778,11779,11780,11781,11782,11785,11786,11787,11788,11789,11790,11799,11800,11802,11803,11804,11805,11806,11808,11809,11810, +11811,11812,11813,11814,11815,11816,11817,11818,11823,11824,11904,11931,12032,12272,12288,12289,12292,12293,12294,12295,12296, +12297,12298,12299,12300,12301,12302,12303,12304,12305,12306,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317,12318, +12320,12321,12330,12336,12337,12342,12344,12347,12348,12349,12350,12353,12441,12443,12445,12447,12448,12449,12539,12540,12543, +12549,12593,12688,12690,12694,12704,12736,12784,12800,12829,12832,12842,12880,12881,12896,12924,12927,12928,12938,12977,12992, +13004,13008,13056,13175,13179,13278,13280,13311,13312,19904,19968,40960,40981,40982,42128,42192,42232,42238,42240,42508,42509, +42512,42528,42538,42560,42606,42607,42608,42611,42620,42622,42623,42624,42656,42726,42736,42738,42752,42775,42784,42786,42864, +42865,42888,42889,42891,42896,42912,43002,43003,43010,43011,43014,43015,43019,43020,43043,43045,43047,43048,43056,43062,43064, +43065,43072,43124,43136,43138,43188,43204,43214,43216,43232,43250,43256,43259,43264,43274,43302,43310,43312,43335,43346,43359, +43360,43392,43395,43396,43443,43444,43446,43450,43452,43453,43457,43471,43472,43486,43520,43561,43567,43569,43571,43573,43584, +43587,43588,43596,43597,43600,43612,43616,43632,43633,43639,43642,43643,43648,43696,43697,43698,43701,43703,43705,43710,43712, +43713,43714,43739,43741,43742,43777,43785,43793,43808,43816,43968,44003,44005,44006,44008,44009,44011,44012,44013,44016,44032, +55216,55243,57344,63744,64048,64112,64256,64275,64285,64286,64287,64297,64298,64311,64312,64317,64318,64319,64320,64322,64323, +64325,64326,64336,64434,64450,64467,64830,64831,64832,64848,64912,64914,64968,64976,65008,65020,65021,65022,65024,65040,65047, +65048,65049,65056,65072,65073,65075,65077,65078,65079,65080,65081,65082,65083,65084,65085,65086,65087,65088,65089,65090,65091, +65092,65093,65095,65096,65097,65101,65104,65105,65106,65108,65109,65110,65112,65113,65114,65115,65116,65117,65118,65119,65120, +65122,65123,65124,65128,65129,65130,65131,65136,65141,65142,65277,65279,65281,65283,65284,65285,65286,65288,65289,65290,65291, +65292,65293,65294,65296,65306,65307,65308,65311,65313,65339,65340,65341,65342,65343,65344,65345,65371,65372,65373,65374,65375, +65376,65377,65378,65379,65380,65382,65392,65393,65438,65440,65474,65482,65490,65498,65504,65506,65507,65508,65509,65512,65513, +65517,65520,65529,65532,65534,65536,65549,65576,65596,65599,65616,65664,65792,65793,65794,65799,65847,65856,65909,65913,65930, +65936,66000,66045,66176,66208,66304,66336,66352,66369,66370,66378,66432,66463,66464,66504,66512,66513,66560,66640,66720,67584, +67590,67592,67593,67594,67638,67639,67641,67644,67645,67647,67670,67671,67672,67680,67840,67862,67868,67871,67872,67898,67903, +67904,68096,68097,68100,68101,68103,68108,68112,68116,68117,68120,68121,68148,68152,68155,68159,68160,68168,68176,68185,68192, +68221,68223,68224,68352,68406,68409,68416,68438,68440,68448,68467,68472,68480,68608,68681,69216,69247,69632,69633,69634,69635, +69688,69703,69714,69734,69760,69762,69763,69808,69811,69815,69817,69819,69821,69822,73728,74752,74864,77824,92160,110592,118784, +119040,119081,119141,119143,119146,119149,119155,119163,119171,119173,119180,119210,119214,119296,119362,119365,119552,119648, +119808,119894,119966,119970,119973,119977,119982,119995,119997,120005,120071,120077,120086,120094,120123,120128,120134,120138, +120146,120488,120513,120514,120539,120540,120571,120572,120597,120598,120629,120630,120655,120656,120687,120688,120713,120714, +120745,120746,120771,120772,120782,124928,126976,127024,127136,127153,127169,127185,127232,127248,127280,127344,127462,127504, +127552,127568,127744,127792,127799,127872,127904,127942,127968,128000,128064,128066,128140,128141,128249,128256,128292,128293, +128336,128507,128513,128530,128534,128536,128538,128540,128544,128552,128557,128560,128565,128581,128640,128768,131070,131072, +173824,177984,194560,196606,262142,327678,393214,458750,524286,589822,655358,720894,786430,851966,917502,917505,917506,917536, +917632,917760,918000,983038,983040,1048574,1048576,1114110 +}; + +private static int[] bcE1 = { +442,443,447,451,659,660,687,696,698,705,709,719,721,735,740,747,748,749,750,767,879,883,884,885,887,890,893,894,901,902,903, +906,908,929,1013,1014,1153,1154,1159,1161,1319,1366,1369,1375,1415,1417,1418,1805,1806,1807,1808,1809,1839,1866,1868,1957, +1968,1969,1983,1993,2026,2035,2037,2038,2041,2042,2047,2069,2073,2074,2083,2084,2087,2088,2093,2095,2110,2111,2136,2139,2141, +2142,2303,2306,2307,2361,2362,2363,2364,2365,2368,2376,2380,2381,2383,2384,2391,2401,2403,2405,2415,2416,2417,2423,2431,2433, +2435,2444,2448,2472,2480,2482,2489,2492,2493,2496,2500,2504,2508,2509,2510,2519,2525,2529,2531,2543,2545,2547,2553,2554,2555, +2562,2563,2570,2576,2600,2608,2611,2614,2617,2620,2624,2626,2632,2637,2641,2652,2654,2671,2673,2676,2677,2690,2691,2701,2705, +2728,2736,2739,2745,2748,2749,2752,2757,2760,2761,2764,2765,2768,2785,2787,2799,2801,2817,2819,2828,2832,2856,2864,2867,2873, +2876,2877,2878,2879,2880,2884,2888,2892,2893,2902,2903,2909,2913,2915,2927,2928,2929,2935,2946,2947,2954,2960,2965,2970,2972, +2975,2980,2986,3001,3007,3008,3010,3016,3020,3021,3024,3031,3055,3058,3064,3065,3066,3075,3084,3088,3112,3123,3129,3133,3136, +3140,3144,3149,3158,3161,3169,3171,3183,3198,3199,3203,3212,3216,3240,3251,3257,3260,3261,3262,3263,3268,3270,3272,3275,3277, +3286,3294,3297,3299,3311,3314,3331,3340,3344,3386,3389,3392,3396,3400,3404,3405,3406,3415,3425,3427,3439,3445,3449,3455,3459, +3478,3505,3515,3517,3526,3530,3537,3540,3542,3551,3571,3572,3632,3633,3635,3642,3647,3653,3654,3662,3663,3673,3675,3714,3716, +3720,3722,3725,3735,3743,3747,3749,3751,3755,3760,3761,3763,3769,3772,3773,3780,3782,3789,3801,3805,3840,3843,3858,3863,3865, +3871,3881,3891,3892,3893,3894,3895,3896,3897,3898,3899,3900,3901,3903,3911,3948,3966,3967,3972,3973,3975,3980,3991,4028,4037, +4038,4044,4047,4052,4056,4058,4138,4140,4144,4145,4151,4152,4154,4156,4158,4159,4169,4175,4181,4183,4185,4189,4192,4193,4196, +4198,4205,4208,4212,4225,4226,4228,4230,4236,4237,4238,4239,4249,4252,4253,4255,4293,4346,4347,4348,4680,4685,4694,4696,4701, +4744,4749,4784,4789,4798,4800,4805,4822,4880,4885,4954,4959,4960,4968,4988,5007,5017,5108,5120,5740,5742,5759,5760,5786,5787, +5788,5866,5869,5872,5900,5905,5908,5937,5940,5942,5969,5971,5996,6000,6003,6067,6069,6070,6077,6085,6086,6088,6099,6102,6103, +6106,6107,6108,6109,6121,6137,6149,6150,6154,6157,6158,6169,6210,6211,6263,6312,6313,6314,6389,6428,6434,6438,6440,6443,6449, +6450,6456,6459,6464,6469,6479,6509,6516,6571,6592,6599,6601,6617,6618,6655,6678,6680,6683,6687,6740,6741,6742,6743,6750,6752, +6753,6754,6756,6764,6770,6780,6783,6793,6809,6822,6823,6829,6915,6916,6963,6964,6965,6970,6971,6972,6977,6978,6980,6987,7001, +7008,7018,7027,7036,7041,7042,7072,7073,7077,7079,7081,7082,7087,7097,7141,7142,7143,7145,7148,7149,7150,7153,7155,7167,7203, +7211,7219,7221,7223,7231,7241,7247,7257,7287,7293,7295,7378,7379,7392,7393,7400,7404,7405,7409,7410,7467,7521,7543,7544,7578, +7615,7654,7679,7957,7965,8005,8013,8023,8025,8027,8029,8061,8116,8124,8125,8126,8129,8132,8140,8143,8147,8155,8159,8172,8175, +8180,8188,8190,8202,8205,8206,8207,8213,8215,8216,8217,8218,8220,8221,8222,8223,8231,8232,8233,8234,8235,8236,8237,8238,8239, +8244,8248,8249,8250,8254,8256,8259,8260,8261,8262,8273,8274,8275,8276,8286,8287,8292,8297,8303,8304,8305,8313,8315,8316,8317, +8318,8319,8329,8331,8332,8333,8334,8348,8377,8412,8416,8417,8420,8432,8449,8450,8454,8455,8457,8467,8468,8469,8471,8472,8477, +8483,8484,8485,8486,8487,8488,8489,8493,8494,8500,8504,8505,8507,8511,8516,8521,8522,8523,8525,8526,8527,8543,8578,8580,8584, +8585,8596,8601,8603,8607,8608,8610,8611,8613,8614,8621,8622,8653,8655,8657,8658,8659,8660,8691,8721,8722,8723,8959,8967,8971, +8991,8993,9000,9001,9002,9013,9082,9083,9084,9108,9109,9114,9139,9179,9185,9203,9254,9290,9351,9371,9449,9471,9654,9655,9664, +9665,9719,9727,9838,9839,9899,9900,9983,10087,10088,10089,10090,10091,10092,10093,10094,10095,10096,10097,10098,10099,10100, +10101,10131,10175,10180,10181,10182,10186,10188,10213,10214,10215,10216,10217,10218,10219,10220,10221,10222,10223,10239,10495, +10626,10627,10628,10629,10630,10631,10632,10633,10634,10635,10636,10637,10638,10639,10640,10641,10642,10643,10644,10645,10646, +10647,10648,10711,10712,10713,10714,10715,10747,10748,10749,11007,11055,11076,11078,11084,11097,11310,11358,11388,11389,11492, +11498,11502,11505,11516,11517,11519,11557,11621,11631,11632,11647,11670,11686,11694,11702,11710,11718,11726,11734,11742,11775, +11777,11778,11779,11780,11781,11784,11785,11786,11787,11788,11789,11798,11799,11801,11802,11803,11804,11805,11807,11808,11809, +11810,11811,11812,11813,11814,11815,11816,11817,11822,11823,11825,11929,12019,12245,12283,12288,12291,12292,12293,12294,12295, +12296,12297,12298,12299,12300,12301,12302,12303,12304,12305,12307,12308,12309,12310,12311,12312,12313,12314,12315,12316,12317, +12319,12320,12329,12335,12336,12341,12343,12346,12347,12348,12349,12351,12438,12442,12444,12446,12447,12448,12538,12539,12542, +12543,12589,12686,12689,12693,12703,12730,12771,12799,12828,12830,12841,12879,12880,12895,12923,12926,12927,12937,12976,12991, +13003,13007,13054,13174,13178,13277,13279,13310,13311,19893,19967,40907,40980,40981,42124,42182,42231,42237,42239,42507,42508, +42511,42527,42537,42539,42605,42606,42607,42610,42611,42621,42622,42623,42647,42725,42735,42737,42743,42774,42783,42785,42863, +42864,42887,42888,42890,42894,42897,42921,43002,43009,43010,43013,43014,43018,43019,43042,43044,43046,43047,43051,43061,43063, +43064,43065,43123,43127,43137,43187,43203,43204,43215,43225,43249,43255,43258,43259,43273,43301,43309,43311,43334,43345,43347, +43359,43388,43394,43395,43442,43443,43445,43449,43451,43452,43456,43469,43471,43481,43487,43560,43566,43568,43570,43572,43574, +43586,43587,43595,43596,43597,43609,43615,43631,43632,43638,43641,43642,43643,43695,43696,43697,43700,43702,43704,43709,43711, +43712,43713,43714,43740,43741,43743,43782,43790,43798,43814,43822,44002,44004,44005,44007,44008,44010,44011,44012,44013,44025, +55203,55238,55291,63743,64045,64109,64217,64262,64279,64285,64286,64296,64297,64310,64311,64316,64317,64318,64319,64321,64322, +64324,64325,64335,64433,64449,64466,64829,64830,64831,64847,64911,64913,64967,64975,65007,65019,65020,65021,65023,65039,65046, +65047,65048,65049,65062,65072,65074,65076,65077,65078,65079,65080,65081,65082,65083,65084,65085,65086,65087,65088,65089,65090, +65091,65092,65094,65095,65096,65100,65103,65104,65105,65106,65108,65109,65111,65112,65113,65114,65115,65116,65117,65118,65119, +65121,65122,65123,65126,65128,65129,65130,65131,65140,65141,65276,65278,65279,65282,65283,65284,65285,65287,65288,65289,65290, +65291,65292,65293,65295,65305,65306,65307,65310,65312,65338,65339,65340,65341,65342,65343,65344,65370,65371,65372,65373,65374, +65375,65376,65377,65378,65379,65381,65391,65392,65437,65439,65470,65479,65487,65495,65500,65505,65506,65507,65508,65510,65512, +65516,65518,65528,65531,65533,65535,65547,65574,65594,65597,65613,65629,65786,65792,65793,65794,65843,65855,65908,65912,65929, +65930,65947,66044,66045,66204,66256,66334,66339,66368,66369,66377,66378,66461,66463,66499,66511,66512,66517,66639,66717,66729, +67589,67591,67592,67593,67637,67638,67640,67643,67644,67646,67669,67670,67671,67679,67839,67861,67867,67870,67871,67897,67902, +67903,68095,68096,68099,68100,68102,68107,68111,68115,68116,68119,68120,68147,68151,68154,68158,68159,68167,68175,68184,68191, +68220,68222,68223,68351,68405,68408,68415,68437,68439,68447,68466,68471,68479,68607,68680,69215,69246,69631,69632,69633,69634, +69687,69702,69709,69733,69743,69761,69762,69807,69810,69814,69816,69818,69820,69821,69825,74606,74850,74867,78894,92728,110593, +119029,119078,119140,119142,119145,119148,119154,119162,119170,119172,119179,119209,119213,119261,119361,119364,119365,119638, +119665,119892,119964,119967,119970,119974,119980,119993,119995,120003,120069,120074,120084,120092,120121,120126,120132,120134, +120144,120485,120512,120513,120538,120539,120570,120571,120596,120597,120628,120629,120654,120655,120686,120687,120712,120713, +120744,120745,120770,120771,120779,120831,126975,127019,127123,127150,127166,127183,127199,127242,127278,127337,127386,127490, +127546,127560,127569,127776,127797,127868,127891,127940,127946,127984,128062,128064,128139,128140,128247,128252,128291,128292, +128317,128359,128511,128528,128532,128534,128536,128538,128542,128549,128555,128557,128563,128576,128591,128709,128883,131071, +173782,177972,178205,195101,196607,262143,327679,393215,458751,524287,589823,655359,720895,786431,851967,917504,917505,917535, +917631,917759,917999,921599,983039,1048573,1048575,1114109,1114111 +}; + +private static byte[] bcC1 = { +1,1,1,1,1,1,1,1,19,1,19,19,1,19,1,19,19,19,1,19,14,1,19,19,1,1,1,19,19,1,19,1,1,1,1,19,1,1,14,14,1,1,1,1,1,1,19,5,5,12,5,14, +5,14,5,5,14,5,5,4,4,14,4,19,19,4,4,4,14,4,14,4,14,4,14,4,4,4,4,14,4,4,4,14,1,1,14,1,14,1,1,14,1,14,1,1,14,1,14,1,1,1,1,1,1, +14,1,1,1,1,1,1,1,14,1,1,14,1,1,14,1,1,1,1,14,1,1,11,1,1,11,14,1,1,1,1,1,1,1,1,14,1,14,14,14,14,1,1,1,14,1,14,14,1,1,1,1,1, +1,1,14,1,1,14,14,1,1,14,1,1,14,1,11,14,1,1,1,1,1,1,1,14,1,1,14,1,14,1,1,14,14,1,1,1,14,1,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,14, +1,1,1,14,1,1,1,1,19,11,19,1,1,1,1,1,1,1,14,1,14,14,14,1,1,14,1,19,1,1,1,1,1,1,1,14,1,1,1,1,1,1,1,14,1,1,1,14,1,1,1,1,1,1,1, +1,14,1,1,14,1,1,1,14,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1,1,1,14,1,14,11,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,14,14,1,1, +1,14,1,1,1,1,1,1,14,1,1,1,1,14,1,14,1,14,19,19,19,19,1,1,1,14,1,14,1,14,1,14,14,1,14,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1, +1,1,14,1,14,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,1,1,1,19,1,19,1,1,1,18,1, +19,19,1,1,1,1,1,14,1,14,1,1,14,1,1,14,1,1,1,14,1,14,1,14,1,1,1,11,1,14,1,19,19,19,19,14,18,1,1,1,1,1,14,1,1,1,14,1,14,1,1, +14,1,14,19,19,1,1,1,1,1,1,1,1,1,19,1,14,1,1,1,1,14,1,14,14,1,14,1,14,1,14,14,1,1,1,1,1,14,1,1,14,1,14,1,14,1,14,1,1,1,1,1, +14,1,14,1,1,1,14,1,14,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,14,1,14,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,14,14,1,1, +1,1,1,1,1,1,1,1,1,19,1,19,1,1,19,1,1,19,1,19,1,1,19,18,15,1,4,19,19,19,19,19,19,19,19,19,19,18,16,2,6,8,3,7,13,11,19,19,19, +19,19,19,13,19,19,19,19,19,19,19,18,15,15,15,9,1,9,10,19,19,19,1,9,10,19,19,19,1,11,14,14,14,14,14,19,1,19,1,19,1,19,1,19, +19,1,19,1,19,1,19,1,19,1,11,1,1,1,19,1,19,1,19,19,19,1,1,19,1,1,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, +19,10,11,19,19,19,19,19,19,19,19,19,1,19,19,19,1,19,19,19,19,19,19,19,19,9,1,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19, +19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,19,19,19,19,19,19,19,19,19,19, +19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,1,1,1,1,19,1,14,19,19,19,1,1,1,1,14,1,1, +1,1,1,1,1,1,1,14,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19, +19,18,19,19,1,1,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,1,14,19,1,19,1,1,1,19,19,1,14,19,1, +1,19,1,19,1,1,1,1,1,1,1,1,19,1,1,19,1,1,19,19,1,19,1,1,1,19,1,19,1,1,19,1,19,1,19,1,19,1,1,1,1,19,1,1,1,1,1,19,1,1,1,1,1,14, +14,19,14,19,19,1,1,1,14,1,19,19,19,1,1,1,19,1,1,1,1,1,1,14,1,14,1,14,1,1,14,1,19,1,1,11,11,1,19,1,1,1,14,1,1,14,1,1,1,1,1, +14,1,1,14,1,1,1,14,1,1,14,1,14,1,14,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1,1,1,1,1,1,1,14,1,14,1,14,1,14,1,14,1,1,1,1, +1,1,1,1,1,1,1,14,1,14,1,1,1,14,1,1,1,1,1,1,1,1,1,1,4,14,4,10,4,4,4,4,4,4,4,4,4,4,4,5,5,5,5,19,19,5,5,5,5,5,15,5,5,19,5,14, +19,19,19,19,14,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,13,19,13,19,13,19,19,19,19,19,19,19, +19,11,19,10,10,19,19,11,11,19,5,5,5,5,15,19,11,11,11,19,19,19,19,10,13,10,13,9,13,19,19,19,1,19,19,19,19,19,19,1,19,19,19, +19,19,19,19,19,19,19,1,1,1,1,1,1,1,1,1,11,19,19,19,11,19,19,19,15,19,19,15,1,1,1,1,1,1,1,1,19,1,1,1,19,19,19,19,19,1,14,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,19,4,4,4,4,4,14,4,14,4,14,4,4,4,4,4,4,14,4,14,4,4,4,4, +4,4,4,4,4,4,19,4,4,4,4,4,4,4,4,4,12,4,1,14,1,1,14,1,19,1,14,1,1,1,14,1,14,1,1,1,1,1,1,1,1,1,1,1,1,1,14,1,1,15,14,1,14,1,14, +1,19,14,19,19,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,1,1,19,1,9,4,19,19,19,19,19,19, +9,1,1,1,1,1,1,1,19,19,19,19,19,19,19,19,19,19,1,19,19,19,1,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,15,1,1,1,1,15, +15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,14,15,15,1,15,1,15 +}; + +/** + * Lookup bidi class for character expressed as unicode scalar value. + * @param ch a unicode scalar value + * @return bidi class + */ +public static int getBidiClass(int ch) { + if (ch <= 0x00FF) { + return bcL1 [ ch - 0x0000 ]; + } else if ((ch >= 0x0590) && (ch <= 0x06FF)) { + return bcR1 [ ch - 0x0590 ]; + } else { + return getBidiClass(ch, bcS1, bcE1, bcC1); + } +} + +private static int getBidiClass(int ch, int[] sa, int[] ea, byte[] ca) { + int k = Arrays.binarySearch(sa, ch); + if (k >= 0) { + return ca [ k ]; + } else { + k = - (k + 1); + if (k == 0) { + return BidiConstants.L; + } else if (ch <= ea [ k - 1 ]) { + return ca [ k - 1 ]; + } else { + return BidiConstants.L; + } + } +} + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiConstants.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiConstants.java new file mode 100644 index 00000000000..845d9a70be6 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/bidi/BidiConstants.java @@ -0,0 +1,94 @@ +/* + * 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.fontbox.ttf.advanced.bidi; + + +/** + *Constants used for bidirectional processing.
+ * + *Adapted from the Apache FOP Project.
+ * + * @author Glenn Adams + */ +public interface BidiConstants { + + // bidi character class + + /** first external (official) category */ + int FIRST = 1; + + // strong category + /** left-to-right class */ + int L = 1; + /** left-to-right embedding class */ + int LRE = 2; + /** left-to-right override class */ + int LRO = 3; + /** right-to-left class */ + int R = 4; + /** right-to-left arabic class */ + int AL = 5; + /** right-to-left embedding class */ + int RLE = 6; + /** right-to-left override class */ + int RLO = 7; + + // weak category + /** pop directional formatting class */ + int PDF = 8; + /** european number class */ + int EN = 9; + /** european number separator class */ + int ES = 10; + /** european number terminator class */ + int ET = 11; + /** arabic number class */ + int AN = 12; + /** common number separator class */ + int CS = 13; + /** non-spacing mark class */ + int NSM = 14; + /** boundary neutral class */ + int BN = 15; + + // neutral category + /** paragraph separator class */ + int B = 16; + /** segment separator class */ + int S = 17; + /** whitespace class */ + int WS = 18; + /** other neutrals class */ + int ON = 19; + + /** last external (official) category */ + int LAST = 19; + + // implementation specific categories + /** placeholder for low surrogate */ + int SURROGATE = 20; + + // other constants + /** last + /** maximum bidirectional levels */ + int MAX_LEVELS = 61; + /** override flag */ + int OVERRIDE = 128; +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/ArabicScriptProcessor.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/ArabicScriptProcessor.java new file mode 100644 index 00000000000..97edcc031fa --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/ArabicScriptProcessor.java @@ -0,0 +1,641 @@ +/* + * 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.fontbox.ttf.advanced.scripts; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fontbox.ttf.advanced.GlyphDefinitionTable; +import org.apache.fontbox.ttf.advanced.bidi.BidiClass; +import org.apache.fontbox.ttf.advanced.bidi.BidiConstants; +import org.apache.fontbox.ttf.advanced.util.CharAssociation; +import org.apache.fontbox.ttf.advanced.util.GlyphContextTester; +import org.apache.fontbox.ttf.advanced.util.GlyphSequence; +import org.apache.fontbox.ttf.advanced.util.ScriptContextTester; + +// CSOFF: LineLengthCheck + +/** + *The ArabicScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Arabic script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +@SuppressWarnings("unchecked") +public class ArabicScriptProcessor extends DefaultScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(ArabicScriptProcessor.class); + + /** features to use for substitutions */ + private static final String[] GSUB_FEATURES = + { + "calt", // contextual alternates + "ccmp", // glyph composition/decomposition + "fina", // final (terminal) forms + "init", // initial forms + "isol", // isolated formas + "liga", // standard ligatures + "medi", // medial forms + "rlig" // required ligatures + }; + + /** features to use for positioning */ + private static final String[] GPOS_FEATURES = + { + "curs", // cursive positioning + "kern", // kerning + "mark", // mark to base or ligature positioning + "mkmk" // mark to mark positioning + }; + + private static class SubstitutionScriptContextTester implements ScriptContextTester { + private static Map/*Default script processor, which enables default glyph composition/decomposition, common ligatures, localized forms + * and kerning.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class DefaultScriptProcessor extends ScriptProcessor { + + private static final String ccmpFeatureName = "ccmp"; + private static final String kernFeatureName = "kern"; + private static final String ligaFeatureName = "liga"; + private static final String loclFeatureName = "locl"; + private static final String markFeatureName = "mark"; + private static final String mkmkFeatureName = "mkmk"; + + /** features to use for substitutions */ + private static final String[] GSUB_FEATURES = + { + ccmpFeatureName, // glyph composition/decomposition + ligaFeatureName, // common ligatures + loclFeatureName // localized forms + }; + + /** features to use for positioning */ + private static final String[] GPOS_FEATURES = + { + kernFeatureName, // kerning + markFeatureName, // mark to base or ligature positioning + mkmkFeatureName // mark to mark positioning + }; + + DefaultScriptProcessor(String script) { + super(script); + } + + @Override + /** {@inheritDoc} */ + public String[] getSubstitutionFeatures(Object[][] features) { + if ((features == null) || (features.length == 0)) + return GSUB_FEATURES; + else + return augmentFeatures(GSUB_FEATURES, features); + } + + @Override + /** {@inheritDoc} */ + public ScriptContextTester getSubstitutionContextTester() { + return null; + } + + @Override + /** {@inheritDoc} */ + public String[] getPositioningFeatures(Object[][] features) { + if ((features == null) || (features.length == 0)) + return GPOS_FEATURES; + else + return augmentFeatures(GPOS_FEATURES, features); + } + + private String[] augmentFeatures(String[] features, Object[][] moreFeatures) { + assert features != null; + assert moreFeatures != null; + ListThe DevanagariScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Devanagari script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class DevanagariScriptProcessor extends IndicScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(DevanagariScriptProcessor.class); + + DevanagariScriptProcessor(String script) { + super(script); + } + + @Override + protected Class extends DevanagariSyllabizer> getSyllabizerClass() { + return DevanagariSyllabizer.class; + } + + @Override + // find rightmost pre-base matra + protected int findPreBaseMatra(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = ng; i > 0; i--) { + int k = i - 1; + if (containsPreBaseMatra(gs, k)) { + lk = k; + break; + } + } + return lk; + } + + @Override + // find leftmost pre-base matra target, starting from source + protected int findPreBaseMatraTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = (source < ng) ? source : ng; i > 0; i--) { + int k = i - 1; + if (containsConsonant(gs, k)) { + if (containsHalfConsonant(gs, k)) { + lk = k; + } else if (lk == -1) { + lk = k; + } else { + break; + } + } + } + return lk; + } + + private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isPreM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsConsonant(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isC(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsHalfConsonant(GlyphSequence gs, int k) { + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); + return (half != null) ? half.booleanValue() : false; + } + + @Override + protected int findReph(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int li = -1; + for (int i = 0; i < ng; i++) { + if (containsReph(gs, i)) { + li = i; + break; + } + } + return li; + } + + @Override + protected int findRephTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int c1 = -1; + int c2 = -1; + // first candidate target is after first non-half consonant + for (int i = 0; i < ng; i++) { + if ((i != source) && containsConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { + c1 = i + 1; + break; + } + } + } + // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark + for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { + c2 = i + 1; + } else if (containsOtherMark(gs, i)) { + c2 = i; + break; + } + } + if (c2 >= 0) { + return c2; + } else if (c1 >= 0) { + return c1; + } else { + return source; + } + } + + private static boolean containsReph(GlyphSequence gs, int k) { + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); + return (rphf != null) ? rphf.booleanValue() : false; + } + + private static boolean containsMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsOtherMark(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + switch (typeOf(ca [ i ])) { + case C_T: // tone (e.g., udatta, anudatta) + case C_A: // accent (e.g., acute, grave) + case C_O: // other (e.g., candrabindu, anusvara, visarga, etc) + return true; + default: + break; + } + } + return false; + } + + private static class DevanagariSyllabizer extends DefaultSyllabizer { + DevanagariSyllabizer(String script, String language) { + super(script, language); + } + @Override + // | C ... + protected int findStartOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + while (s < e) { + int c = ca [ s ]; + if (isC(c)) { + break; + } else { + s++; + } + } + return s; + } + } + @Override + // D* L? | ... + protected int findEndOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + int nd = 0; + int nl = 0; + int i; + // consume dead consonants + while ((i = isDeadConsonant(ca, s, e)) > s) { + s = i; + nd++; + } + // consume zero or one live consonant + if ((i = isLiveConsonant(ca, s, e)) > s) { + s = i; + nl++; + } + return ((nd > 0) || (nl > 0)) ? s : -1; + } + } + // D := ( C N? H )? + private int isDeadConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nh = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + 1 ]; + if (isN(c)) { + i++; + } + } + // H + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + i++; + nh++; + } else { + break; + } + } + } while (false); + return (nc > 0) && (nh > 0) ? s + i : -1; + } + } + // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK ) + private int isLiveConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nv = 0; + int nx = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else if (isV(c)) { + i++; + nv++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + i ]; + if (isN(c)) { + i++; + } + } + // X* + while ((s + i) < e) { + c = ca [ s + i ]; + if (isX(c)) { + i++; + nx++; + } else { + break; + } + } + } while (false); + // if no X but has H, then ignore C|I + if (nx == 0) { + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + if (nc > 0) { + nc--; + } else if (nv > 0) { + nv--; + } + } + } + } + return ((nc > 0) || (nv > 0)) ? s + i : -1; + } + } + } + + // devanagari character types + static final short C_U = 0; // unassigned + static final short C_C = 1; // consonant + static final short C_V = 2; // vowel + static final short C_M = 3; // vowel sign (matra) + static final short C_S = 4; // symbol or sign + static final short C_T = 5; // tone mark + static final short C_A = 6; // accent mark + static final short C_P = 7; // punctuation + static final short C_D = 8; // digit + static final short C_H = 9; // halant (virama) + static final short C_O = 10; // other signs + static final short C_N = 0x0100; // nukta(ized) + static final short C_R = 0x0200; // reph(ized) + static final short C_PRE = 0x0400; // pre-base + static final short C_M_TYPE = 0x00FF; // type mask + static final short C_M_FLAGS = 0x7F00; // flag mask + // devanagari block range + static final int CCA_START = 0x0900; // first code point mapped by cca + static final int CCA_END = 0x0980; // last code point + 1 mapped by cca + // devanagari character type lookups + static final short[] CCA = { + C_O, // 0x0900 // INVERTED CANDRABINDU + C_O, // 0x0901 // CANDRABINDU + C_O, // 0x0902 // ANUSVARA + C_O, // 0x0903 // VISARGA + C_V, // 0x0904 // SHORT A + C_V, // 0x0905 // A + C_V, // 0x0906 // AA + C_V, // 0x0907 // I + C_V, // 0x0908 // II + C_V, // 0x0909 // U + C_V, // 0x090A // UU + C_V, // 0x090B // VOCALIC R + C_V, // 0x090C // VOCALIC L + C_V, // 0x090D // CANDRA E + C_V, // 0x090E // SHORT E + C_V, // 0x090F // E + C_V, // 0x0910 // AI + C_V, // 0x0911 // CANDRA O + C_V, // 0x0912 // SHORT O + C_V, // 0x0913 // O + C_V, // 0x0914 // AU + C_C, // 0x0915 // KA + C_C, // 0x0916 // KHA + C_C, // 0x0917 // GA + C_C, // 0x0918 // GHA + C_C, // 0x0919 // NGA + C_C, // 0x091A // CA + C_C, // 0x091B // CHA + C_C, // 0x091C // JA + C_C, // 0x091D // JHA + C_C, // 0x091E // NYA + C_C, // 0x091F // TTA + C_C, // 0x0920 // TTHA + C_C, // 0x0921 // DDA + C_C, // 0x0922 // DDHA + C_C, // 0x0923 // NNA + C_C, // 0x0924 // TA + C_C, // 0x0925 // THA + C_C, // 0x0926 // DA + C_C, // 0x0927 // DHA + C_C, // 0x0928 // NA + C_C, // 0x0929 // NNNA + C_C, // 0x092A // PA + C_C, // 0x092B // PHA + C_C, // 0x092C // BA + C_C, // 0x092D // BHA + C_C, // 0x092E // MA + C_C, // 0x092F // YA + C_C | C_R, // 0x0930 // RA + C_C | C_R | C_N, // 0x0931 // RRA = 0930+093C + C_C, // 0x0932 // LA + C_C, // 0x0933 // LLA + C_C, // 0x0934 // LLLA + C_C, // 0x0935 // VA + C_C, // 0x0936 // SHA + C_C, // 0x0937 // SSA + C_C, // 0x0938 // SA + C_C, // 0x0939 // HA + C_M, // 0x093A // OE (KASHMIRI) + C_M, // 0x093B // OOE (KASHMIRI) + C_N, // 0x093C // NUKTA + C_S, // 0x093D // AVAGRAHA + C_M, // 0x093E // AA + C_M | C_PRE, // 0x093F // I + C_M, // 0x0940 // II + C_M, // 0x0941 // U + C_M, // 0x0942 // UU + C_M, // 0x0943 // VOCALIC R + C_M, // 0x0944 // VOCALIC RR + C_M, // 0x0945 // CANDRA E + C_M, // 0x0946 // SHORT E + C_M, // 0x0947 // E + C_M, // 0x0948 // AI + C_M, // 0x0949 // CANDRA O + C_M, // 0x094A // SHORT O + C_M, // 0x094B // O + C_M, // 0x094C // AU + C_H, // 0x094D // VIRAMA (HALANT) + C_M, // 0x094E // PRISHTHAMATRA E + C_M, // 0x094F // AW + C_S, // 0x0950 // OM + C_T, // 0x0951 // UDATTA + C_T, // 0x0952 // ANUDATTA + C_A, // 0x0953 // GRAVE + C_A, // 0x0954 // ACUTE + C_M, // 0x0955 // CANDRA LONG E + C_M, // 0x0956 // UE + C_M, // 0x0957 // UUE + C_C | C_N, // 0x0958 // QA + C_C | C_N, // 0x0959 // KHHA + C_C | C_N, // 0x095A // GHHA + C_C | C_N, // 0x095B // ZA + C_C | C_N, // 0x095C // DDDHA + C_C | C_N, // 0x095D // RHA + C_C | C_N, // 0x095E // FA + C_C | C_N, // 0x095F // YYA + C_V, // 0x0960 // VOCALIC RR + C_V, // 0x0961 // VOCALIC LL + C_M, // 0x0962 // VOCALIC RR + C_M, // 0x0963 // VOCALIC LL + C_P, // 0x0964 // DANDA + C_P, // 0x0965 // DOUBLE DANDA + C_D, // 0x0966 // ZERO + C_D, // 0x0967 // ONE + C_D, // 0x0968 // TWO + C_D, // 0x0969 // THREE + C_D, // 0x096A // FOUR + C_D, // 0x096B // FIVE + C_D, // 0x096C // SIX + C_D, // 0x096D // SEVEN + C_D, // 0x096E // EIGHT + C_D, // 0x096F // NINE + C_S, // 0x0970 // ABBREVIATION SIGN + C_S, // 0x0971 // HIGH SPACING DOT + C_V, // 0x0972 // CANDRA A (MARATHI) + C_V, // 0x0973 // OE (KASHMIRI) + C_V, // 0x0974 // OOE (KASHMIRI) + C_V, // 0x0975 // AW (KASHMIRI) + C_V, // 0x0976 // UE (KASHMIRI) + C_V, // 0x0977 // UUE (KASHMIRI) + C_U, // 0x0978 // UNASSIGNED + C_C, // 0x0979 // ZHA + C_C, // 0x097A // HEAVY YA + C_C, // 0x097B // GGAA (SINDHI) + C_C, // 0x097C // JJA (SINDHI) + C_C, // 0x097D // GLOTTAL STOP (LIMBU) + C_C, // 0x097E // DDDA (SINDHI) + C_C // 0x097F // BBA (SINDHI) + }; + static int typeOf(int c) { + if ((c >= CCA_START) && (c < CCA_END)) { + return CCA [ c - CCA_START ] & C_M_TYPE; + } else { + return C_U; + } + } + static boolean isType(int c, int t) { + return typeOf(c) == t; + } + static boolean hasFlag(int c, int f) { + if ((c >= CCA_START) && (c < CCA_END)) { + return (CCA [ c - CCA_START ] & f) == f; + } else { + return false; + } + } + static boolean isC(int c) { + return isType(c, C_C); + } + static boolean isR(int c) { + return isType(c, C_C) && hasR(c); + } + static boolean isV(int c) { + return isType(c, C_V); + } + static boolean isN(int c) { + return c == 0x093C; + } + static boolean isH(int c) { + return c == 0x094D; + } + static boolean isM(int c) { + return isType(c, C_M); + } + static boolean isPreM(int c) { + return isType(c, C_M) && hasFlag(c, C_PRE); + } + static boolean isX(int c) { + switch (typeOf(c)) { + case C_M: // matra (combining vowel) + case C_A: // accent mark + case C_T: // tone mark + case C_O: // other (modifying) mark + return true; + default: + return false; + } + } + static boolean hasR(int c) { + return hasFlag(c, C_R); + } + static boolean hasN(int c) { + return hasFlag(c, C_N); + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GujaratiScriptProcessor.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GujaratiScriptProcessor.java new file mode 100644 index 00000000000..b0ba82f0063 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GujaratiScriptProcessor.java @@ -0,0 +1,540 @@ +/* + * 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.fontbox.ttf.advanced.scripts; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fontbox.ttf.advanced.util.CharAssociation; +import org.apache.fontbox.ttf.advanced.util.GlyphSequence; + +// CSOFF: LineLengthCheck + +/** + *The GujaratiScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Gujarati script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class GujaratiScriptProcessor extends IndicScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(GujaratiScriptProcessor.class); + + GujaratiScriptProcessor(String script) { + super(script); + } + + @Override + protected Class extends GujaratiSyllabizer> getSyllabizerClass() { + return GujaratiSyllabizer.class; + } + + @Override + // find rightmost pre-base matra + protected int findPreBaseMatra(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = ng; i > 0; i--) { + int k = i - 1; + if (containsPreBaseMatra(gs, k)) { + lk = k; + break; + } + } + return lk; + } + + @Override + // find leftmost pre-base matra target, starting from source + protected int findPreBaseMatraTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = (source < ng) ? source : ng; i > 0; i--) { + int k = i - 1; + if (containsConsonant(gs, k)) { + if (containsHalfConsonant(gs, k)) { + lk = k; + } else if (lk == -1) { + lk = k; + } else { + break; + } + } + } + return lk; + } + + private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isPreM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsConsonant(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isC(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsHalfConsonant(GlyphSequence gs, int k) { + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); + return (half != null) ? half.booleanValue() : false; + } + + @Override + protected int findReph(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int li = -1; + for (int i = 0; i < ng; i++) { + if (containsReph(gs, i)) { + li = i; + break; + } + } + return li; + } + + @Override + protected int findRephTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int c1 = -1; + int c2 = -1; + // first candidate target is after first non-half consonant + for (int i = 0; i < ng; i++) { + if ((i != source) && containsConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { + c1 = i + 1; + break; + } + } + } + // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark + for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { + c2 = i + 1; + } else if (containsOtherMark(gs, i)) { + c2 = i; + break; + } + } + if (c2 >= 0) { + return c2; + } else if (c1 >= 0) { + return c1; + } else { + return source; + } + } + + private static boolean containsReph(GlyphSequence gs, int k) { + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); + return (rphf != null) ? rphf.booleanValue() : false; + } + + private static boolean containsMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsOtherMark(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + switch (typeOf(ca [ i ])) { + case C_T: // tone (e.g., udatta, anudatta) + case C_A: // accent (e.g., acute, grave) + case C_O: // other (e.g., candrabindu, anusvara, visarga, etc) + return true; + default: + break; + } + } + return false; + } + + private static class GujaratiSyllabizer extends DefaultSyllabizer { + GujaratiSyllabizer(String script, String language) { + super(script, language); + } + @Override + // | C ... + protected int findStartOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + while (s < e) { + int c = ca [ s ]; + if (isC(c)) { + break; + } else { + s++; + } + } + return s; + } + } + @Override + // D* L? | ... + protected int findEndOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + int nd = 0; + int nl = 0; + int i; + // consume dead consonants + while ((i = isDeadConsonant(ca, s, e)) > s) { + s = i; + nd++; + } + // consume zero or one live consonant + if ((i = isLiveConsonant(ca, s, e)) > s) { + s = i; + nl++; + } + return ((nd > 0) || (nl > 0)) ? s : -1; + } + } + // D := ( C N? H )? + private int isDeadConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nh = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + 1 ]; + if (isN(c)) { + i++; + } + } + // H + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + i++; + nh++; + } else { + break; + } + } + } while (false); + return (nc > 0) && (nh > 0) ? s + i : -1; + } + } + // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK ) + private int isLiveConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nv = 0; + int nx = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else if (isV(c)) { + i++; + nv++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + i ]; + if (isN(c)) { + i++; + } + } + // X* + while ((s + i) < e) { + c = ca [ s + i ]; + if (isX(c)) { + i++; + nx++; + } else { + break; + } + } + } while (false); + // if no X but has H, then ignore C|I + if (nx == 0) { + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + if (nc > 0) { + nc--; + } else if (nv > 0) { + nv--; + } + } + } + } + return ((nc > 0) || (nv > 0)) ? s + i : -1; + } + } + } + + // gujarati character types + static final short C_U = 0; // unassigned + static final short C_C = 1; // consonant + static final short C_V = 2; // vowel + static final short C_M = 3; // vowel sign (matra) + static final short C_S = 4; // symbol or sign + static final short C_T = 5; // tone mark + static final short C_A = 6; // accent mark + static final short C_P = 7; // punctuation + static final short C_D = 8; // digit + static final short C_H = 9; // halant (virama) + static final short C_O = 10; // other signs + static final short C_N = 0x0100; // nukta(ized) + static final short C_R = 0x0200; // reph(ized) + static final short C_PRE = 0x0400; // pre-base + static final short C_M_TYPE = 0x00FF; // type mask + static final short C_M_FLAGS = 0x7F00; // flag mask + // gujarati block range + static final int CCA_START = 0x0A80; // first code point mapped by cca + static final int CCA_END = 0x0B00; // last code point + 1 mapped by cca + // gujarati character type lookups + static final short[] CCA = { + C_U, // 0x0A80 // UNASSIGNED + C_O, // 0x0A81 // CANDRABINDU + C_O, // 0x0A82 // ANUSVARA + C_O, // 0x0A83 // VISARGA + C_U, // 0x0A84 // UNASSIGNED + C_V, // 0x0A85 // A + C_V, // 0x0A86 // AA + C_V, // 0x0A87 // I + C_V, // 0x0A88 // II + C_V, // 0x0A89 // U + C_V, // 0x0A8A // UU + C_V, // 0x0A8B // VOCALIC R + C_V, // 0x0A8C // VOCALIC L + C_V, // 0x0A8D // CANDRA E + C_U, // 0x0A8E // UNASSIGNED + C_V, // 0x0A8F // E + C_V, // 0x0A90 // AI + C_V, // 0x0A91 // CANDRA O + C_U, // 0x0A92 // UNASSIGNED + C_V, // 0x0A93 // O + C_V, // 0x0A94 // AU + C_C, // 0x0A95 // KA + C_C, // 0x0A96 // KHA + C_C, // 0x0A97 // GA + C_C, // 0x0A98 // GHA + C_C, // 0x0A99 // NGA + C_C, // 0x0A9A // CA + C_C, // 0x0A9B // CHA + C_C, // 0x0A9C // JA + C_C, // 0x0A9D // JHA + C_C, // 0x0A9E // NYA + C_C, // 0x0A9F // TTA + C_C, // 0x0AA0 // TTHA + C_C, // 0x0AA1 // DDA + C_C, // 0x0AA2 // DDHA + C_C, // 0x0AA3 // NNA + C_C, // 0x0AA4 // TA + C_C, // 0x0AA5 // THA + C_C, // 0x0AA6 // DA + C_C, // 0x0AA7 // DHA + C_C, // 0x0AA8 // NA + C_U, // 0x0AA9 // UNASSIGNED + C_C, // 0x0AAA // PA + C_C, // 0x0AAB // PHA + C_C, // 0x0AAC // BA + C_C, // 0x0AAD // BHA + C_C, // 0x0AAE // MA + C_C, // 0x0AAF // YA + C_C | C_R, // 0x0AB0 // RA + C_U, // 0x0AB1 // UNASSIGNED + C_C, // 0x0AB2 // LA + C_C, // 0x0AB3 // LLA + C_U, // 0x0AB4 // UNASSIGNED + C_C, // 0x0AB5 // VA + C_C, // 0x0AB6 // SHA + C_C, // 0x0AB7 // SSA + C_C, // 0x0AB8 // SA + C_C, // 0x0AB9 // HA + C_U, // 0x0ABA // UNASSIGNED + C_U, // 0x0ABB // UNASSIGNED + C_N, // 0x0ABC // NUKTA + C_S, // 0x0ABD // AVAGRAHA + C_M, // 0x0ABE // AA + C_M | C_PRE, // 0x0ABF // I + C_M, // 0x0AC0 // II + C_M, // 0x0AC1 // U + C_M, // 0x0AC2 // UU + C_M, // 0x0AC3 // VOCALIC R + C_M, // 0x0AC4 // VOCALIC RR + C_M, // 0x0AC5 // CANDRA E + C_U, // 0x0AC6 // UNASSIGNED + C_M, // 0x0AC7 // E + C_M, // 0x0AC8 // AI + C_M, // 0x0AC9 // CANDRA O + C_U, // 0x0ACA // UNASSIGNED + C_M, // 0x0ACB // O + C_M, // 0x0ACC // AU + C_H, // 0x0ACD // VIRAMA (HALANT) + C_U, // 0x0ACE // UNASSIGNED + C_U, // 0x0ACF // UNASSIGNED + C_S, // 0x0AD0 // OM + C_U, // 0x0AD1 // UNASSIGNED + C_U, // 0x0AD2 // UNASSIGNED + C_U, // 0x0AD3 // UNASSIGNED + C_U, // 0x0AD4 // UNASSIGNED + C_U, // 0x0AD5 // UNASSIGNED + C_U, // 0x0AD6 // UNASSIGNED + C_U, // 0x0AD7 // UNASSIGNED + C_U, // 0x0AD8 // UNASSIGNED + C_U, // 0x0AD9 // UNASSIGNED + C_U, // 0x0ADA // UNASSIGNED + C_U, // 0x0ADB // UNASSIGNED + C_U, // 0x0ADC // UNASSIGNED + C_U, // 0x0ADD // UNASSIGNED + C_U, // 0x0ADE // UNASSIGNED + C_U, // 0x0ADF // UNASSIGNED + C_V, // 0x0AE0 // VOCALIC RR + C_V, // 0x0AE1 // VOCALIC LL + C_M, // 0x0AE2 // VOCALIC L + C_M, // 0x0AE3 // VOCALIC LL + C_U, // 0x0AE4 // UNASSIGNED + C_U, // 0x0AE5 // UNASSIGNED + C_D, // 0x0AE6 // ZERO + C_D, // 0x0AE7 // ONE + C_D, // 0x0AE8 // TWO + C_D, // 0x0AE9 // THREE + C_D, // 0x0AEA // FOUR + C_D, // 0x0AEB // FIVE + C_D, // 0x0AEC // SIX + C_D, // 0x0AED // SEVEN + C_D, // 0x0AEE // EIGHT + C_D, // 0x0AEF // NINE + C_U, // 0x0AF0 // UNASSIGNED + C_S, // 0x0AF1 // RUPEE SIGN + C_U, // 0x0AF2 // UNASSIGNED + C_U, // 0x0AF3 // UNASSIGNED + C_U, // 0x0AF4 // UNASSIGNED + C_U, // 0x0AF5 // UNASSIGNED + C_U, // 0x0AF6 // UNASSIGNED + C_U, // 0x0AF7 // UNASSIGNED + C_U, // 0x0AF8 // UNASSIGNED + C_U, // 0x0AF9 // UNASSIGNED + C_U, // 0x0AFA // UNASSIGNED + C_U, // 0x0AFB // UNASSIGNED + C_U, // 0x0AFC // UNASSIGNED + C_U, // 0x0AFD // UNASSIGNED + C_U, // 0x0AFE // UNASSIGNED + C_U // 0x0AFF // UNASSIGNED + }; + static int typeOf(int c) { + if ((c >= CCA_START) && (c < CCA_END)) { + return CCA [ c - CCA_START ] & C_M_TYPE; + } else { + return C_U; + } + } + static boolean isType(int c, int t) { + return typeOf(c) == t; + } + static boolean hasFlag(int c, int f) { + if ((c >= CCA_START) && (c < CCA_END)) { + return (CCA [ c - CCA_START ] & f) == f; + } else { + return false; + } + } + static boolean isC(int c) { + return isType(c, C_C); + } + static boolean isR(int c) { + return isType(c, C_C) && hasR(c); + } + static boolean isV(int c) { + return isType(c, C_V); + } + static boolean isN(int c) { + return c == 0x0ABC; + } + static boolean isH(int c) { + return c == 0x0ACD; + } + static boolean isM(int c) { + return isType(c, C_M); + } + static boolean isPreM(int c) { + return isType(c, C_M) && hasFlag(c, C_PRE); + } + static boolean isX(int c) { + switch (typeOf(c)) { + case C_M: // matra (combining vowel) + case C_A: // accent mark + case C_T: // tone mark + case C_O: // other (modifying) mark + return true; + default: + return false; + } + } + static boolean hasR(int c) { + return hasFlag(c, C_R); + } + static boolean hasN(int c) { + return hasFlag(c, C_N); + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GurmukhiScriptProcessor.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GurmukhiScriptProcessor.java new file mode 100644 index 00000000000..baa45281889 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/GurmukhiScriptProcessor.java @@ -0,0 +1,540 @@ +/* + * 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.fontbox.ttf.advanced.scripts; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fontbox.ttf.advanced.util.CharAssociation; +import org.apache.fontbox.ttf.advanced.util.GlyphSequence; + +// CSOFF: LineLengthCheck + +/** + *The GurmukhiScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Gurmukhi script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class GurmukhiScriptProcessor extends IndicScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(GurmukhiScriptProcessor.class); + + GurmukhiScriptProcessor(String script) { + super(script); + } + + @Override + protected Class extends GurmukhiSyllabizer> getSyllabizerClass() { + return GurmukhiSyllabizer.class; + } + + @Override + // find rightmost pre-base matra + protected int findPreBaseMatra(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = ng; i > 0; i--) { + int k = i - 1; + if (containsPreBaseMatra(gs, k)) { + lk = k; + break; + } + } + return lk; + } + + @Override + // find leftmost pre-base matra target, starting from source + protected int findPreBaseMatraTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = (source < ng) ? source : ng; i > 0; i--) { + int k = i - 1; + if (containsConsonant(gs, k)) { + if (containsHalfConsonant(gs, k)) { + lk = k; + } else if (lk == -1) { + lk = k; + } else { + break; + } + } + } + return lk; + } + + private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isPreM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsConsonant(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isC(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsHalfConsonant(GlyphSequence gs, int k) { + Boolean half = (Boolean) gs.getAssociation(k) .getPredication("half"); + return (half != null) ? half.booleanValue() : false; + } + + @Override + protected int findReph(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int li = -1; + for (int i = 0; i < ng; i++) { + if (containsReph(gs, i)) { + li = i; + break; + } + } + return li; + } + + @Override + protected int findRephTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int c1 = -1; + int c2 = -1; + // first candidate target is after first non-half consonant + for (int i = 0; i < ng; i++) { + if ((i != source) && containsConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { + c1 = i + 1; + break; + } + } + } + // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark + for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { + c2 = i + 1; + } else if (containsOtherMark(gs, i)) { + c2 = i; + break; + } + } + if (c2 >= 0) { + return c2; + } else if (c1 >= 0) { + return c1; + } else { + return source; + } + } + + private static boolean containsReph(GlyphSequence gs, int k) { + Boolean rphf = (Boolean) gs.getAssociation(k) .getPredication("rphf"); + return (rphf != null) ? rphf.booleanValue() : false; + } + + private static boolean containsMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsOtherMark(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + switch (typeOf(ca [ i ])) { + case C_T: // tone (e.g., udatta, anudatta) + case C_A: // accent (e.g., acute, grave) + case C_O: // other (e.g., candrabindu, anusvara, visarga, etc) + return true; + default: + break; + } + } + return false; + } + + private static class GurmukhiSyllabizer extends DefaultSyllabizer { + GurmukhiSyllabizer(String script, String language) { + super(script, language); + } + @Override + // | C ... + protected int findStartOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + while (s < e) { + int c = ca [ s ]; + if (isC(c)) { + break; + } else { + s++; + } + } + return s; + } + } + @Override + // D* L? | ... + protected int findEndOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + int nd = 0; + int nl = 0; + int i; + // consume dead consonants + while ((i = isDeadConsonant(ca, s, e)) > s) { + s = i; + nd++; + } + // consume zero or one live consonant + if ((i = isLiveConsonant(ca, s, e)) > s) { + s = i; + nl++; + } + return ((nd > 0) || (nl > 0)) ? s : -1; + } + } + // D := ( C N? H )? + private int isDeadConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nh = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + 1 ]; + if (isN(c)) { + i++; + } + } + // H + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + i++; + nh++; + } else { + break; + } + } + } while (false); + return (nc > 0) && (nh > 0) ? s + i : -1; + } + } + // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK ) + private int isLiveConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nv = 0; + int nx = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else if (isV(c)) { + i++; + nv++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + i ]; + if (isN(c)) { + i++; + } + } + // X* + while ((s + i) < e) { + c = ca [ s + i ]; + if (isX(c)) { + i++; + nx++; + } else { + break; + } + } + } while (false); + // if no X but has H, then ignore C|I + if (nx == 0) { + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + if (nc > 0) { + nc--; + } else if (nv > 0) { + nv--; + } + } + } + } + return ((nc > 0) || (nv > 0)) ? s + i : -1; + } + } + } + + // gurmukhi character types + static final short C_U = 0; // unassigned + static final short C_C = 1; // consonant + static final short C_V = 2; // vowel + static final short C_M = 3; // vowel sign (matra) + static final short C_S = 4; // symbol or sign + static final short C_T = 5; // tone mark + static final short C_A = 6; // accent mark + static final short C_P = 7; // punctuation + static final short C_D = 8; // digit + static final short C_H = 9; // halant (virama) + static final short C_O = 10; // other signs + static final short C_N = 0x0100; // nukta(ized) + static final short C_R = 0x0200; // reph(ized) + static final short C_PRE = 0x0400; // pre-base + static final short C_M_TYPE = 0x00FF; // type mask + static final short C_M_FLAGS = 0x7F00; // flag mask + // gurmukhi block range + static final int CCA_START = 0x0A00; // first code point mapped by cca + static final int CCA_END = 0x0A80; // last code point + 1 mapped by cca + // gurmukhi character type lookups + static final short[] CCA = { + C_U, // 0x0A00 // UNASSIGNED + C_O, // 0x0A01 // ADAK BINDI + C_O, // 0x0A02 // BINDI + C_O, // 0x0A03 // VISARGA + C_U, // 0x0A04 // UNASSIGNED + C_V, // 0x0A05 // A + C_V, // 0x0A06 // AA + C_V, // 0x0A07 // I + C_V, // 0x0A08 // II + C_V, // 0x0A09 // U + C_V, // 0x0A0A // UU + C_U, // 0x0A0B // UNASSIGNED + C_U, // 0x0A0C // UNASSIGNED + C_U, // 0x0A0D // UNASSIGNED + C_U, // 0x0A0E // UNASSIGNED + C_V, // 0x0A0F // E + C_V, // 0x0A10 // AI + C_U, // 0x0A11 // UNASSIGNED + C_U, // 0x0A12 // UNASSIGNED + C_V, // 0x0A13 // O + C_V, // 0x0A14 // AU + C_C, // 0x0A15 // KA + C_C, // 0x0A16 // KHA + C_C, // 0x0A17 // GA + C_C, // 0x0A18 // GHA + C_C, // 0x0A19 // NGA + C_C, // 0x0A1A // CA + C_C, // 0x0A1B // CHA + C_C, // 0x0A1C // JA + C_C, // 0x0A1D // JHA + C_C, // 0x0A1E // NYA + C_C, // 0x0A1F // TTA + C_C, // 0x0A20 // TTHA + C_C, // 0x0A21 // DDA + C_C, // 0x0A22 // DDHA + C_C, // 0x0A23 // NNA + C_C, // 0x0A24 // TA + C_C, // 0x0A25 // THA + C_C, // 0x0A26 // DA + C_C, // 0x0A27 // DHA + C_C, // 0x0A28 // NA + C_U, // 0x0A29 // UNASSIGNED + C_C, // 0x0A2A // PA + C_C, // 0x0A2B // PHA + C_C, // 0x0A2C // BA + C_C, // 0x0A2D // BHA + C_C, // 0x0A2E // MA + C_C, // 0x0A2F // YA + C_C | C_R, // 0x0A30 // RA + C_U, // 0x0A31 // UNASSIGNED + C_C, // 0x0A32 // LA + C_C, // 0x0A33 // LLA + C_U, // 0x0A34 // UNASSIGNED + C_C, // 0x0A35 // VA + C_C, // 0x0A36 // SHA + C_U, // 0x0A37 // UNASSIGNED + C_C, // 0x0A38 // SA + C_C, // 0x0A39 // HA + C_U, // 0x0A3A // UNASSIGNED + C_U, // 0x0A3B // UNASSIGNED + C_N, // 0x0A3C // NUKTA + C_U, // 0x0A3D // UNASSIGNED + C_M, // 0x0A3E // AA + C_M | C_PRE, // 0x0A3F // I + C_M, // 0x0A40 // II + C_M, // 0x0A41 // U + C_M, // 0x0A42 // UU + C_U, // 0x0A43 // UNASSIGNED + C_U, // 0x0A44 // UNASSIGNED + C_U, // 0x0A45 // UNASSIGNED + C_U, // 0x0A46 // UNASSIGNED + C_M, // 0x0A47 // EE + C_M, // 0x0A48 // AI + C_U, // 0x0A49 // UNASSIGNED + C_U, // 0x0A4A // UNASSIGNED + C_M, // 0x0A4B // OO + C_M, // 0x0A4C // AU + C_H, // 0x0A4D // VIRAMA (HALANT) + C_U, // 0x0A4E // UNASSIGNED + C_U, // 0x0A4F // UNASSIGNED + C_U, // 0x0A50 // UNASSIGNED + C_T, // 0x0A51 // UDATTA + C_U, // 0x0A52 // UNASSIGNED + C_U, // 0x0A53 // UNASSIGNED + C_U, // 0x0A54 // UNASSIGNED + C_U, // 0x0A55 // UNASSIGNED + C_U, // 0x0A56 // UNASSIGNED + C_U, // 0x0A57 // UNASSIGNED + C_U, // 0x0A58 // UNASSIGNED + C_C | C_N, // 0x0A59 // KHHA + C_C | C_N, // 0x0A5A // GHHA + C_C | C_N, // 0x0A5B // ZA + C_C | C_N, // 0x0A5C // RRA + C_U, // 0x0A5D // UNASSIGNED + C_C | C_N, // 0x0A5E // FA + C_U, // 0x0A5F // UNASSIGNED + C_U, // 0x0A60 // UNASSIGNED + C_U, // 0x0A61 // UNASSIGNED + C_U, // 0x0A62 // UNASSIGNED + C_U, // 0x0A63 // UNASSIGNED + C_U, // 0x0A64 // UNASSIGNED + C_U, // 0x0A65 // UNASSIGNED + C_D, // 0x0A66 // ZERO + C_D, // 0x0A67 // ONE + C_D, // 0x0A68 // TWO + C_D, // 0x0A69 // THREE + C_D, // 0x0A6A // FOUR + C_D, // 0x0A6B // FIVE + C_D, // 0x0A6C // SIX + C_D, // 0x0A6D // SEVEN + C_D, // 0x0A6E // EIGHT + C_D, // 0x0A6F // NINE + C_O, // 0x0A70 // TIPPI + C_O, // 0x0A71 // ADDAK + C_V, // 0x0A72 // IRI + C_V, // 0x0A73 // URA + C_S, // 0x0A74 // EK ONKAR + C_O, // 0x0A75 // YAKASH + C_U, // 0x0A76 // UNASSIGNED + C_U, // 0x0A77 // UNASSIGNED + C_U, // 0x0A78 // UNASSIGNED + C_U, // 0x0A79 // UNASSIGNED + C_U, // 0x0A7A // UNASSIGNED + C_U, // 0x0A7B // UNASSIGNED + C_U, // 0x0A7C // UNASSIGNED + C_U, // 0x0A7D // UNASSIGNED + C_U, // 0x0A7E // UNASSIGNED + C_U // 0x0A7F // UNASSIGNED + }; + static int typeOf(int c) { + if ((c >= CCA_START) && (c < CCA_END)) { + return CCA [ c - CCA_START ] & C_M_TYPE; + } else { + return C_U; + } + } + static boolean isType(int c, int t) { + return typeOf(c) == t; + } + static boolean hasFlag(int c, int f) { + if ((c >= CCA_START) && (c < CCA_END)) { + return (CCA [ c - CCA_START ] & f) == f; + } else { + return false; + } + } + static boolean isC(int c) { + return isType(c, C_C); + } + static boolean isR(int c) { + return isType(c, C_C) && hasR(c); + } + static boolean isV(int c) { + return isType(c, C_V); + } + static boolean isN(int c) { + return c == 0x0A3C; + } + static boolean isH(int c) { + return c == 0x0A4D; + } + static boolean isM(int c) { + return isType(c, C_M); + } + static boolean isPreM(int c) { + return isType(c, C_M) && hasFlag(c, C_PRE); + } + static boolean isX(int c) { + switch (typeOf(c)) { + case C_M: // matra (combining vowel) + case C_A: // accent mark + case C_T: // tone mark + case C_O: // other (modifying) mark + return true; + default: + return false; + } + } + static boolean hasR(int c) { + return hasFlag(c, C_R); + } + static boolean hasN(int c) { + return hasFlag(c, C_N); + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/IndicScriptProcessor.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/IndicScriptProcessor.java new file mode 100644 index 00000000000..5c510805385 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/scripts/IndicScriptProcessor.java @@ -0,0 +1,593 @@ +/* + * 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.fontbox.ttf.advanced.scripts; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.fontbox.ttf.advanced.AdvancedTypographicTable; +import org.apache.fontbox.ttf.advanced.util.CharAssociation; +import org.apache.fontbox.ttf.advanced.util.CharScript; +import org.apache.fontbox.ttf.advanced.util.GlyphContextTester; +import org.apache.fontbox.ttf.advanced.util.GlyphSequence; +import org.apache.fontbox.ttf.advanced.util.ScriptContextTester; + +// CSOFF: LineLengthCheck + +/** + *The IndicScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Indic script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class IndicScriptProcessor extends DefaultScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(IndicScriptProcessor.class); + + /** required features to use for substitutions */ + private static final String[] GSUB_REQ_FEATURES = + { + "abvf", // above base forms + "abvs", // above base substitutions + "akhn", // akhand + "blwf", // below base forms + "blws", // below base substitutions + "ccmp", // glyph composition/decomposition + "cjct", // conjunct forms + "clig", // contextual ligatures + "half", // half forms + "haln", // halant forms + "locl", // localized forms + "nukt", // nukta forms + "pref", // pre-base forms + "pres", // pre-base substitutions + "pstf", // post-base forms + "psts", // post-base substitutions + "rkrf", // rakar forms + "rphf", // reph form + "vatu" // vattu variants + }; + + /** optional features to use for substitutions */ + private static final String[] GSUB_OPT_FEATURES = + { + "afrc", // alternative fractions + "calt", // contextual alternatives + "dlig" // discretionary ligatures + }; + + /** required features to use for positioning */ + private static final String[] GPOS_REQ_FEATURES = + { + "abvm", // above base marks + "blwm", // below base marks + "dist", // distance (adjustment) + "kern" // kerning + }; + + /** required features to use for positioning */ + private static final String[] GPOS_OPT_FEATURES = + { + }; + + private static class SubstitutionScriptContextTester implements ScriptContextTester { + private static Map/*Abstract script processor base class for which an implementation of the substitution and positioning methods + * must be supplied.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +@SuppressWarnings("unchecked") +public abstract class ScriptProcessor { + + private final String script; + + private final Map/*The TamilScriptProcessor class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Tamil script.
This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class TamilScriptProcessor extends IndicScriptProcessor { + + /** logging instance */ + private static final Log log = LogFactory.getLog(TamilScriptProcessor.class); + + TamilScriptProcessor(String script) { + super(script); + } + + @Override + protected Class extends TamilSyllabizer> getSyllabizerClass() { + return TamilSyllabizer.class; + } + + @Override + // find rightmost pre-base matra + protected int findPreBaseMatra(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = ng; i > 0; i--) { + int k = i - 1; + if (containsPreBaseMatra(gs, k)) { + lk = k; + break; + } + } + return lk; + } + + @Override + // find leftmost pre-base matra target, starting from source + protected int findPreBaseMatraTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int lk = -1; + for (int i = (source < ng) ? source : ng; i > 0; i--) { + int k = i - 1; + if (containsConsonant(gs, k)) { + if (containsHalfConsonant(gs, k)) { + lk = k; + } else if (lk == -1) { + lk = k; + } else { + break; + } + } + } + return lk; + } + + private static boolean containsPreBaseMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isPreM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsConsonant(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isC(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsHalfConsonant(GlyphSequence gs, int k) { + Boolean half = (Boolean) gs.getAssociation(k).getPredication("half"); + return (half != null) ? half.booleanValue() : false; + } + + @Override + protected int findReph(GlyphSequence gs) { + int ng = gs.getGlyphCount(); + int li = -1; + for (int i = 0; i < ng; i++) { + if (containsReph(gs, i)) { + li = i; + break; + } + } + return li; + } + + @Override + protected int findRephTarget(GlyphSequence gs, int source) { + int ng = gs.getGlyphCount(); + int c1 = -1; + int c2 = -1; + // first candidate target is after first non-half consonant + for (int i = 0; i < ng; i++) { + if ((i != source) && containsConsonant(gs, i)) { + if (!containsHalfConsonant(gs, i)) { + c1 = i + 1; + break; + } + } + } + // second candidate target is after last non-prebase matra after first candidate or before first syllable or vedic mark + for (int i = (c1 >= 0) ? c1 : 0; i < ng; i++) { + if (containsMatra(gs, i) && !containsPreBaseMatra(gs, i)) { + c2 = i + 1; + } else if (containsOtherMark(gs, i)) { + c2 = i; + break; + } + } + if (c2 >= 0) { + return c2; + } else if (c1 >= 0) { + return c1; + } else { + return source; + } + } + + private static boolean containsReph(GlyphSequence gs, int k) { + Boolean rphf = (Boolean) gs.getAssociation(k).getPredication("rphf"); + return (rphf != null) ? rphf.booleanValue() : false; + } + + private static boolean containsMatra(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + if (isM(ca [ i ])) { + return true; + } + } + return false; + } + + private static boolean containsOtherMark(GlyphSequence gs, int k) { + CharAssociation a = gs.getAssociation(k); + int[] ca = gs.getCharacterArray(false); + for (int i = a.getStart(), e = a.getEnd(); i < e; i++) { + switch (typeOf(ca [ i ])) { + case C_T: // tone (e.g., udatta, anudatta) + case C_A: // accent (e.g., acute, grave) + case C_O: // other (e.g., candrabindu, anusvara, visarga, etc) + return true; + default: + break; + } + } + return false; + } + + private static class TamilSyllabizer extends DefaultSyllabizer { + TamilSyllabizer(String script, String language) { + super(script, language); + } + @Override + // | C ... + protected int findStartOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + while (s < e) { + int c = ca [ s ]; + if (isC(c)) { + break; + } else { + s++; + } + } + return s; + } + } + @Override + // D* L? | ... + protected int findEndOfSyllable(int[] ca, int s, int e) { + if ((s < 0) || (s >= e)) { + return -1; + } else { + int nd = 0; + int nl = 0; + int i; + // consume dead consonants + while ((i = isDeadConsonant(ca, s, e)) > s) { + s = i; + nd++; + } + // consume zero or one live consonant + if ((i = isLiveConsonant(ca, s, e)) > s) { + s = i; + nl++; + } + return ((nd > 0) || (nl > 0)) ? s : -1; + } + } + // D := ( C N? H )? + private int isDeadConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nh = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + 1 ]; + if (isN(c)) { + i++; + } + } + // H + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + i++; + nh++; + } else { + break; + } + } + } while (false); + return (nc > 0) && (nh > 0) ? s + i : -1; + } + } + // L := ( (C|V) N? X* )?; where X = ( MATRA | ACCENT MARK | TONE MARK | OTHER MARK ) + private int isLiveConsonant(int[] ca, int s, int e) { + if (s < 0) { + return -1; + } else { + int c; + int i = 0; + int nc = 0; + int nv = 0; + int nx = 0; + do { + // C + if ((s + i) < e) { + c = ca [ s + i ]; + if (isC(c)) { + i++; + nc++; + } else if (isV(c)) { + i++; + nv++; + } else { + break; + } + } + // N? + if ((s + i) < e) { + c = ca [ s + i ]; + if (isN(c)) { + i++; + } + } + // X* + while ((s + i) < e) { + c = ca [ s + i ]; + if (isX(c)) { + i++; + nx++; + } else { + break; + } + } + } while (false); + // if no X but has H, then ignore C|I + if (nx == 0) { + if ((s + i) < e) { + c = ca [ s + i ]; + if (isH(c)) { + if (nc > 0) { + nc--; + } else if (nv > 0) { + nv--; + } + } + } + } + return ((nc > 0) || (nv > 0)) ? s + i : -1; + } + } + } + + // tamil character types + static final short C_U = 0; // unassigned + static final short C_C = 1; // consonant + static final short C_V = 2; // vowel + static final short C_M = 3; // vowel sign (matra) + static final short C_S = 4; // symbol or sign + static final short C_T = 5; // tone mark + static final short C_A = 6; // accent mark + static final short C_P = 7; // punctuation + static final short C_D = 8; // digit + static final short C_H = 9; // halant (virama) + static final short C_O = 10; // other signs + static final short C_N = 0x0100; // nukta(ized) + static final short C_R = 0x0200; // reph(ized) + static final short C_PRE = 0x0400; // pre-base + static final short C_POST = 0x1000; // post-base + static final short C_WRAP = C_PRE | C_POST; // wrap (two part) vowel + static final short C_M_TYPE = 0x00FF; // type mask + static final short C_M_FLAGS = 0x7F00; // flag mask + // tamil block range + static final int CCA_START = 0x0B80; // first code point mapped by cca + static final int CCA_END = 0x0C00; // last code point + 1 mapped by cca + // tamil character type lookups + static final short[] CCA = { + C_U, // 0x0B80 // + C_U, // 0x0B81 // + C_O, // 0x0B82 // ANUSVARA + C_O, // 0x0B83 // VISARGA + C_U, // 0x0B84 // + C_V, // 0x0B85 // A + C_V, // 0x0B86 // AA + C_V, // 0x0B87 // I + C_V, // 0x0B88 // II + C_V, // 0x0B89 // U + C_V, // 0x0B8A // UU + C_U, // 0x0B8B // + C_U, // 0x0B8C // + C_U, // 0x0B8D // + C_V, // 0x0B8E // E + C_V, // 0x0B8F // EE + C_V, // 0x0B90 // AI + C_U, // 0x0B91 // + C_V, // 0x0B92 // O + C_V, // 0x0B93 // OO + C_V, // 0x0B94 // AU + C_C, // 0x0B95 // KA + C_U, // 0x0B96 // + C_U, // 0x0B97 // + C_U, // 0x0B98 // + C_C, // 0x0B99 // NGA + C_C, // 0x0B9A // CA + C_U, // 0x0B9B // + C_C, // 0x0B9C // JA + C_U, // 0x0B9D // + C_C, // 0x0B9E // NYA + C_C, // 0x0B9F // TTA + C_U, // 0x0BA0 // + C_U, // 0x0BA1 // + C_U, // 0x0BA2 // + C_C, // 0x0BA3 // NNA + C_C, // 0x0BA4 // TA + C_U, // 0x0BA5 // + C_U, // 0x0BA6 // + C_U, // 0x0BA7 // + C_C, // 0x0BA8 // NA + C_C, // 0x0BA9 // NNNA + C_C, // 0x0BAA // PA + C_U, // 0x0BAB // + C_U, // 0x0BAC // + C_U, // 0x0BAD // + C_C, // 0x0BAE // MA + C_C, // 0x0BAF // YA + C_C | C_R, // 0x0BB0 // RA + C_C | C_R, // 0x0BB1 // RRA + C_C, // 0x0BB2 // LA + C_C, // 0x0BB3 // LLA + C_C, // 0x0BB4 // LLLA + C_C, // 0x0BB5 // VA + C_C, // 0x0BB6 // SHA + C_C, // 0x0BB7 // SSA + C_C, // 0x0BB8 // SA + C_C, // 0x0BB9 // HA + C_U, // 0x0BBA // + C_U, // 0x0BBB // + C_U, // 0x0BBC // + C_U, // 0x0BBD // + C_M, // 0x0BBE // AA + C_M, // 0x0BBF // I + C_M, // 0x0BC0 // II + C_M, // 0x0BC1 // U + C_M, // 0x0BC2 // UU + C_U, // 0x0BC3 // + C_U, // 0x0BC4 // + C_U, // 0x0BC5 // + C_M | C_PRE, // 0x0BC6 // E + C_M | C_PRE, // 0x0BC7 // EE + C_M | C_PRE, // 0x0BC8 // AI + C_U, // 0x0BC9 // + C_M | C_WRAP, // 0x0BCA // O + C_M | C_WRAP, // 0x0BCB // OO + C_M | C_WRAP, // 0x0BCC // AU + C_H, // 0x0BCD // VIRAMA (HALANT) + C_U, // 0x0BCE // + C_U, // 0x0BCF // + C_S, // 0x0BD0 // OM + C_U, // 0x0BD1 // + C_U, // 0x0BD2 // + C_U, // 0x0BD3 // + C_U, // 0x0BD4 // + C_U, // 0x0BD5 // + C_U, // 0x0BD6 // + C_M, // 0x0BD7 // AU LENGTH MARK + C_U, // 0x0BD8 // + C_U, // 0x0BD9 // + C_U, // 0x0BDA // + C_U, // 0x0BDB // + C_U, // 0x0BDC // + C_U, // 0x0BDD // + C_U, // 0x0BDE // + C_U, // 0x0BDF // + C_U, // 0x0BE0 // + C_U, // 0x0BE1 // + C_U, // 0x0BE2 // + C_U, // 0x0BE3 // + C_U, // 0x0BE4 // + C_U, // 0x0BE5 // + C_D, // 0x0BE6 // ZERO + C_D, // 0x0BE7 // ONE + C_D, // 0x0BE8 // TWO + C_D, // 0x0BE9 // THREE + C_D, // 0x0BEA // FOUR + C_D, // 0x0BEB // FIVE + C_D, // 0x0BEC // SIX + C_D, // 0x0BED // SEVEN + C_D, // 0x0BEE // EIGHT + C_D, // 0x0BEF // NINE + C_S, // 0x0BF0 // TEN + C_S, // 0x0BF1 // ONE HUNDRED + C_S, // 0x0BF2 // ONE THOUSAND + C_S, // 0x0BF3 // DAY SIGN (naal) + C_S, // 0x0BF4 // MONTH SIGN (maatham) + C_S, // 0x0BF5 // YEAR SIGN (varudam) + C_S, // 0x0BF6 // DEBIT SIGN (patru) + C_S, // 0x0BF7 // CREDIT SIGN (varavu) + C_S, // 0x0BF8 // AS ABOVE SIGN (merpadi) + C_S, // 0x0BF9 // RUPEE SIGN (rupai) + C_S, // 0x0BFA // NUMBER SIGN (enn) + C_U, // 0x0BFB // + C_U, // 0x0BFC // + C_U, // 0x0BFD // + C_U, // 0x0BFE // + C_U // 0x0BFF // + }; + static int typeOf(int c) { + if ((c >= CCA_START) && (c < CCA_END)) { + return CCA [ c - CCA_START ] & C_M_TYPE; + } else { + return C_U; + } + } + static boolean isType(int c, int t) { + return typeOf(c) == t; + } + static boolean hasFlag(int c, int f) { + if ((c >= CCA_START) && (c < CCA_END)) { + return (CCA [ c - CCA_START ] & f) == f; + } else { + return false; + } + } + static boolean isC(int c) { + return isType(c, C_C); + } + static boolean isR(int c) { + return isType(c, C_C) && hasR(c); + } + static boolean isV(int c) { + return isType(c, C_V); + } + static boolean isN(int c) { + return c == 0x093C; + } + static boolean isH(int c) { + return c == 0x094D; + } + static boolean isM(int c) { + return isType(c, C_M); + } + static boolean isPreM(int c) { + return isType(c, C_M) && hasFlag(c, C_PRE); + } + static boolean isX(int c) { + switch (typeOf(c)) { + case C_M: // matra (combining vowel) + case C_A: // accent mark + case C_T: // tone mark + case C_O: // other (modifying) mark + return true; + default: + return false; + } + } + static boolean hasR(int c) { + return hasFlag(c, C_R); + } + static boolean hasN(int c) { + return hasFlag(c, C_N); + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharAssociation.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharAssociation.java new file mode 100644 index 00000000000..0223684a55d --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharAssociation.java @@ -0,0 +1,488 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced.util; + +import java.util.HashMap; +import java.util.Map; + +/** + * A structure class encapsulating an interval of characters expressed as an offset and count of + * Unicode scalar values (in an IntBuffer). ACharAssociation is used to maintain a
+ * backpointer from a glyph to one or more character intervals from which the glyph was derived.
+ *
+ * Each glyph in a glyph sequence is associated with a single CharAssociation instance.
+ *
+ * A CharAssociation instance is additionally (and optionally) used to record
+ * predication information about the glyph, such as whether the glyph was produced by the
+ * application of a specific substitution table or whether its position was adjusted by a specific
+ * poisitioning table.
+ *
+ * @author Glenn Adams
+ */
+public class CharAssociation implements Cloneable {
+
+ // instance state
+ private final int offset;
+ private final int count;
+ private final int[] subIntervals;
+ private MapPredicationMerger
+ * if one exists, otherwise uses V2 if non-null, otherwise uses V1.
+ * @param key predication key
+ * @param v1 first (original) predication value
+ * @param v2 second (to be merged) predication value
+ * @return merged value
+ */
+ public static Object mergePredicationValues(String key, Object v1, Object v2) {
+ PredicationMerger pm = getPredicationMerger(key);
+ if (pm != null) {
+ return pm.merge(key, v1, v2);
+ } else if (v2 != null) {
+ return v2;
+ } else {
+ return v1;
+ }
+ }
+
+ /**
+ * Merge predications from another CA.
+ * @param ca from which to merge
+ */
+ public void mergePredications(CharAssociation ca) {
+ if (ca.predications != null) {
+ for (Map.Entryrepeat new associations.
+ * @param a association to replicate
+ * @param repeat count
+ * @return array of replicated associations
+ */
+ public static CharAssociation[] replicate(CharAssociation a, int repeat) {
+ CharAssociation[] aa = new CharAssociation [ repeat ];
+ for (int i = 0, n = aa.length; i < n; i++) {
+ aa [ i ] = (CharAssociation) a.clone();
+ }
+ return aa;
+ }
+
+ /**
+ * Join (merge) multiple associations into a single, potentially disjoint
+ * association.
+ * @param aa array of associations to join
+ * @return (possibly disjoint) association containing joined associations
+ */
+ public static CharAssociation join(CharAssociation[] aa) {
+ CharAssociation ca;
+ // extract sorted intervals
+ int[] ia = extractIntervals(aa);
+ if ((ia == null) || (ia.length == 0)) {
+ ca = new CharAssociation(0, 0);
+ } else if (ia.length == 2) {
+ int s = ia[0];
+ int e = ia[1];
+ ca = new CharAssociation(s, e - s);
+ } else {
+ ca = new CharAssociation(mergeIntervals(ia));
+ }
+ return mergePredicates(ca, aa);
+ }
+
+ private static CharAssociation mergePredicates(CharAssociation ca, CharAssociation[] aa) {
+ for (CharAssociation a : aa) {
+ ca.mergePredications(a);
+ }
+ return ca;
+ }
+
+ private static int getSubIntervalsStart(int[] ia) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if (ia != null) {
+ for (int i = 0, n = ia.length; i < n; i += 2) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if (s < us) {
+ us = s;
+ }
+ if (e > ue) {
+ ue = e;
+ }
+ }
+ if (ue < 0) {
+ ue = 0;
+ }
+ if (us > ue) {
+ us = ue;
+ }
+ }
+ return us;
+ }
+
+ private static int getSubIntervalsLength(int[] ia) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if (ia != null) {
+ for (int i = 0, n = ia.length; i < n; i += 2) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if (s < us) {
+ us = s;
+ }
+ if (e > ue) {
+ ue = e;
+ }
+ }
+ if (ue < 0) {
+ ue = 0;
+ }
+ if (us > ue) {
+ us = ue;
+ }
+ }
+ return ue - us;
+ }
+
+ /**
+ * Extract sorted sub-intervals.
+ */
+ private static int[] extractIntervals(CharAssociation[] aa) {
+ int ni = 0;
+ for (int i = 0, n = aa.length; i < n; i++) {
+ CharAssociation a = aa [ i ];
+ if (a.isDisjoint()) {
+ ni += a.getSubIntervalCount();
+ } else {
+ ni += 1;
+ }
+ }
+ int[] sa = new int [ ni ];
+ int[] ea = new int [ ni ];
+ for (int i = 0, k = 0; i < aa.length; i++) {
+ CharAssociation a = aa [ i ];
+ if (a.isDisjoint()) {
+ int[] da = a.getSubIntervals();
+ for (int j = 0; j < da.length; j += 2) {
+ sa [ k ] = da [ j + 0 ];
+ ea [ k ] = da [ j + 1 ];
+ k++;
+ }
+ } else {
+ sa [ k ] = a.getStart();
+ ea [ k ] = a.getEnd();
+ k++;
+ }
+ }
+ return sortIntervals(sa, ea);
+ }
+
+ private static final int[] SORT_INCREMENTS_16
+ = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
+
+ private static final int[] SORT_INCREMENTS_03
+ = { 7, 3, 1 };
+
+ /**
+ * Sort sub-intervals using modified Shell Sort.
+ */
+ private static int[] sortIntervals(int[] sa, int[] ea) {
+ assert sa != null;
+ assert ea != null;
+ assert sa.length == ea.length;
+ int ni = sa.length;
+ int[] incr = (ni < 21) ? SORT_INCREMENTS_03 : SORT_INCREMENTS_16;
+ for (int k = 0; k < incr.length; k++) {
+ for (int h = incr [ k ], i = h, n = ni, j; i < n; i++) {
+ int s1 = sa [ i ];
+ int e1 = ea [ i ];
+ for (j = i; j >= h; j -= h) {
+ int s2 = sa [ j - h ];
+ int e2 = ea [ j - h ];
+ if (s2 > s1) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else if ((s2 == s1) && (e2 > e1)) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else {
+ break;
+ }
+ }
+ sa [ j ] = s1;
+ ea [ j ] = e1;
+ }
+ }
+ int[] ia = new int [ ni * 2 ];
+ for (int i = 0; i < ni; i++) {
+ ia [ (i * 2) + 0 ] = sa [ i ];
+ ia [ (i * 2) + 1 ] = ea [ i ];
+ }
+ return ia;
+ }
+
+ /**
+ * Merge overlapping and abutting sub-intervals.
+ */
+ private static int[] mergeIntervals(int[] ia) {
+ int ni = ia.length;
+ int i;
+ int n;
+ int nm;
+ int is;
+ int ie;
+ // count merged sub-intervals
+ for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ((ie < 0) || (s > ie)) {
+ is = s;
+ ie = e;
+ nm++;
+ } else if (s >= is) {
+ if (e > ie) {
+ ie = e;
+ }
+ }
+ }
+ int[] mi = new int [ nm * 2 ];
+ // populate merged sub-intervals
+ for (i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ int k = nm * 2;
+ if ((ie < 0) || (s > ie)) {
+ is = s;
+ ie = e;
+ mi [ k + 0 ] = is;
+ mi [ k + 1 ] = ie;
+ nm++;
+ } else if (s >= is) {
+ if (e > ie) {
+ ie = e;
+ }
+ mi [ k - 1 ] = ie;
+ }
+ }
+ return mi;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('[');
+ sb.append(offset);
+ sb.append(',');
+ sb.append(count);
+ sb.append(']');
+ return sb.toString();
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharMirror.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharMirror.java
new file mode 100644
index 00000000000..5d6f6519390
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharMirror.java
@@ -0,0 +1,729 @@
+/*
+ * 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.
+ */
+
+package org.apache.fontbox.ttf.advanced.util;
+
+import java.util.Arrays;
+
+/**
+ * Mirror related utilities.
+ * + * @author Glenn Adams + */ +public final class CharMirror { + + private CharMirror() { + } + + /** + * Mirror characters that are designated as having the bidi mirrorred property. + * @param s a string whose characters are to be mirrored + * @return the resulting string + */ + public static String mirror(String s) { + StringBuffer sb = new StringBuffer(s); + for (int i = 0, n = sb.length(); i < n; ++i) { + sb.setCharAt(i, (char) mirror(sb.charAt(i))); + } + return sb.toString(); + } + + /** + * Determine if string has a mirrorable character. + * @param s a string whose characters are to be tested for mirrorability + * @return true if some character can be mirrored + */ + public static boolean hasMirrorable(String s) { + for (int i = 0, n = s.length(); i < n; ++i) { + char c = s.charAt(i); + if (Arrays.binarySearch(mirroredCharacters, c) >= 0) { + return true; + } + } + return false; + } + + private static int[] mirroredCharacters = { + 0x0028, + 0x0029, + 0x003C, + 0x003E, + 0x005B, + 0x005D, + 0x007B, + 0x007D, + 0x00AB, + 0x00BB, + 0x0F3A, + 0x0F3B, + 0x0F3C, + 0x0F3D, + 0x169B, + 0x169C, + 0x2039, + 0x203A, + 0x2045, + 0x2046, + 0x207D, + 0x207E, + 0x208D, + 0x208E, + 0x2208, + 0x2209, + 0x220A, + 0x220B, + 0x220C, + 0x220D, + 0x2215, + 0x223C, + 0x223D, + 0x2243, + 0x2252, + 0x2253, + 0x2254, + 0x2255, + 0x2264, + 0x2265, + 0x2266, + 0x2267, + 0x2268, + 0x2269, + 0x226A, + 0x226B, + 0x226E, + 0x226F, + 0x2270, + 0x2271, + 0x2272, + 0x2273, + 0x2274, + 0x2275, + 0x2276, + 0x2277, + 0x2278, + 0x2279, + 0x227A, + 0x227B, + 0x227C, + 0x227D, + 0x227E, + 0x227F, + 0x2280, + 0x2281, + 0x2282, + 0x2283, + 0x2284, + 0x2285, + 0x2286, + 0x2287, + 0x2288, + 0x2289, + 0x228A, + 0x228B, + 0x228F, + 0x2290, + 0x2291, + 0x2292, + 0x2298, + 0x22A2, + 0x22A3, + 0x22A6, + 0x22A8, + 0x22A9, + 0x22AB, + 0x22B0, + 0x22B1, + 0x22B2, + 0x22B3, + 0x22B4, + 0x22B5, + 0x22B6, + 0x22B7, + 0x22C9, + 0x22CA, + 0x22CB, + 0x22CC, + 0x22CD, + 0x22D0, + 0x22D1, + 0x22D6, + 0x22D7, + 0x22D8, + 0x22D9, + 0x22DA, + 0x22DB, + 0x22DC, + 0x22DD, + 0x22DE, + 0x22DF, + 0x22E0, + 0x22E1, + 0x22E2, + 0x22E3, + 0x22E4, + 0x22E5, + 0x22E6, + 0x22E7, + 0x22E8, + 0x22E9, + 0x22EA, + 0x22EB, + 0x22EC, + 0x22ED, + 0x22F0, + 0x22F1, + 0x22F2, + 0x22F3, + 0x22F4, + 0x22F6, + 0x22F7, + 0x22FA, + 0x22FB, + 0x22FC, + 0x22FD, + 0x22FE, + 0x2308, + 0x2309, + 0x230A, + 0x230B, + 0x2329, + 0x232A, + 0x2768, + 0x2769, + 0x276A, + 0x276B, + 0x276C, + 0x276D, + 0x276E, + 0x276F, + 0x2770, + 0x2771, + 0x2772, + 0x2773, + 0x2774, + 0x2775, + 0x27C3, + 0x27C4, + 0x27C5, + 0x27C6, + 0x27C8, + 0x27C9, + 0x27D5, + 0x27D6, + 0x27DD, + 0x27DE, + 0x27E2, + 0x27E3, + 0x27E4, + 0x27E5, + 0x27E6, + 0x27E7, + 0x27E8, + 0x27E9, + 0x27EA, + 0x27EB, + 0x27EC, + 0x27ED, + 0x27EE, + 0x27EF, + 0x2983, + 0x2984, + 0x2985, + 0x2986, + 0x2987, + 0x2988, + 0x2989, + 0x298A, + 0x298B, + 0x298C, + 0x298D, + 0x298E, + 0x298F, + 0x2990, + 0x2991, + 0x2992, + 0x2993, + 0x2994, + 0x2995, + 0x2996, + 0x2997, + 0x2998, + 0x29B8, + 0x29C0, + 0x29C1, + 0x29C4, + 0x29C5, + 0x29CF, + 0x29D0, + 0x29D1, + 0x29D2, + 0x29D4, + 0x29D5, + 0x29D8, + 0x29D9, + 0x29DA, + 0x29DB, + 0x29F5, + 0x29F8, + 0x29F9, + 0x29FC, + 0x29FD, + 0x2A2B, + 0x2A2C, + 0x2A2D, + 0x2A2E, + 0x2A34, + 0x2A35, + 0x2A3C, + 0x2A3D, + 0x2A64, + 0x2A65, + 0x2A79, + 0x2A7A, + 0x2A7D, + 0x2A7E, + 0x2A7F, + 0x2A80, + 0x2A81, + 0x2A82, + 0x2A83, + 0x2A84, + 0x2A8B, + 0x2A8C, + 0x2A91, + 0x2A92, + 0x2A93, + 0x2A94, + 0x2A95, + 0x2A96, + 0x2A97, + 0x2A98, + 0x2A99, + 0x2A9A, + 0x2A9B, + 0x2A9C, + 0x2AA1, + 0x2AA2, + 0x2AA6, + 0x2AA7, + 0x2AA8, + 0x2AA9, + 0x2AAA, + 0x2AAB, + 0x2AAC, + 0x2AAD, + 0x2AAF, + 0x2AB0, + 0x2AB3, + 0x2AB4, + 0x2AC3, + 0x2AC4, + 0x2AC5, + 0x2AC6, + 0x2ACD, + 0x2ACE, + 0x2ACF, + 0x2AD0, + 0x2AD1, + 0x2AD2, + 0x2AD3, + 0x2AD4, + 0x2AD5, + 0x2AD6, + 0x2ADE, + 0x2AE3, + 0x2E02, + 0x2E03, + 0x2E04, + 0x2E05, + 0x2E09, + 0x2E0A, + 0x2E0C, + 0x2E0D, + 0x2E1C, + 0x2E1D, + 0x2E20, + 0x2E21, + 0x2E22, + 0x2E23, + 0x2E24, + 0x2E25, + 0x2E26, + 0x300E, + 0x300F, + 0x3010, + 0x3011, + 0x3014, + 0x3015, + 0x3016, + 0x3017, + 0x3018, + 0x3019, + 0x301A, + 0x301B, + 0xFE59, + 0xFE5A, + 0xFF3B, + 0xFF3D, + 0xFF5B, + 0xFF5D, + 0xFF5F, + 0xFF60, + 0xFF62, + 0xFF63 + }; + + private static int[] mirroredCharactersMapping = { + 0x0029, + 0x0028, + 0x003E, + 0x003C, + 0x005D, + 0x005B, + 0x007D, + 0x007B, + 0x00BB, + 0x00AB, + 0x0F3B, + 0x0F3A, + 0x0F3D, + 0x0F3C, + 0x169C, + 0x169B, + 0x203A, + 0x2039, + 0x2046, + 0x2045, + 0x207E, + 0x207D, + 0x208E, + 0x208D, + 0x220B, + 0x220C, + 0x220D, + 0x2208, + 0x2209, + 0x220A, + 0x29F5, + 0x223D, + 0x223C, + 0x22CD, + 0x2253, + 0x2252, + 0x2255, + 0x2254, + 0x2265, + 0x2264, + 0x2267, + 0x2266, + 0x2269, + 0x2268, + 0x226B, + 0x226A, + 0x226F, + 0x226E, + 0x2271, + 0x2270, + 0x2273, + 0x2272, + 0x2275, + 0x2274, + 0x2277, + 0x2276, + 0x2279, + 0x2278, + 0x227B, + 0x227A, + 0x227D, + 0x227C, + 0x227F, + 0x227E, + 0x2281, + 0x2280, + 0x2283, + 0x2282, + 0x2285, + 0x2284, + 0x2287, + 0x2286, + 0x2289, + 0x2288, + 0x228B, + 0x228A, + 0x2290, + 0x228F, + 0x2292, + 0x2291, + 0x29B8, + 0x22A3, + 0x22A2, + 0x2ADE, + 0x2AE4, + 0x2AE3, + 0x2AE5, + 0x22B1, + 0x22B0, + 0x22B3, + 0x22B2, + 0x22B5, + 0x22B4, + 0x22B7, + 0x22B6, + 0x22CA, + 0x22C9, + 0x22CC, + 0x22CB, + 0x2243, + 0x22D1, + 0x22D0, + 0x22D7, + 0x22D6, + 0x22D9, + 0x22D8, + 0x22DB, + 0x22DA, + 0x22DD, + 0x22DC, + 0x22DF, + 0x22DE, + 0x22E1, + 0x22E0, + 0x22E3, + 0x22E2, + 0x22E5, + 0x22E4, + 0x22E7, + 0x22E6, + 0x22E9, + 0x22E8, + 0x22EB, + 0x22EA, + 0x22ED, + 0x22EC, + 0x22F1, + 0x22F0, + 0x22FA, + 0x22FB, + 0x22FC, + 0x22FD, + 0x22FE, + 0x22F2, + 0x22F3, + 0x22F4, + 0x22F6, + 0x22F7, + 0x2309, + 0x2308, + 0x230B, + 0x230A, + 0x232A, + 0x2329, + 0x2769, + 0x2768, + 0x276B, + 0x276A, + 0x276D, + 0x276C, + 0x276F, + 0x276E, + 0x2771, + 0x2770, + 0x2773, + 0x2772, + 0x2775, + 0x2774, + 0x27C4, + 0x27C3, + 0x27C6, + 0x27C5, + 0x27C9, + 0x27C8, + 0x27D6, + 0x27D5, + 0x27DE, + 0x27DD, + 0x27E3, + 0x27E2, + 0x27E5, + 0x27E4, + 0x27E7, + 0x27E6, + 0x27E9, + 0x27E8, + 0x27EB, + 0x27EA, + 0x27ED, + 0x27EC, + 0x27EF, + 0x27EE, + 0x2984, + 0x2983, + 0x2986, + 0x2985, + 0x2988, + 0x2987, + 0x298A, + 0x2989, + 0x298C, + 0x298B, + 0x2990, + 0x298F, + 0x298E, + 0x298D, + 0x2992, + 0x2991, + 0x2994, + 0x2993, + 0x2996, + 0x2995, + 0x2998, + 0x2997, + 0x2298, + 0x29C1, + 0x29C0, + 0x29C5, + 0x29C4, + 0x29D0, + 0x29CF, + 0x29D2, + 0x29D1, + 0x29D5, + 0x29D4, + 0x29D9, + 0x29D8, + 0x29DB, + 0x29DA, + 0x2215, + 0x29F9, + 0x29F8, + 0x29FD, + 0x29FC, + 0x2A2C, + 0x2A2B, + 0x2A2E, + 0x2A2D, + 0x2A35, + 0x2A34, + 0x2A3D, + 0x2A3C, + 0x2A65, + 0x2A64, + 0x2A7A, + 0x2A79, + 0x2A7E, + 0x2A7D, + 0x2A80, + 0x2A7F, + 0x2A82, + 0x2A81, + 0x2A84, + 0x2A83, + 0x2A8C, + 0x2A8B, + 0x2A92, + 0x2A91, + 0x2A94, + 0x2A93, + 0x2A96, + 0x2A95, + 0x2A98, + 0x2A97, + 0x2A9A, + 0x2A99, + 0x2A9C, + 0x2A9B, + 0x2AA2, + 0x2AA1, + 0x2AA7, + 0x2AA6, + 0x2AA9, + 0x2AA8, + 0x2AAB, + 0x2AAA, + 0x2AAD, + 0x2AAC, + 0x2AB0, + 0x2AAF, + 0x2AB4, + 0x2AB3, + 0x2AC4, + 0x2AC3, + 0x2AC6, + 0x2AC5, + 0x2ACE, + 0x2ACD, + 0x2AD0, + 0x2ACF, + 0x2AD2, + 0x2AD1, + 0x2AD4, + 0x2AD3, + 0x2AD6, + 0x2AD5, + 0x22A6, + 0x22A9, + 0x2E03, + 0x2E02, + 0x2E05, + 0x2E04, + 0x2E0A, + 0x2E09, + 0x2E0D, + 0x2E0C, + 0x2E1D, + 0x2E1C, + 0x2E21, + 0x2E20, + 0x2E23, + 0x2E22, + 0x2E25, + 0x2E24, + 0x2E27, + 0x300F, + 0x300E, + 0x3011, + 0x3010, + 0x3015, + 0x3014, + 0x3017, + 0x3016, + 0x3019, + 0x3018, + 0x301B, + 0x301A, + 0xFE5A, + 0xFE59, + 0xFF3D, + 0xFF3B, + 0xFF5D, + 0xFF5B, + 0xFF60, + 0xFF5F, + 0xFF63, + 0xFF62 + }; + + private static int mirror(int c) { + int i = Arrays.binarySearch(mirroredCharacters, c); + if (i < 0) { + return c; + } else { + return mirroredCharactersMapping [ i ]; + } + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharNormalize.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharNormalize.java new file mode 100644 index 00000000000..30e1833e90f --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharNormalize.java @@ -0,0 +1,104 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced.util; + +import java.util.Arrays; + +/** + *Normalization related utilities. N.B. This implementation is an experimental + * shortcut, the full version of which would require either using ICU4J or an extraction + * of its normalization function, either being a significant undertaking. At present + * we handle only specialized decomposition of Indic two part matras.
+ * + * @author Glenn Adams + */ +public final class CharNormalize { + + // CSOFF: LineLength + + private CharNormalize() { + } + + private static final int[] DECOMPOSABLES = { + // bengali + 0x09CB, + 0x09CC, + // oriya + 0x0B4B, + 0x0B4C, + // tamil + 0x0BCA, + 0x0BCB, + 0x0BCC, + // malayalam + 0x0D4A, + 0x0D4B, + 0x0D4C, + // sinhala + 0x0DDA, + 0x0DDC, + 0x0DDD, + 0x0DDE, + }; + + private static final int[][] DECOMPOSITIONS = { + // bengali + { 0x09C7, 0x09BE }, // 0x09CB + { 0x09C7, 0x09D7 }, // 0x09CC + // oriya + { 0x0B47, 0x0B4E }, // 0x0B4B + { 0x0B47, 0x0B57 }, // 0x0B4C + // tamil + { 0x0BC6, 0x0BBE }, // 0x0BCA + { 0x0BC7, 0x0BBE }, // 0x0BCB + { 0x0BC6, 0x0BD7 }, // 0x0BCC + // malayalam + { 0x0D46, 0x0D3E }, // 0x0D4A + { 0x0D47, 0x0D3E }, // 0x0D4B + { 0x0D46, 0x0D57 }, // 0x0D4C + // sinhala + { 0x0DD9, 0x0DCA }, // 0x0DDA + { 0x0DD9, 0x0DCF }, // 0x0DDC + { 0x0DD9, 0x0DCF, 0x0DCA }, // 0x0DDD + { 0x0DD9, 0x0DDF }, // 0x0DDE + }; + + private static final int MAX_DECOMPOSITION_LENGTH = 3; + + public static boolean isDecomposable(int c) { + return Arrays.binarySearch(DECOMPOSABLES, c) >= 0; + } + + public static int maximumDecompositionLength() { + return MAX_DECOMPOSITION_LENGTH; + } + + public static int[] decompose(int c, int[] da) { + int di = Arrays.binarySearch(DECOMPOSABLES, c); + if (di >= 0) { + return DECOMPOSITIONS[di]; + } else if ((da != null) && (da.length > 1)) { + da[0] = c; + da[1] = 0; + return da; + } else { + return new int[] { c }; + } + } + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharScript.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharScript.java new file mode 100644 index 00000000000..219c7fc1c40 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/CharScript.java @@ -0,0 +1,944 @@ +/* + * 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. + */ + +package org.apache.fontbox.ttf.advanced.util; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + *Script related utilities.
+ * + * @author Glenn Adams + */ +@SuppressWarnings("unchecked") +public final class CharScript { + + // CSOFF: LineLength + + // + // The following script codes are based on ISO 15924. Codes less than 1000 are + // official assignments from 15924; those equal to or greater than 1000 are FOP + // implementation specific. + // + /** hebrew script constant */ + public static final int SCRIPT_HEBREW = 125; // 'hebr' + /** mongolian script constant */ + public static final int SCRIPT_MONGOLIAN = 145; // 'mong' + /** arabic script constant */ + public static final int SCRIPT_ARABIC = 160; // 'arab' + /** greek script constant */ + public static final int SCRIPT_GREEK = 200; // 'grek' + /** latin script constant */ + public static final int SCRIPT_LATIN = 215; // 'latn' + /** cyrillic script constant */ + public static final int SCRIPT_CYRILLIC = 220; // 'cyrl' + /** georgian script constant */ + public static final int SCRIPT_GEORGIAN = 240; // 'geor' + /** bopomofo script constant */ + public static final int SCRIPT_BOPOMOFO = 285; // 'bopo' + /** hangul script constant */ + public static final int SCRIPT_HANGUL = 286; // 'hang' + /** gurmukhi script constant */ + public static final int SCRIPT_GURMUKHI = 310; // 'guru' + /** gurmukhi 2 script constant */ + public static final int SCRIPT_GURMUKHI_2 = 1310; // 'gur2' -- MSFT (pseudo) script tag for variant shaping semantics + /** devanagari script constant */ + public static final int SCRIPT_DEVANAGARI = 315; // 'deva' + /** devanagari 2 script constant */ + public static final int SCRIPT_DEVANAGARI_2 = 1315; // 'dev2' -- MSFT (pseudo) script tag for variant shaping semantics + /** gujarati script constant */ + public static final int SCRIPT_GUJARATI = 320; // 'gujr' + /** gujarati 2 script constant */ + public static final int SCRIPT_GUJARATI_2 = 1320; // 'gjr2' -- MSFT (pseudo) script tag for variant shaping semantics + /** bengali script constant */ + public static final int SCRIPT_BENGALI = 326; // 'beng' + /** bengali 2 script constant */ + public static final int SCRIPT_BENGALI_2 = 1326; // 'bng2' -- MSFT (pseudo) script tag for variant shaping semantics + /** oriya script constant */ + public static final int SCRIPT_ORIYA = 327; // 'orya' + /** oriya 2 script constant */ + public static final int SCRIPT_ORIYA_2 = 1327; // 'ory2' -- MSFT (pseudo) script tag for variant shaping semantics + /** tibetan script constant */ + public static final int SCRIPT_TIBETAN = 330; // 'tibt' + /** telugu script constant */ + public static final int SCRIPT_TELUGU = 340; // 'telu' + /** telugu 2 script constant */ + public static final int SCRIPT_TELUGU_2 = 1340; // 'tel2' -- MSFT (pseudo) script tag for variant shaping semantics + /** kannada script constant */ + public static final int SCRIPT_KANNADA = 345; // 'knda' + /** kannada 2 script constant */ + public static final int SCRIPT_KANNADA_2 = 1345; // 'knd2' -- MSFT (pseudo) script tag for variant shaping semantics + /** tamil script constant */ + public static final int SCRIPT_TAMIL = 346; // 'taml' + /** tamil 2 script constant */ + public static final int SCRIPT_TAMIL_2 = 1346; // 'tml2' -- MSFT (pseudo) script tag for variant shaping semantics + /** malayalam script constant */ + public static final int SCRIPT_MALAYALAM = 347; // 'mlym' + /** malayalam 2 script constant */ + public static final int SCRIPT_MALAYALAM_2 = 1347; // 'mlm2' -- MSFT (pseudo) script tag for variant shaping semantics + /** sinhalese script constant */ + public static final int SCRIPT_SINHALESE = 348; // 'sinh' + /** burmese script constant */ + public static final int SCRIPT_BURMESE = 350; // 'mymr' + /** thai script constant */ + public static final int SCRIPT_THAI = 352; // 'thai' + /** khmer script constant */ + public static final int SCRIPT_KHMER = 355; // 'khmr' + /** lao script constant */ + public static final int SCRIPT_LAO = 356; // 'laoo' + /** hiragana script constant */ + public static final int SCRIPT_HIRAGANA = 410; // 'hira' + /** ethiopic script constant */ + public static final int SCRIPT_ETHIOPIC = 430; // 'ethi' + /** han script constant */ + public static final int SCRIPT_HAN = 500; // 'hani' + /** katakana script constant */ + public static final int SCRIPT_KATAKANA = 410; // 'kana' + /** math script constant */ + public static final int SCRIPT_MATH = 995; // 'zmth' + /** symbol script constant */ + public static final int SCRIPT_SYMBOL = 996; // 'zsym' + /** undetermined script constant */ + public static final int SCRIPT_UNDETERMINED = 998; // 'zyyy' + /** uncoded script constant */ + public static final int SCRIPT_UNCODED = 999; // 'zzzz' + + /** + * A static (class) parameter indicating whether V2 indic shaping + * rules apply or not, with default beingtrue.
+ */
+ private static final boolean USE_V2_INDIC = true;
+
+ private CharScript() {
+ }
+
+ /**
+ * Determine if character c is punctuation.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character is punctuation
+ */
+ public static boolean isPunctuation(int c) {
+ if ((c >= 0x0021) && (c <= 0x002F)) { // basic latin punctuation
+ return true;
+ } else if ((c >= 0x003A) && (c <= 0x0040)) { // basic latin punctuation
+ return true;
+ } else if ((c >= 0x005F) && (c <= 0x0060)) { // basic latin punctuation
+ return true;
+ } else if ((c >= 0x007E) && (c <= 0x007E)) { // basic latin punctuation
+ return true;
+ } else if ((c >= 0x007E) && (c <= 0x007E)) { // basic latin punctuation
+ return true;
+ } else if ((c >= 0x00A1) && (c <= 0x00BF)) { // latin supplement punctuation
+ return true;
+ } else if ((c >= 0x00D7) && (c <= 0x00D7)) { // latin supplement punctuation
+ return true;
+ } else if ((c >= 0x00F7) && (c <= 0x00F7)) { // latin supplement punctuation
+ return true;
+ } else if ((c >= 0x2000) && (c <= 0x206F)) { // general punctuation
+ return true;
+ } else { // [TBD] - not complete
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c is a digit.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character is a digit
+ */
+ public static boolean isDigit(int c) {
+ if ((c >= 0x0030) && (c <= 0x0039)) { // basic latin digits
+ return true;
+ } else { // [TBD] - not complete
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hebrew script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hebrew script
+ */
+ public static boolean isHebrew(int c) {
+ if ((c >= 0x0590) && (c <= 0x05FF)) { // hebrew block
+ return true;
+ } else if ((c >= 0xFB00) && (c <= 0xFB4F)) { // hebrew presentation forms block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the mongolian script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to mongolian script
+ */
+ public static boolean isMongolian(int c) {
+ if ((c >= 0x1800) && (c <= 0x18AF)) { // mongolian block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the arabic script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to arabic script
+ */
+ public static boolean isArabic(int c) {
+ if ((c >= 0x0600) && (c <= 0x06FF)) { // arabic block
+ return true;
+ } else if ((c >= 0x0750) && (c <= 0x077F)) { // arabic supplement block
+ return true;
+ } else if ((c >= 0xFB50) && (c <= 0xFDFF)) { // arabic presentation forms a block
+ return true;
+ } else if ((c >= 0xFE70) && (c <= 0xFEFF)) { // arabic presentation forms b block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the greek script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to greek script
+ */
+ public static boolean isGreek(int c) {
+ if ((c >= 0x0370) && (c <= 0x03FF)) { // greek (and coptic) block
+ return true;
+ } else if ((c >= 0x1F00) && (c <= 0x1FFF)) { // greek extended block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the latin script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to latin script
+ */
+ public static boolean isLatin(int c) {
+ if ((c >= 0x0041) && (c <= 0x005A)) { // basic latin upper case
+ return true;
+ } else if ((c >= 0x0061) && (c <= 0x007A)) { // basic latin lower case
+ return true;
+ } else if ((c >= 0x00C0) && (c <= 0x00D6)) { // latin supplement upper case
+ return true;
+ } else if ((c >= 0x00D8) && (c <= 0x00DF)) { // latin supplement upper case
+ return true;
+ } else if ((c >= 0x00E0) && (c <= 0x00F6)) { // latin supplement lower case
+ return true;
+ } else if ((c >= 0x00F8) && (c <= 0x00FF)) { // latin supplement lower case
+ return true;
+ } else if ((c >= 0x0100) && (c <= 0x017F)) { // latin extended a
+ return true;
+ } else if ((c >= 0x0180) && (c <= 0x024F)) { // latin extended b
+ return true;
+ } else if ((c >= 0x1E00) && (c <= 0x1EFF)) { // latin extended additional
+ return true;
+ } else if ((c >= 0x2C60) && (c <= 0x2C7F)) { // latin extended c
+ return true;
+ } else if ((c >= 0xA720) && (c <= 0xA7FF)) { // latin extended d
+ return true;
+ } else if ((c >= 0xFB00) && (c <= 0xFB0F)) { // latin ligatures
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the cyrillic script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to cyrillic script
+ */
+ public static boolean isCyrillic(int c) {
+ if ((c >= 0x0400) && (c <= 0x04FF)) { // cyrillic block
+ return true;
+ } else if ((c >= 0x0500) && (c <= 0x052F)) { // cyrillic supplement block
+ return true;
+ } else if ((c >= 0x2DE0) && (c <= 0x2DFF)) { // cyrillic extended-a block
+ return true;
+ } else if ((c >= 0xA640) && (c <= 0xA69F)) { // cyrillic extended-b block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the georgian script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to georgian script
+ */
+ public static boolean isGeorgian(int c) {
+ if ((c >= 0x10A0) && (c <= 0x10FF)) { // georgian block
+ return true;
+ } else if ((c >= 0x2D00) && (c <= 0x2D2F)) { // georgian supplement block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hangul script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hangul script
+ */
+ public static boolean isHangul(int c) {
+ if ((c >= 0x1100) && (c <= 0x11FF)) { // hangul jamo
+ return true;
+ } else if ((c >= 0x3130) && (c <= 0x318F)) { // hangul compatibility jamo
+ return true;
+ } else if ((c >= 0xA960) && (c <= 0xA97F)) { // hangul jamo extended a
+ return true;
+ } else if ((c >= 0xAC00) && (c <= 0xD7A3)) { // hangul syllables
+ return true;
+ } else if ((c >= 0xD7B0) && (c <= 0xD7FF)) { // hangul jamo extended a
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the gurmukhi script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to gurmukhi script
+ */
+ public static boolean isGurmukhi(int c) {
+ if ((c >= 0x0A00) && (c <= 0x0A7F)) { // gurmukhi block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the devanagari script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to devanagari script
+ */
+ public static boolean isDevanagari(int c) {
+ if ((c >= 0x0900) && (c <= 0x097F)) { // devangari block
+ return true;
+ } else if ((c >= 0xA8E0) && (c <= 0xA8FF)) { // devangari extended block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the gujarati script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to gujarati script
+ */
+ public static boolean isGujarati(int c) {
+ if ((c >= 0x0A80) && (c <= 0x0AFF)) { // gujarati block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the bengali script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to bengali script
+ */
+ public static boolean isBengali(int c) {
+ if ((c >= 0x0980) && (c <= 0x09FF)) { // bengali block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the oriya script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to oriya script
+ */
+ public static boolean isOriya(int c) {
+ if ((c >= 0x0B00) && (c <= 0x0B7F)) { // oriya block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the tibetan script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to tibetan script
+ */
+ public static boolean isTibetan(int c) {
+ if ((c >= 0x0F00) && (c <= 0x0FFF)) { // tibetan block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the telugu script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to telugu script
+ */
+ public static boolean isTelugu(int c) {
+ if ((c >= 0x0C00) && (c <= 0x0C7F)) { // telugu block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the kannada script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to kannada script
+ */
+ public static boolean isKannada(int c) {
+ if ((c >= 0x0C00) && (c <= 0x0C7F)) { // kannada block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the tamil script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to tamil script
+ */
+ public static boolean isTamil(int c) {
+ if ((c >= 0x0B80) && (c <= 0x0BFF)) { // tamil block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the malayalam script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to malayalam script
+ */
+ public static boolean isMalayalam(int c) {
+ if ((c >= 0x0D00) && (c <= 0x0D7F)) { // malayalam block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the sinhalese script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to sinhalese script
+ */
+ public static boolean isSinhalese(int c) {
+ if ((c >= 0x0D80) && (c <= 0x0DFF)) { // sinhala block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the burmese script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to burmese script
+ */
+ public static boolean isBurmese(int c) {
+ if ((c >= 0x1000) && (c <= 0x109F)) { // burmese (myanmar) block
+ return true;
+ } else if ((c >= 0xAA60) && (c <= 0xAA7F)) { // burmese (myanmar) extended block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the thai script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to thai script
+ */
+ public static boolean isThai(int c) {
+ if ((c >= 0x0E00) && (c <= 0x0E7F)) { // thai block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the khmer script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to khmer script
+ */
+ public static boolean isKhmer(int c) {
+ if ((c >= 0x1780) && (c <= 0x17FF)) { // khmer block
+ return true;
+ } else if ((c >= 0x19E0) && (c <= 0x19FF)) { // khmer symbols block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the lao script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to lao script
+ */
+ public static boolean isLao(int c) {
+ if ((c >= 0x0E80) && (c <= 0x0EFF)) { // lao block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the ethiopic (amharic) script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to ethiopic (amharic) script
+ */
+ public static boolean isEthiopic(int c) {
+ if ((c >= 0x1200) && (c <= 0x137F)) { // ethiopic block
+ return true;
+ } else if ((c >= 0x1380) && (c <= 0x139F)) { // ethoipic supplement block
+ return true;
+ } else if ((c >= 0x2D80) && (c <= 0x2DDF)) { // ethoipic extended block
+ return true;
+ } else if ((c >= 0xAB00) && (c <= 0xAB2F)) { // ethoipic extended-a block
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the han (unified cjk) script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to han (unified cjk) script
+ */
+ public static boolean isHan(int c) {
+ if ((c >= 0x3400) && (c <= 0x4DBF)) {
+ return true; // cjk unified ideographs extension a
+ } else if ((c >= 0x4E00) && (c <= 0x9FFF)) {
+ return true; // cjk unified ideographs
+ } else if ((c >= 0xF900) && (c <= 0xFAFF)) {
+ return true; // cjk compatibility ideographs
+ } else if ((c >= 0x20000) && (c <= 0x2A6DF)) {
+ return true; // cjk unified ideographs extension b
+ } else if ((c >= 0x2A700) && (c <= 0x2B73F)) {
+ return true; // cjk unified ideographs extension c
+ } else if ((c >= 0x2F800) && (c <= 0x2FA1F)) {
+ return true; // cjk compatibility ideographs supplement
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the bopomofo script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to bopomofo script
+ */
+ public static boolean isBopomofo(int c) {
+ if ((c >= 0x3100) && (c <= 0x312F)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the hiragana script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to hiragana script
+ */
+ public static boolean isHiragana(int c) {
+ if ((c >= 0x3040) && (c <= 0x309F)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if character c belong to the katakana script.
+ * @param c a character represented as a unicode scalar value
+ * @return true if character belongs to katakana script
+ */
+ public static boolean isKatakana(int c) {
+ if ((c >= 0x30A0) && (c <= 0x30FF)) {
+ return true;
+ } else if ((c >= 0x31F0) && (c <= 0x31FF)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Obtain ISO15924 numeric script code of character. If script is not or cannot be determined,
+ * then the script code 998 ('zyyy') is returned.
+ * @param c the character to obtain script
+ * @return an ISO15924 script code
+ */
+ public static int scriptOf(int c) { // [TBD] - needs optimization!!!
+ if (isAnySpace(c)) {
+ return SCRIPT_UNDETERMINED;
+ } else if (isPunctuation(c)) {
+ return SCRIPT_UNDETERMINED;
+ } else if (isDigit(c)) {
+ return SCRIPT_UNDETERMINED;
+ } else if (isLatin(c)) {
+ return SCRIPT_LATIN;
+ } else if (isCyrillic(c)) {
+ return SCRIPT_CYRILLIC;
+ } else if (isGreek(c)) {
+ return SCRIPT_GREEK;
+ } else if (isHan(c)) {
+ return SCRIPT_HAN;
+ } else if (isBopomofo(c)) {
+ return SCRIPT_BOPOMOFO;
+ } else if (isKatakana(c)) {
+ return SCRIPT_KATAKANA;
+ } else if (isHiragana(c)) {
+ return SCRIPT_HIRAGANA;
+ } else if (isHangul(c)) {
+ return SCRIPT_HANGUL;
+ } else if (isArabic(c)) {
+ return SCRIPT_ARABIC;
+ } else if (isHebrew(c)) {
+ return SCRIPT_HEBREW;
+ } else if (isMongolian(c)) {
+ return SCRIPT_MONGOLIAN;
+ } else if (isGeorgian(c)) {
+ return SCRIPT_GEORGIAN;
+ } else if (isGurmukhi(c)) {
+ return useV2IndicRules(SCRIPT_GURMUKHI);
+ } else if (isDevanagari(c)) {
+ return useV2IndicRules(SCRIPT_DEVANAGARI);
+ } else if (isGujarati(c)) {
+ return useV2IndicRules(SCRIPT_GUJARATI);
+ } else if (isBengali(c)) {
+ return useV2IndicRules(SCRIPT_BENGALI);
+ } else if (isOriya(c)) {
+ return useV2IndicRules(SCRIPT_ORIYA);
+ } else if (isTibetan(c)) {
+ return SCRIPT_TIBETAN;
+ } else if (isTelugu(c)) {
+ return useV2IndicRules(SCRIPT_TELUGU);
+ } else if (isKannada(c)) {
+ return useV2IndicRules(SCRIPT_KANNADA);
+ } else if (isTamil(c)) {
+ return useV2IndicRules(SCRIPT_TAMIL);
+ } else if (isMalayalam(c)) {
+ return useV2IndicRules(SCRIPT_MALAYALAM);
+ } else if (isSinhalese(c)) {
+ return SCRIPT_SINHALESE;
+ } else if (isBurmese(c)) {
+ return SCRIPT_BURMESE;
+ } else if (isThai(c)) {
+ return SCRIPT_THAI;
+ } else if (isKhmer(c)) {
+ return SCRIPT_KHMER;
+ } else if (isLao(c)) {
+ return SCRIPT_LAO;
+ } else if (isEthiopic(c)) {
+ return SCRIPT_ETHIOPIC;
+ } else {
+ return SCRIPT_UNDETERMINED;
+ }
+ }
+
+ /**
+ * Obtain the V2 indic script code corresponding to V1 indic script code SC if
+ * and only iff V2 indic rules apply; otherwise return SC.
+ * @param sc a V1 indic script code
+ * @return either SC or the V2 flavor of SC if V2 indic rules apply
+ */
+ public static int useV2IndicRules(int sc) {
+ if (USE_V2_INDIC) {
+ return (sc < 1000) ? (sc + 1000) : sc;
+ } else {
+ return sc;
+ }
+ }
+
+ /**
+ * Obtain the script codes of each character in a character sequence. If script
+ * is not or cannot be determined for some character, then the script code 998
+ * ('zyyy') is returned.
+ * @param cs the character sequence
+ * @return a (possibly empty) array of script codes
+ */
+ public static int[] scriptsOf(CharSequence cs) {
+ Set s = new HashSet();
+ for (int i = 0, n = cs.length(); i < n; i++) {
+ s.add(Integer.valueOf(scriptOf(cs.charAt(i))));
+ }
+ int[] sa = new int [ s.size() ];
+ int ns = 0;
+ for (Iterator it = s.iterator(); it.hasNext();) {
+ sa [ ns++ ] = ((Integer) it.next()) .intValue();
+ }
+ Arrays.sort(sa);
+ return sa;
+ }
+
+ /**
+ * Determine the dominant script of a character sequence.
+ * @param cs the character sequence
+ * @return the dominant script or SCRIPT_UNDETERMINED
+ */
+ public static int dominantScript(CharSequence cs) {
+ Map m = new HashMap();
+ for (int i = 0, n = cs.length(); i < n; i++) {
+ int c = cs.charAt(i);
+ int s = scriptOf(c);
+ Integer k = Integer.valueOf(s);
+ Integer v = (Integer) m.get(k);
+ if (v != null) {
+ m.put(k, Integer.valueOf(v.intValue() + 1));
+ } else {
+ m.put(k, Integer.valueOf(0));
+ }
+ }
+ int sMax = -1;
+ int cMax = -1;
+ for (Iterator it = m.entrySet().iterator(); it.hasNext();) {
+ Map.Entry e = (Map.Entry) it.next();
+ Integer k = (Integer) e.getKey();
+ int s = k.intValue();
+ switch (s) {
+ case SCRIPT_UNDETERMINED:
+ case SCRIPT_UNCODED:
+ break;
+ default:
+ Integer v = (Integer) e.getValue();
+ assert v != null;
+ int c = v.intValue();
+ if (c > cMax) {
+ cMax = c;
+ sMax = s;
+ }
+ break;
+ }
+ }
+ if (sMax < 0) {
+ sMax = SCRIPT_UNDETERMINED;
+ }
+ return sMax;
+ }
+
+ /**
+ * Determine if script tag denotes an 'Indic' script, where a
+ * script is an 'Indic' script if it is intended to be processed by
+ * the generic 'Indic' Script Processor.
+ * @param script a script tag
+ * @return true if script tag is a designated 'Indic' script
+ */
+ public static boolean isIndicScript(String script) {
+ return isIndicScript(scriptCodeFromTag(script));
+ }
+
+ /**
+ * Determine if script tag denotes an 'Indic' script, where a
+ * script is an 'Indic' script if it is intended to be processed by
+ * the generic 'Indic' Script Processor.
+ * @param script a script code
+ * @return true if script code is a designated 'Indic' script
+ */
+ public static boolean isIndicScript(int script) {
+ switch (script) {
+ case SCRIPT_BENGALI:
+ case SCRIPT_BENGALI_2:
+ case SCRIPT_BURMESE:
+ case SCRIPT_DEVANAGARI:
+ case SCRIPT_DEVANAGARI_2:
+ case SCRIPT_GUJARATI:
+ case SCRIPT_GUJARATI_2:
+ case SCRIPT_GURMUKHI:
+ case SCRIPT_GURMUKHI_2:
+ case SCRIPT_KANNADA:
+ case SCRIPT_KANNADA_2:
+ case SCRIPT_MALAYALAM:
+ case SCRIPT_MALAYALAM_2:
+ case SCRIPT_ORIYA:
+ case SCRIPT_ORIYA_2:
+ case SCRIPT_TAMIL:
+ case SCRIPT_TAMIL_2:
+ case SCRIPT_TELUGU:
+ case SCRIPT_TELUGU_2:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Determine the script tag associated with an internal script code.
+ * @param code the script code
+ * @return a script tag
+ */
+ public static String scriptTagFromCode(int code) {
+ MapException thrown during when attempting to map glyphs to associated characters + * in the case that the associated characters do not represent a compact interval.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public class DiscontinuousAssociationException extends RuntimeException { + /** + * Instantiate discontinuous association exception + */ + public DiscontinuousAssociationException() { + super(); + } + /** + * Instantiate discontinuous association exception + * @param message a message string + */ + public DiscontinuousAssociationException(String message) { + super(message); + } +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphContextTester.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphContextTester.java new file mode 100644 index 00000000000..f3e410aafd8 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphContextTester.java @@ -0,0 +1,43 @@ +/* + * 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.fontbox.ttf.advanced.util; + +// CSOFF: LineLengthCheck + +/** + *Interface for testing the originating (source) character context of a glyph sequence.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public interface GlyphContextTester { + + /** + * Perform a test on a glyph sequence in a specific (originating) character context. + * @param script governing script + * @param language governing language + * @param feature governing feature + * @param gs glyph sequence to test + * @param index index into glyph sequence to test + * @param flags that apply to lookup in scope + * @return true if test is satisfied + */ + boolean test(String script, String language, String feature, GlyphSequence gs, int index, int flags); + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphSequence.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphSequence.java new file mode 100644 index 00000000000..ab903c66400 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphSequence.java @@ -0,0 +1,615 @@ +/* + * 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.fontbox.ttf.advanced.util; + +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.List; + +// CSOFF: LineLengthCheck + +/** + *A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes, + * and a sequence of character associations, where, for each glyph in the sequence of glyph + * codes, there is a corresponding character association. Character associations server to + * relate the glyph codes in a glyph sequence to the specific characters in an original + * character code sequence with which the glyph codes are associated.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +@SuppressWarnings("unchecked") +public class GlyphSequence implements Cloneable { + + /** default character buffer capacity in case new character buffer is created */ + private static final int DEFAULT_CHARS_CAPACITY = 8; + + /** character buffer */ + private IntBuffer characters; + /** glyph buffer */ + private IntBuffer glyphs; + /** association list */ + private List associations; + /** predications flag */ + private boolean predications; + + /** + * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced + * character and glyph buffers and associations. If characters is null, then + * an empty character buffer is created. If glyphs is null, then a glyph buffer + * is created whose capacity is that of the character buffer. If associations is + * null, then identity associations are created. + * @param characters a (possibly null) buffer of associated (originating) characters + * @param glyphs a (possibly null) buffer of glyphs + * @param associations a (possibly null) array of glyph to character associations + * @param predications true if predications are enabled + */ + public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations, boolean predications) { + if (characters == null) { + characters = IntBuffer.allocate(DEFAULT_CHARS_CAPACITY); + } + if (glyphs == null) { + glyphs = IntBuffer.allocate(characters.capacity()); + } + if (associations == null) { + associations = makeIdentityAssociations(characters.limit(), glyphs.limit()); + } + this.characters = characters; + this.glyphs = glyphs; + this.associations = associations; + this.predications = predications; + } + + /** + * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced + * character and glyph buffers and associations. If characters is null, then + * an empty character buffer is created. If glyphs is null, then a glyph buffer + * is created whose capacity is that of the character buffer. If associations is + * null, then identity associations are created. + * @param characters a (possibly null) buffer of associated (originating) characters + * @param glyphs a (possibly null) buffer of glyphs + * @param associations a (possibly null) array of glyph to character associations + */ + public GlyphSequence(IntBuffer characters, IntBuffer glyphs, List associations) { + this (characters, glyphs, associations, false); + } + + /** + * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares + * the character array of the existing sequence (but not the buffer object), and creates new copies + * of glyphs buffer and association list. + * @param gs an existing glyph sequence + */ + public GlyphSequence(GlyphSequence gs) { + this (gs.characters.duplicate(), copyBuffer(gs.glyphs), copyAssociations(gs.associations), gs.predications); + } + + /** + * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares + * the character array of the existing sequence (but not the buffer object), but uses the specified + * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified + * of glyphs buffer and association list. + * backtrack, input, and lookahead association arrays to populate the associations. + * @param gs an existing glyph sequence + * @param bga backtrack glyph array + * @param iga input glyph array + * @param lga lookahead glyph array + * @param bal backtrack association list + * @param ial input association list + * @param lal lookahead association list + */ + public GlyphSequence(GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal) { + this (gs.characters.duplicate(), concatGlyphs(bga, iga, lga), concatAssociations(bal, ial, lal), gs.predications); + } + + /** + * Obtain reference to underlying character buffer. + * @return character buffer reference + */ + public IntBuffer getCharacters() { + return characters; + } + + /** + * Obtain array of characters. Ifcopy is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of characters in array.
+ * To determine the number of characters, use {@link #getCharacterCount}.
+ * @param copy true if to return a newly instantiated array of characters
+ * @return array of characters
+ */
+ public int[] getCharacterArray(boolean copy) {
+ if (copy) {
+ return toArray(characters);
+ } else {
+ return characters.array();
+ }
+ }
+
+ /**
+ * Obtain the number of characters in character array, where
+ * each character constitutes a unicode scalar value.
+ * @return number of characters available in character array
+ */
+ public int getCharacterCount() {
+ return characters.limit();
+ }
+
+ /**
+ * Obtain glyph id at specified index.
+ * @param index to obtain glyph
+ * @return the glyph identifier of glyph at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public int getGlyph(int index) throws IndexOutOfBoundsException {
+ return glyphs.get(index);
+ }
+
+ /**
+ * Set glyph id at specified index.
+ * @param index to set glyph
+ * @param gi glyph index
+ * @throws IndexOutOfBoundsException if index is greater or equal to
+ * the limit of the underlying glyph buffer
+ */
+ public void setGlyph(int index, int gi) throws IndexOutOfBoundsException {
+ if (gi > 65535) {
+ gi = 65535;
+ }
+ glyphs.put(index, gi);
+ }
+
+ /**
+ * Obtain reference to underlying glyph buffer.
+ * @return glyph buffer reference
+ */
+ public IntBuffer getGlyphs() {
+ return glyphs;
+ }
+
+ /**
+ * Obtain count glyphs starting at offset. If count is
+ * negative, then it is treated as if the number of available glyphs
+ * were specified.
+ * @param offset into glyph sequence
+ * @param count of glyphs to obtain starting at offset, or negative,
+ * indicating all avaialble glyphs starting at offset
+ * @return glyph array
+ */
+ public int[] getGlyphs(int offset, int count) {
+ int ng = getGlyphCount();
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > ng) {
+ offset = ng;
+ }
+ if (count < 0) {
+ count = ng - offset;
+ }
+ int[] ga = new int [ count ];
+ for (int i = offset, n = offset + count, k = 0; i < n; i++) {
+ if (k < ga.length) {
+ ga [ k++ ] = glyphs.get(i);
+ }
+ }
+ return ga;
+ }
+
+ /**
+ * Obtain array of glyphs. If copy is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of glyphs in array.
+ * To determine the number of glyphs, use {@link #getGlyphCount}.
+ * @param copy true if to return a newly instantiated array of glyphs
+ * @return array of glyphs
+ */
+ public int[] getGlyphArray(boolean copy) {
+ if (copy) {
+ return toArray(glyphs);
+ } else {
+ return glyphs.array();
+ }
+ }
+
+ /**
+ * Obtain the number of glyphs in glyphs array, where
+ * each glyph constitutes a font specific glyph index.
+ * @return number of glyphs available in character array
+ */
+ public int getGlyphCount() {
+ return glyphs.limit();
+ }
+
+ /**
+ * Obtain association at specified index.
+ * @param index into associations array
+ * @return glyph to character associations at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public CharAssociation getAssociation(int index) throws IndexOutOfBoundsException {
+ return (CharAssociation) associations.get(index);
+ }
+
+ /**
+ * Obtain reference to underlying associations list.
+ * @return associations list
+ */
+ public List getAssociations() {
+ return associations;
+ }
+
+ /**
+ * Obtain count associations starting at offset.
+ * @param offset into glyph sequence
+ * @param count of associations to obtain starting at offset, or negative,
+ * indicating all avaialble associations starting at offset
+ * @return associations
+ */
+ public CharAssociation[] getAssociations(int offset, int count) {
+ int ng = getGlyphCount();
+ if (offset < 0) {
+ offset = 0;
+ } else if (offset > ng) {
+ offset = ng;
+ }
+ if (count < 0) {
+ count = ng - offset;
+ }
+ CharAssociation[] aa = new CharAssociation [ count ];
+ for (int i = offset, n = offset + count, k = 0; i < n; i++) {
+ if (k < aa.length) {
+ aa [ k++ ] = (CharAssociation) associations.get(i);
+ }
+ }
+ return aa;
+ }
+
+ /**
+ * Enable or disable predications.
+ * @param enable true if predications are to be enabled; otherwise false to disable
+ */
+ public void setPredications(boolean enable) {
+ this.predications = enable;
+ }
+
+ /**
+ * Obtain predications state.
+ * @return true if predications are enabled
+ */
+ public boolean getPredications() {
+ return this.predications;
+ }
+
+ /**
+ * Set predication <KEY,VALUE> at glyph sequence OFFSET.
+ * @param offset offset (index) into glyph sequence
+ * @param key predication key
+ * @param value predication value
+ */
+ public void setPredication(int offset, String key, Object value) {
+ if (predications) {
+ CharAssociation[] aa = getAssociations(offset, 1);
+ CharAssociation ca = aa[0];
+ ca.setPredication(key, value);
+ }
+ }
+
+ /**
+ * Get predication KEY at glyph sequence OFFSET.
+ * @param offset offset (index) into glyph sequence
+ * @param key predication key
+ * @return predication KEY at OFFSET or null if none exists
+ */
+ public Object getPredication(int offset, String key) {
+ if (predications) {
+ CharAssociation[] aa = getAssociations(offset, 1);
+ CharAssociation ca = aa[0];
+ return ca.getPredication(key);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Compare glyphs.
+ * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared
+ * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
+ * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
+ */
+ public int compareGlyphs(IntBuffer gb) {
+ int ng = getGlyphCount();
+ for (int i = 0, n = gb.limit(); i < n; i++) {
+ if (i < ng) {
+ int g1 = glyphs.get(i);
+ int g2 = gb.get(i);
+ if (g1 > g2) {
+ return 1;
+ } else if (g1 < g2) {
+ return -1;
+ }
+ } else {
+ return -1; // this gb is a proper prefix of specified gb
+ }
+ }
+ return 0; // same lengths with no difference
+ }
+
+ /** {@inheritDoc} */
+ public Object clone() {
+ try {
+ GlyphSequence gs = (GlyphSequence) super.clone();
+ gs.characters = copyBuffer(characters);
+ gs.glyphs = copyBuffer(glyphs);
+ gs.associations = copyAssociations(associations);
+ return gs;
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('{');
+ sb.append("chars = [");
+ sb.append(characters);
+ sb.append("], glyphs = [");
+ sb.append(glyphs);
+ sb.append("], associations = [");
+ sb.append(associations);
+ sb.append("]");
+ sb.append('}');
+ return sb.toString();
+ }
+
+ /**
+ * Determine if two arrays of glyphs are identical.
+ * @param ga1 first glyph array
+ * @param ga2 second glyph array
+ * @return true if arrays are botth null or both non-null and have identical elements
+ */
+ public static boolean sameGlyphs(int[] ga1, int[] ga2) {
+ if (ga1 == ga2) {
+ return true;
+ } else if ((ga1 == null) || (ga2 == null)) {
+ return false;
+ } else if (ga1.length != ga2.length) {
+ return false;
+ } else {
+ for (int i = 0, n = ga1.length; i < n; i++) {
+ if (ga1[i] != ga2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Concatenante glyph arrays.
+ * @param bga backtrack glyph array
+ * @param iga input glyph array
+ * @param lga lookahead glyph array
+ * @return new integer buffer containing concatenated glyphs
+ */
+ public static IntBuffer concatGlyphs(int[] bga, int[] iga, int[] lga) {
+ int ng = 0;
+ if (bga != null) {
+ ng += bga.length;
+ }
+ if (iga != null) {
+ ng += iga.length;
+ }
+ if (lga != null) {
+ ng += lga.length;
+ }
+ IntBuffer gb = IntBuffer.allocate(ng);
+ if (bga != null) {
+ gb.put(bga);
+ }
+ if (iga != null) {
+ gb.put(iga);
+ }
+ if (lga != null) {
+ gb.put(lga);
+ }
+ gb.flip();
+ return gb;
+ }
+
+ /**
+ * Concatenante association arrays.
+ * @param baa backtrack association array
+ * @param iaa input association array
+ * @param laa lookahead association array
+ * @return new list containing concatenated associations
+ */
+ public static List concatAssociations(CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa) {
+ int na = 0;
+ if (baa != null) {
+ na += baa.length;
+ }
+ if (iaa != null) {
+ na += iaa.length;
+ }
+ if (laa != null) {
+ na += laa.length;
+ }
+ if (na > 0) {
+ List gl = new ArrayList(na);
+ if (baa != null) {
+ for (int i = 0; i < baa.length; i++) {
+ gl.add(baa[i]);
+ }
+ }
+ if (iaa != null) {
+ for (int i = 0; i < iaa.length; i++) {
+ gl.add(iaa[i]);
+ }
+ }
+ if (laa != null) {
+ for (int i = 0; i < laa.length; i++) {
+ gl.add(laa[i]);
+ }
+ }
+ return gl;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Join (concatenate) glyph sequences.
+ * @param gs original glyph sequence from which to reuse character array reference
+ * @param sa array of glyph sequences, whose glyph arrays and association lists are to be concatenated
+ * @return new glyph sequence referring to character array of GS and concatenated glyphs and associations of SA
+ */
+ public static GlyphSequence join(GlyphSequence gs, GlyphSequence[] sa) {
+ assert sa != null;
+ int tg = 0;
+ int ta = 0;
+ for (int i = 0, n = sa.length; i < n; i++) {
+ GlyphSequence s = sa [ i ];
+ IntBuffer ga = s.getGlyphs();
+ assert ga != null;
+ int ng = ga.limit();
+ List al = s.getAssociations();
+ assert al != null;
+ int na = al.size();
+ assert na == ng;
+ tg += ng;
+ ta += na;
+ }
+ IntBuffer uga = IntBuffer.allocate(tg);
+ ArrayList ual = new ArrayList(ta);
+ for (int i = 0, n = sa.length; i < n; i++) {
+ GlyphSequence s = sa [ i ];
+ uga.put(s.getGlyphs());
+ ual.addAll(s.getAssociations());
+ }
+ return new GlyphSequence(gs.getCharacters(), uga, ual, gs.getPredications());
+ }
+
+ /**
+ * Reorder sequence such that [SOURCE,SOURCE+COUNT) is moved just prior to TARGET.
+ * @param gs input sequence
+ * @param source index of sub-sequence to reorder
+ * @param count length of sub-sequence to reorder
+ * @param target index to which source sub-sequence is to be moved
+ * @return reordered sequence (or original if no reordering performed)
+ */
+ public static GlyphSequence reorder(GlyphSequence gs, int source, int count, int target) {
+ if (source != target) {
+ int ng = gs.getGlyphCount();
+ int[] ga = gs.getGlyphArray(false);
+ int[] nga = new int [ ng ];
+ CharAssociation[] aa = gs.getAssociations(0, ng);
+ CharAssociation[] naa = new CharAssociation [ ng ];
+ if (source < target) {
+ int t = 0;
+ for (int s = 0, e = source; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = source + count, e = target; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = source, e = source + count; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = target, e = ng; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ } else {
+ int t = 0;
+ for (int s = 0, e = target; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = source, e = source + count; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = target, e = source; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ for (int s = source + count, e = ng; s < e; s++, t++) {
+ nga[t] = ga[s];
+ naa[t] = aa[s];
+ }
+ }
+ return new GlyphSequence(gs, null, nga, null, null, naa, null);
+ } else {
+ return gs;
+ }
+ }
+
+ private static int[] toArray(IntBuffer ib) {
+ if (ib != null) {
+ int n = ib.limit();
+ int[] ia = new int[n];
+ ib.get(ia, 0, n);
+ return ia;
+ } else {
+ return new int[0];
+ }
+ }
+
+ private static List makeIdentityAssociations(int numChars, int numGlyphs) {
+ int nc = numChars;
+ int ng = numGlyphs;
+ List av = new ArrayList(ng);
+ for (int i = 0, n = ng; i < n; i++) {
+ int k = (i > nc) ? nc : i;
+ av.add(new CharAssociation(i, (k == nc) ? 0 : 1));
+ }
+ return av;
+ }
+
+ private static IntBuffer copyBuffer(IntBuffer ib) {
+ if (ib != null) {
+ int[] ia = new int [ ib.capacity() ];
+ int p = ib.position();
+ int l = ib.limit();
+ System.arraycopy(ib.array(), 0, ia, 0, ia.length);
+ return IntBuffer.wrap(ia, p, l - p);
+ } else {
+ return null;
+ }
+ }
+
+ private static List copyAssociations(List ca) {
+ if (ca != null) {
+ return new ArrayList(ca);
+ } else {
+ return ca;
+ }
+ }
+
+}
diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphTester.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphTester.java
new file mode 100644
index 00000000000..061a7c86dc7
--- /dev/null
+++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/GlyphTester.java
@@ -0,0 +1,37 @@
+/*
+ * 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.fontbox.ttf.advanced.util;
+
+/**
+ * Interface for testing glyph properties according to glyph identifier.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public interface GlyphTester { + + /** + * Perform a test on a glyph identifier. + * @param gi glyph identififer + * @param flags that apply to lookup in scope + * @return true if test is satisfied + */ + boolean test(int gi, int flags); + +} diff --git a/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/ScriptContextTester.java b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/ScriptContextTester.java new file mode 100644 index 00000000000..e51f19f4f44 --- /dev/null +++ b/fontbox/src/main/java/org/apache/fontbox/ttf/advanced/util/ScriptContextTester.java @@ -0,0 +1,36 @@ +/* + * 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.fontbox.ttf.advanced.util; + +/** + *Interface for providing script specific context testers.
+ * + *This work was originally authored by Glenn Adams (gadams@apache.org).
+ */ +public interface ScriptContextTester { + + /** + * Obtain a glyph context tester for the specified feature. + * @param feature a feature identifier + * @return a glyph context tester or null if none available for the specified feature + */ + GlyphContextTester getTester(String feature); + +}