/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* $Id$ */

package org.apache.fop.complexscripts.fonts;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.fop.complexscripts.scripts.ScriptProcessor;
import org.apache.fop.complexscripts.util.CharAssociation;
import org.apache.fop.complexscripts.util.GlyphSequence;
import org.apache.fop.complexscripts.util.GlyphTester;

// CSOFF: LineLengthCheck

The GlyphSubstitutionTable class is a glyph table that implements GlyphSubstitution functionality.

This work was originally authored by Glenn Adams (gadams@apache.org).

/** * <p>The <code>GlyphSubstitutionTable</code> class is a glyph table that implements * <code>GlyphSubstitution</code> functionality.</p> * * <p>This work was originally authored by Glenn Adams (gadams@apache.org).</p> */
public class GlyphSubstitutionTable extends GlyphTable {
logging instance
/** logging instance */
private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class);
single substitution subtable type
/** single substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_SINGLE = 1;
multiple substitution subtable type
/** multiple substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_MULTIPLE = 2;
alternate substitution subtable type
/** alternate substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3;
ligature substitution subtable type
/** ligature substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4;
contextual substitution subtable type
/** contextual substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_CONTEXTUAL = 5;
chained contextual substitution subtable type
/** chained contextual substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 6;
extension substitution substitution subtable type
/** extension substitution substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION = 7;
reverse chained contextual single substitution subtable type
/** reverse chained contextual single substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE = 8;
Instantiate a GlyphSubstitutionTable object using the specified lookups and subtables.
Params:
  • gdef – glyph definition table that applies
  • lookups – a map of lookup specifications to subtable identifier strings
  • subtables – a list of identified subtables
/** * Instantiate a <code>GlyphSubstitutionTable</code> 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(GlyphDefinitionTable gdef, Map lookups, List subtables, Map<String, ScriptProcessor> processors) { super(gdef, lookups, processors); if ((subtables == null) || (subtables.size() == 0)) { throw new AdvancedTypographicTableFormatException("subtables must be non-empty"); } else { for (Object o : subtables) { if (o instanceof GlyphSubstitutionSubtable) { addSubtable((GlyphSubtable) o); } else { throw new AdvancedTypographicTableFormatException("subtable must be a glyph substitution subtable"); } } freezeSubtables(); } }
Perform substitution processing using all matching lookups.
Params:
  • gs – an input glyph sequence
  • script – a script identifier
  • language – a language identifier
Returns:the substituted (output) glyph sequence
/** * Perform substitution processing using all matching lookups. * @param gs an input glyph sequence * @param script a script identifier * @param language a language identifier * @return the substituted (output) glyph sequence */
public GlyphSequence substitute(GlyphSequence gs, String script, String language) { GlyphSequence ogs; Map<LookupSpec, List<LookupTable>> lookups = matchLookups(script, language, "*"); if ((lookups != null) && (lookups.size() > 0)) { ScriptProcessor sp = ScriptProcessor.getInstance(script, processors); ogs = sp.substitute(this, gs, script, language, lookups); } else { ogs = gs; } return ogs; }
Map a lookup type name to its constant (integer) value.
Params:
  • name – lookup type name
Returns:lookup type
/** * 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 = GSUB_LOOKUP_TYPE_SINGLE; } else if ("multiple".equals(s)) { t = GSUB_LOOKUP_TYPE_MULTIPLE; } else if ("alternate".equals(s)) { t = GSUB_LOOKUP_TYPE_ALTERNATE; } else if ("ligature".equals(s)) { t = GSUB_LOOKUP_TYPE_LIGATURE; } else if ("contextual".equals(s)) { t = GSUB_LOOKUP_TYPE_CONTEXTUAL; } else if ("chainedcontextual".equals(s)) { t = GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL; } else if ("extensionsubstitution".equals(s)) { t = GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION; } else if ("reversechainiingcontextualsingle".equals(s)) { t = GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE; } else { t = -1; } return t; }
Map a lookup type constant (integer) value to its name.
Params:
  • type – lookup type
Returns:lookup type name
/** * 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 GSUB_LOOKUP_TYPE_SINGLE: tn = "single"; break; case GSUB_LOOKUP_TYPE_MULTIPLE: tn = "multiple"; break; case GSUB_LOOKUP_TYPE_ALTERNATE: tn = "alternate"; break; case GSUB_LOOKUP_TYPE_LIGATURE: tn = "ligature"; break; case GSUB_LOOKUP_TYPE_CONTEXTUAL: tn = "contextual"; break; case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL: tn = "chainedcontextual"; break; case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION: tn = "extensionsubstitution"; break; case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE: tn = "reversechainiingcontextualsingle"; break; default: tn = "unknown"; break; } return tn; }
Create a substitution subtable according to the specified arguments.
Params:
  • type – subtable type
  • id – subtable identifier
  • sequence – subtable sequence
  • flags – subtable flags
  • format – subtable format
  • coverage – subtable coverage table
  • entries – subtable entries
Returns:a glyph subtable instance
/** * Create a substitution 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 GSUB_LOOKUP_TYPE_SINGLE: st = SingleSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_MULTIPLE: st = MultipleSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_ALTERNATE: st = AlternateSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_LIGATURE: st = LigatureSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_CONTEXTUAL: st = ContextualSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL: st = ChainedContextualSubtable.create(id, sequence, flags, format, coverage, entries); break; case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE: st = ReverseChainedSingleSubtable.create(id, sequence, flags, format, coverage, entries); break; default: break; } return st; }
Create a substitution subtable according to the specified arguments.
Params:
  • type – subtable type
  • id – subtable identifier
  • sequence – subtable sequence
  • flags – subtable flags
  • format – subtable format
  • coverage – list of coverage table entries
  • entries – subtable entries
Returns:a glyph subtable instance
/** * Create a substitution 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); } private abstract static class SingleSubtable extends GlyphSubstitutionSubtable { SingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_SINGLE; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof SingleSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { int go = getGlyphForCoverageIndex(ci, gi); if ((go < 0) || (go > 65535)) { go = 65535; } ss.putGlyph(go, ss.getAssociation(), Boolean.TRUE); ss.consume(1); return true; } }
Obtain glyph for coverage index.
Params:
  • ci – coverage index
  • gi – original glyph index
Throws:
Returns:substituted glyph value
/** * Obtain glyph for coverage index. * @param ci coverage index * @param gi original glyph index * @return substituted glyph value * @throws IllegalArgumentException if coverage index is not valid */
public abstract int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException; static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new SingleSubtableFormat1(id, sequence, flags, format, coverage, entries); } else if (format == 2) { return new SingleSubtableFormat2(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class SingleSubtableFormat1 extends SingleSubtable { private int delta; private int ciMax; SingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { List entries = new ArrayList(1); entries.add(delta); return entries; }
{@inheritDoc}
/** {@inheritDoc} */
public int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException { if (ci <= ciMax) { return gi + delta; } else { throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + ciMax); } } private void populate(List entries) { if ((entries == null) || (entries.size() != 1)) { throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null and contain exactly one entry"); } else { Object o = entries.get(0); int delta = 0; if (o instanceof Integer) { delta = (Integer) o; } else { throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Integer, but is: " + o); } this.delta = delta; this.ciMax = getCoverageSize() - 1; } } } private static class SingleSubtableFormat2 extends SingleSubtable { private int[] glyphs; SingleSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { List entries = new ArrayList(glyphs.length); for (int glyph : glyphs) { entries.add(glyph); } return entries; }
{@inheritDoc}
/** {@inheritDoc} */
public int getGlyphForCoverageIndex(int ci, int gi) throws IllegalArgumentException { if (glyphs == null) { return -1; } else if (ci >= glyphs.length) { throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + glyphs.length); } else { return glyphs [ ci ]; } } private void populate(List entries) { int i = 0; int n = entries.size(); int[] glyphs = new int [ n ]; for (Object o : entries) { if (o instanceof Integer) { int gid = (Integer) o; if ((gid >= 0) && (gid < 65536)) { glyphs[i++] = gid; } else { throw new AdvancedTypographicTableFormatException("illegal glyph index: " + gid); } } else { throw new AdvancedTypographicTableFormatException("illegal entries entry, must be Integer: " + o); } } assert i == n; assert this.glyphs == null; this.glyphs = glyphs; } } private abstract static class MultipleSubtable extends GlyphSubstitutionSubtable { public MultipleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_MULTIPLE; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof MultipleSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { int[] ga = getGlyphsForCoverageIndex(ci, gi); if (ga != null) { ss.putGlyphs(ga, CharAssociation.replicate(ss.getAssociation(), ga.length), Boolean.TRUE); ss.consume(1); } return true; } }
Obtain glyph sequence for coverage index.
Params:
  • ci – coverage index
  • gi – original glyph index
Throws:
Returns:sequence of glyphs to substitute for input glyph
/** * Obtain glyph sequence for coverage index. * @param ci coverage index * @param gi original glyph index * @return sequence of glyphs to substitute for input glyph * @throws IllegalArgumentException if coverage index is not valid */
public abstract int[] getGlyphsForCoverageIndex(int ci, int gi) throws IllegalArgumentException; static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new MultipleSubtableFormat1(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class MultipleSubtableFormat1 extends MultipleSubtable { private int[][] gsa; // glyph sequence array, ordered by coverage index MultipleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (gsa != null) { List entries = new ArrayList(1); entries.add(gsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public int[] getGlyphsForCoverageIndex(int ci, int gi) throws IllegalArgumentException { if (gsa == null) { return null; } else if (ci >= gsa.length) { throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + gsa.length); } else { return gsa [ ci ]; } } 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 int[][])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an int[][], but is: " + ((o != null) ? o.getClass() : null)); } else { gsa = (int[][]) o; } } } } private abstract static class AlternateSubtable extends GlyphSubstitutionSubtable { public AlternateSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_ALTERNATE; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof AlternateSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { int[] ga = getAlternatesForCoverageIndex(ci, gi); int ai = ss.getAlternatesIndex(ci); int go; if ((ai < 0) || (ai >= ga.length)) { go = gi; } else { go = ga [ ai ]; } if ((go < 0) || (go > 65535)) { go = 65535; } ss.putGlyph(go, ss.getAssociation(), Boolean.TRUE); ss.consume(1); return true; } }
Obtain glyph alternates for coverage index.
Params:
  • ci – coverage index
  • gi – original glyph index
Throws:
Returns:sequence of glyphs to substitute for input glyph
/** * Obtain glyph alternates for coverage index. * @param ci coverage index * @param gi original glyph index * @return sequence of glyphs to substitute for input glyph * @throws IllegalArgumentException if coverage index is not valid */
public abstract int[] getAlternatesForCoverageIndex(int ci, int gi) throws IllegalArgumentException; static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new AlternateSubtableFormat1(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class AlternateSubtableFormat1 extends AlternateSubtable { private int[][] gaa; // glyph alternates array, ordered by coverage index AlternateSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { List entries = new ArrayList(gaa.length); Collections.addAll(entries, gaa); return entries; }
{@inheritDoc}
/** {@inheritDoc} */
public int[] getAlternatesForCoverageIndex(int ci, int gi) throws IllegalArgumentException { if (gaa == null) { return null; } else if (ci >= gaa.length) { throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + gaa.length); } else { return gaa [ ci ]; } } private void populate(List entries) { int i = 0; int n = entries.size(); int[][] gaa = new int [ n ][]; for (Object o : entries) { if (o instanceof int[]) { gaa[i++] = (int[]) o; } else { throw new AdvancedTypographicTableFormatException("illegal entries entry, must be int[]: " + o); } } assert i == n; assert this.gaa == null; this.gaa = gaa; } } private abstract static class LigatureSubtable extends GlyphSubstitutionSubtable { public LigatureSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_LIGATURE; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof LigatureSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { LigatureSet ls = getLigatureSetForCoverageIndex(ci, gi); if (ls != null) { boolean reverse = false; GlyphTester ignores = ss.getIgnoreDefault(); int[] counts = ss.getGlyphsAvailable(0, reverse, ignores); int nga = counts[0]; int ngi; if (nga > 1) { int[] iga = ss.getGlyphs(0, nga, reverse, ignores, null, counts); Ligature l = findLigature(ls, iga); if (l != null) { int go = l.getLigature(); if ((go < 0) || (go > 65535)) { go = 65535; } int nmg = 1 + l.getNumComponents(); // fetch matched number of component glyphs to determine matched and ignored count ss.getGlyphs(0, nmg, reverse, ignores, null, counts); nga = counts[0]; ngi = counts[1]; // fetch associations of matched component glyphs CharAssociation[] laa = ss.getAssociations(0, nga); // output ligature glyph and its association ss.putGlyph(go, CharAssociation.join(laa), Boolean.TRUE); // fetch and output ignored glyphs (if necessary) if (ngi > 0) { ss.putGlyphs(ss.getIgnoredGlyphs(0, ngi), ss.getIgnoredAssociations(0, ngi), null); } ss.consume(nga + ngi); } } } return true; } } private Ligature findLigature(LigatureSet ls, int[] glyphs) { Ligature[] la = ls.getLigatures(); int k = -1; int maxComponents = -1; for (int i = 0, n = la.length; i < n; i++) { Ligature l = la [ i ]; if (l.matchesComponents(glyphs)) { int nc = l.getNumComponents(); if (nc > maxComponents) { maxComponents = nc; k = i; } } } if (k >= 0) { return la [ k ]; } else { return null; } }
Obtain ligature set for coverage index.
Params:
  • ci – coverage index
  • gi – original glyph index
Throws:
Returns:ligature set (or null if none defined)
/** * Obtain ligature set for coverage index. * @param ci coverage index * @param gi original glyph index * @return ligature set (or null if none defined) * @throws IllegalArgumentException if coverage index is not valid */
public abstract LigatureSet getLigatureSetForCoverageIndex(int ci, int gi) throws IllegalArgumentException; static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new LigatureSubtableFormat1(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class LigatureSubtableFormat1 extends LigatureSubtable { private LigatureSet[] ligatureSets; public LigatureSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { List entries = new ArrayList(ligatureSets.length); Collections.addAll(entries, ligatureSets); return entries; }
{@inheritDoc}
/** {@inheritDoc} */
public LigatureSet getLigatureSetForCoverageIndex(int ci, int gi) throws IllegalArgumentException { if (ligatureSets == null) { return null; } else if (ci >= ligatureSets.length) { throw new IllegalArgumentException("coverage index " + ci + " out of range, maximum coverage index is " + ligatureSets.length); } else { return ligatureSets [ ci ]; } } private void populate(List entries) { int i = 0; int n = entries.size(); LigatureSet[] ligatureSets = new LigatureSet [ n ]; for (Object o : entries) { if (o instanceof LigatureSet) { ligatureSets[i++] = (LigatureSet) o; } else { throw new AdvancedTypographicTableFormatException("illegal ligatures entry, must be LigatureSet: " + o); } } assert i == n; assert this.ligatureSets == null; this.ligatureSets = ligatureSets; } } private abstract static class ContextualSubtable extends GlyphSubstitutionSubtable { public ContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_CONTEXTUAL; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof ContextualSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { int[] rv = new int[1]; RuleLookup[] la = getLookups(ci, gi, ss, rv); if (la != null) { ss.apply(la, rv[0]); } return true; } }
Obtain rule lookups set associated current input glyph context.
Params:
  • ci – coverage index of glyph at current position
  • gi – glyph index of glyph at current position
  • ss – glyph substitution state
  • 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
Returns:array of rule lookups or null if none applies
/** * 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 ss glyph substitution 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, GlyphSubstitutionState ss, int[] rv); static GlyphSubstitutionSubtable 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}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(1); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) { ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r; int[] iga = cr.getGlyphs(gi); if (matches(ss, iga, 0, rv)) { return r.getLookups(); } } } } } return null; } static boolean matches(GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv) { if ((glyphs == null) || (glyphs.length == 0)) { return true; // match null or empty glyph sequence } else { boolean reverse = offset < 0; GlyphTester ignores = ss.getIgnoreDefault(); int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores); int nga = counts[0]; int ngm = glyphs.length; if (nga < ngm) { return false; // insufficient glyphs available to match } else { int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts); for (int k = 0; k < ngm; k++) { if (ga [ k ] != glyphs [ k ]) { return false; // match fails at ga [ k ] } } if (rv != null) { rv[0] = counts[0] + counts[1]; } return true; // all glyphs match } } } 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 RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; } } } } private static class ContextualSubtableFormat2 extends ContextualSubtable { private GlyphClassTable cdt; // class def table private int ngc; // class set count private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1] ContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(3); entries.add(cdt); entries.add(ngc); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedClassSequenceRule)) { ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r; int[] ca = cr.getClasses(cdt.getClassIndex(gi, ss.getClassMatchSet(gi))); if (matches(ss, cdt, ca, 0, rv)) { return r.getLookups(); } } } } } return null; } static boolean matches(GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv) { if ((cdt == null) || (classes == null) || (classes.length == 0)) { return true; // match null class definitions, null or empty class sequence } else { boolean reverse = offset < 0; GlyphTester ignores = ss.getIgnoreDefault(); int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores); int nga = counts[0]; int ngm = classes.length; if (nga < ngm) { return false; // insufficient glyphs available to match } else { int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts); for (int k = 0; k < ngm; k++) { int gi = ga [ k ]; int ms = ss.getClassMatchSet(gi); int gc = cdt.getClassIndex(gi, ms); if ((gc < 0) || (gc >= cdt.getClassSize(ms))) { return false; // none or invalid class fails mat ch } else if (gc != classes [ k ]) { return false; // match fails at ga [ k ] } } if (rv != null) { rv[0] = counts[0] + counts[1]; } return true; // all glyphs match } } } private void populate(List entries) { if (entries == null) { throw new AdvancedTypographicTableFormatException("illegal entries, must be non-null"); } else if (entries.size() != 3) { throw new AdvancedTypographicTableFormatException("illegal entries, " + entries.size() + " entries present, but requires 3 entries"); } else { Object o; if (((o = entries.get(0)) == null) || !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { cdt = (GlyphClassTable) 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 { ngc = (Integer) (o); } if (((o = entries.get(2)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; if (rsa.length != ngc) { throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes"); } } } } } private static class ContextualSubtableFormat3 extends ContextualSubtable { private RuleSet[] rsa; // rule set array, containing a single rule set ContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(1); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) { ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r; GlyphCoverageTable[] gca = cr.getCoverages(); if (matches(ss, gca, 0, rv)) { return r.getLookups(); } } } } } return null; } static boolean matches(GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv) { if ((gca == null) || (gca.length == 0)) { return true; // match null or empty coverage array } else { boolean reverse = offset < 0; GlyphTester ignores = ss.getIgnoreDefault(); int[] counts = ss.getGlyphsAvailable(offset, reverse, ignores); int nga = counts[0]; int ngm = gca.length; if (nga < ngm) { return false; // insufficient glyphs available to match } else { int[] ga = ss.getGlyphs(offset, ngm, reverse, ignores, null, counts); for (int k = 0; k < ngm; k++) { GlyphCoverageTable ct = gca [ k ]; if (ct != null) { if (ct.getCoverageIndex(ga [ k ]) < 0) { return false; // match fails at ga [ k ] } } } if (rv != null) { rv[0] = counts[0] + counts[1]; } return true; // all glyphs match } } } 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 RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; } } } } private abstract static class ChainedContextualSubtable extends GlyphSubstitutionSubtable { public ChainedContextualSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof ChainedContextualSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean substitute(GlyphSubstitutionState ss) { int gi = ss.getGlyph(); int ci; if ((ci = getCoverageIndex(gi)) < 0) { return false; } else { int[] rv = new int[1]; RuleLookup[] la = getLookups(ci, gi, ss, rv); if (la != null) { ss.apply(la, rv[0]); return true; } else { return false; } } }
Obtain rule lookups set associated current input glyph context.
Params:
  • ci – coverage index of glyph at current position
  • gi – glyph index of glyph at current position
  • ss – glyph substitution state
  • rv – array of ints used to receive multiple return values, must be of length 1 or greater
Returns:array of rule lookups or null if none applies
/** * 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 ss glyph substitution state * @param rv array of ints used to receive multiple return values, must be of length 1 or greater * @return array of rule lookups or null if none applies */
public abstract RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv); static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new ChainedContextualSubtableFormat1(id, sequence, flags, format, coverage, entries); } else if (format == 2) { return new ChainedContextualSubtableFormat2(id, sequence, flags, format, coverage, entries); } else if (format == 3) { return new ChainedContextualSubtableFormat3(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class ChainedContextualSubtableFormat1 extends ChainedContextualSubtable { private RuleSet[] rsa; // rule set array, ordered by glyph coverage index ChainedContextualSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(1); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedGlyphSequenceRule)) { ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r; int[] iga = cr.getGlyphs(gi); if (matches(ss, iga, 0, rv)) { int[] bga = cr.getBacktrackGlyphs(); if (matches(ss, bga, -1, null)) { int[] lga = cr.getLookaheadGlyphs(); if (matches(ss, lga, rv[0], null)) { return r.getLookups(); } } } } } } } return null; } private boolean matches(GlyphSubstitutionState ss, int[] glyphs, int offset, int[] rv) { return ContextualSubtableFormat1.matches(ss, glyphs, offset, rv); } 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 RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; } } } } private static class ChainedContextualSubtableFormat2 extends ChainedContextualSubtable { private GlyphClassTable icdt; // input class def table private GlyphClassTable bcdt; // backtrack class def table private GlyphClassTable lcdt; // lookahead class def table private int ngc; // class set count private RuleSet[] rsa; // rule set array, ordered by class number [0...ngc - 1] ChainedContextualSubtableFormat2(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(5); entries.add(icdt); entries.add(bcdt); entries.add(lcdt); entries.add(ngc); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedClassSequenceRule)) { ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r; int[] ica = cr.getClasses(icdt.getClassIndex(gi, ss.getClassMatchSet(gi))); if (matches(ss, icdt, ica, 0, rv)) { int[] bca = cr.getBacktrackClasses(); if (matches(ss, bcdt, bca, -1, null)) { int[] lca = cr.getLookaheadClasses(); if (matches(ss, lcdt, lca, rv[0], null)) { return r.getLookups(); } } } } } } } return null; } private boolean matches(GlyphSubstitutionState ss, GlyphClassTable cdt, int[] classes, int offset, int[] rv) { return ContextualSubtableFormat2.matches(ss, cdt, classes, offset, rv); }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); } 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 GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an GlyphClassTable, but is: " + ((o != null) ? o.getClass() : null)); } else { icdt = (GlyphClassTable) o; } if (((o = entries.get(1)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, second entry must be an GlyphClassTable, but is: " + o.getClass()); } else { bcdt = (GlyphClassTable) o; } if (((o = entries.get(2)) != null) && !(o instanceof GlyphClassTable)) { throw new AdvancedTypographicTableFormatException("illegal entries, third entry must be an GlyphClassTable, but is: " + o.getClass()); } else { lcdt = (GlyphClassTable) o; } if (((o = entries.get(3)) == null) || !(o instanceof Integer)) { throw new AdvancedTypographicTableFormatException("illegal entries, fourth entry must be an Integer, but is: " + ((o != null) ? o.getClass() : null)); } else { ngc = (Integer) (o); } if (((o = entries.get(4)) == null) || !(o instanceof RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, fifth entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; if (rsa.length != ngc) { throw new AdvancedTypographicTableFormatException("illegal entries, RuleSet[] length is " + rsa.length + ", but expected " + ngc + " glyph classes"); } } } } } private static class ChainedContextualSubtableFormat3 extends ChainedContextualSubtable { private RuleSet[] rsa; // rule set array, containing a single rule set ChainedContextualSubtableFormat3(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { if (rsa != null) { List entries = new ArrayList(1); entries.add(rsa); return entries; } else { return null; } }
{@inheritDoc}
/** {@inheritDoc} */
public void resolveLookupReferences(Map<String, LookupTable> lookupTables) { GlyphTable.resolveLookupReferences(rsa, lookupTables); }
{@inheritDoc}
/** {@inheritDoc} */
public RuleLookup[] getLookups(int ci, int gi, GlyphSubstitutionState ss, int[] rv) { assert ss != null; assert (rv != null) && (rv.length > 0); assert rsa != null; if (rsa.length > 0) { RuleSet rs = rsa [ 0 ]; if (rs != null) { Rule[] ra = rs.getRules(); for (Rule r : ra) { if ((r != null) && (r instanceof ChainedCoverageSequenceRule)) { ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r; GlyphCoverageTable[] igca = cr.getCoverages(); if (matches(ss, igca, 0, rv)) { GlyphCoverageTable[] bgca = cr.getBacktrackCoverages(); if (matches(ss, bgca, -1, null)) { GlyphCoverageTable[] lgca = cr.getLookaheadCoverages(); if (matches(ss, lgca, rv[0], null)) { return r.getLookups(); } } } } } } } return null; } private boolean matches(GlyphSubstitutionState ss, GlyphCoverageTable[] gca, int offset, int[] rv) { return ContextualSubtableFormat3.matches(ss, gca, offset, rv); } 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 RuleSet[])) { throw new AdvancedTypographicTableFormatException("illegal entries, first entry must be an RuleSet[], but is: " + ((o != null) ? o.getClass() : null)); } else { rsa = (RuleSet[]) o; } } } } private abstract static class ReverseChainedSingleSubtable extends GlyphSubstitutionSubtable { public ReverseChainedSingleSubtable(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage); }
{@inheritDoc}
/** {@inheritDoc} */
public int getType() { return GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) { return subtable instanceof ReverseChainedSingleSubtable; }
{@inheritDoc}
/** {@inheritDoc} */
public boolean usesReverseScan() { return true; } static GlyphSubstitutionSubtable create(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { if (format == 1) { return new ReverseChainedSingleSubtableFormat1(id, sequence, flags, format, coverage, entries); } else { throw new UnsupportedOperationException(); } } } private static class ReverseChainedSingleSubtableFormat1 extends ReverseChainedSingleSubtable { ReverseChainedSingleSubtableFormat1(String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries) { super(id, sequence, flags, format, coverage, entries); populate(entries); }
{@inheritDoc}
/** {@inheritDoc} */
public List getEntries() { return null; } private void populate(List entries) { } }
The 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.
/** * The <code>Ligature</code> 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.
Params:
  • ligature – glyph id
  • components – sequence of N+1... component glyph (or character) identifiers
/** * 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 gc : components) { if ((gc < 0) || (gc > 65535)) { throw new AdvancedTypographicTableFormatException("invalid component glyph index: " + gc); } } this.ligature = ligature; this.components = components; } }
Returns:ligature glyph id
/** @return ligature glyph id */
public int getLigature() { return ligature; }
Returns:array of N+1... components
/** @return array of N+1... components */
public int[] getComponents() { return components; }
Returns:components count
/** @return components count */
public int getNumComponents() { return components.length; }
Determine if input sequence at offset matches ligature's components.
Params:
  • glyphs – array of glyph components to match (including first, implied glyph)
Returns:true if matches
/** * 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}
/** {@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.
/** * The <code>LigatureSet</code> 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.
Params:
  • ligatures – collection of ligatures
/** * 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.
Params:
  • ligatures – array of ligatures
/** * 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 (Ligature l : ligatures) { int nc = l.getNumComponents() + 1; if (nc > ncMax) { ncMax = nc; } } maxComponents = ncMax; } }
Returns:array of ligatures in this ligature set
/** @return array of ligatures in this ligature set */
public Ligature[] getLigatures() { return ligatures; }
Returns:count of ligatures in this ligature set
/** @return count of ligatures in this ligature set */
public int getNumLigatures() { return ligatures.length; }
Returns:maximum number of components in one ligature (including first component)
/** @return maximum number of components in one ligature (including first component) */
public int getMaxComponents() { return maxComponents; }
{@inheritDoc}
/** {@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(); } } }