org.apache.fop.complexscripts.fonts.GlyphSubstitutionTable Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of org.apache.fop Show documentation
Show all versions of org.apache.fop Show documentation
The core maven build properties
The newest version!
/*
* 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;
import org.apache.fop.fonts.MultiByteFont;
// CSOFF: LineLengthCheck
/**
* The GlyphSubstitutionTable
class is a glyph table that implements
* GlyphSubstitution
functionality.
*
* This work was originally authored by Glenn Adams ([email protected]).
*/
public class GlyphSubstitutionTable extends GlyphTable {
/** logging instance */
private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class);
/** 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;
/**
* Instantiate a GlyphSubstitutionTable
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 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.
* @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> 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;
}
public CharSequence preProcess(CharSequence charSequence, String script, MultiByteFont font, List associations) {
ScriptProcessor scriptProcessor = ScriptProcessor.getInstance(script, processors);
return scriptProcessor.preProcess(charSequence, font, associations);
}
/**
* 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.
* @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.
* @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.
* @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} */
public int getType() {
return GSUB_LOOKUP_TYPE_SINGLE;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof SingleSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
List entries = new ArrayList(1);
entries.add(delta);
return entries;
}
/** {@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} */
public List getEntries() {
List entries = new ArrayList(glyphs.length);
for (int glyph : glyphs) {
entries.add(glyph);
}
return entries;
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_MULTIPLE;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof MultipleSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
if (gsa != null) {
List entries = new ArrayList(1);
entries.add(gsa);
return entries;
} else {
return null;
}
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_ALTERNATE;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof AlternateSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
List entries = new ArrayList(gaa.length);
Collections.addAll(entries, gaa);
return entries;
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_LIGATURE;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof LigatureSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
List entries = new ArrayList(ligatureSets.length);
Collections.addAll(entries, ligatureSets);
return entries;
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_CONTEXTUAL;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof ContextualSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
if (rsa != null) {
List entries = new ArrayList(1);
entries.add(rsa);
return entries;
} else {
return null;
}
}
/** {@inheritDoc} */
public void resolveLookupReferences(Map lookupTables) {
GlyphTable.resolveLookupReferences(rsa, lookupTables);
}
/** {@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} */
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} */
public void resolveLookupReferences(Map lookupTables) {
GlyphTable.resolveLookupReferences(rsa, lookupTables);
}
/** {@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} */
public List getEntries() {
if (rsa != null) {
List entries = new ArrayList(1);
entries.add(rsa);
return entries;
} else {
return null;
}
}
/** {@inheritDoc} */
public void resolveLookupReferences(Map lookupTables) {
GlyphTable.resolveLookupReferences(rsa, lookupTables);
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof ChainedContextualSubtable;
}
/** {@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.
* @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} */
public List getEntries() {
if (rsa != null) {
List entries = new ArrayList(1);
entries.add(rsa);
return entries;
} else {
return null;
}
}
/** {@inheritDoc} */
public void resolveLookupReferences(Map lookupTables) {
GlyphTable.resolveLookupReferences(rsa, lookupTables);
}
/** {@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} */
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} */
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} */
public void resolveLookupReferences(Map 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} */
public List getEntries() {
if (rsa != null) {
List entries = new ArrayList(1);
entries.add(rsa);
return entries;
} else {
return null;
}
}
/** {@inheritDoc} */
public void resolveLookupReferences(Map lookupTables) {
GlyphTable.resolveLookupReferences(rsa, lookupTables);
}
/** {@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} */
public int getType() {
return GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
}
/** {@inheritDoc} */
public boolean isCompatible(GlyphSubtable subtable) {
return subtable instanceof ReverseChainedSingleSubtable;
}
/** {@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} */
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.
*/
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 gc : components) {
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 (Ligature l : ligatures) {
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();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy