<target name="junit-transcoder" depends="junit-compile" description="Runs FOP's JUnit transcoder tests" if="junit.present">
<echo message="Running basic functionality tests for fop-transcoder.jar"/>
<junit dir="${basedir}" haltonfailure="${junit.haltonfailure}" fork="${junit.fork}" printsummary="${junit.printsummary}">
+ <jvmarg value="-Xmx1024m"/>
<sysproperty key="basedir" value="${basedir}"/>
<sysproperty key="jawa.awt.headless" value="true"/>
<formatter type="brief" usefile="false" if="junit.formatter.brief.use"/>
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN" "http://www.puppycrawl.com/dtds/suppressions_1_1.dtd">
<suppressions>
<suppress files="org/apache/fop/fo/FOPropertyMapping.java" checks="FileLengthCheck"/>
+ <suppress files="org/apache/fop/fonts/GlyphPositioningTable.java" checks="FileLengthCheck"/>
<suppress files="org/apache/fop/fonts/truetype/TTFFile.java" checks="FileLengthCheck"/>
<suppress files="org/apache/fop/Version.java" lines="40-50" checks="LineLengthCheck"/>
</suppressions>
<!-- use of null is preferred over zero length array -->
<Match>
<Class name="org.apache.fop.area.inline.WordArea"/>
- <Method name="getBidiLevels"/>
+ <Or>
+ <Method name="getBidiLevels"/>
+ <Method name="glyphPositionAdjustmentsAt"/>
+ </Or>
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
</Match>
<Match>
<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
</Match>
<!-- string not exposed to end user -->
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphDefinitionTable"/>
+ <Method name="getLookupTypeFromName"/>
+ <Bug pattern="DM_CONVERT_CASE"/>
+ </Match>
<Match>
<Class name="org.apache.fop.fonts.GlyphPositioningTable"/>
<Method name="getLookupTypeFromName"/>
<Bug pattern="DM_CONVERT_CASE"/>
</Match>
<!-- performance optimizations -->
+ <Match>
+ <Class name="org.apache.fop.fonts.ArabicScriptProcessor"/>
+ <Or>
+ <Method name="getPositioningFeatures"/>
+ <Method name="getSubstitutionFeatures"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.DefaultScriptProcessor"/>
+ <Or>
+ <Method name="getPositioningFeatures"/>
+ <Method name="getSubstitutionFeatures"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningState"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ChainedContextualSubtableFormat1"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ChainedContextualSubtableFormat2"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ChainedContextualSubtableFormat3"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ContextualSubtableFormat1"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ContextualSubtableFormat2"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$ContextualSubtableFormat3"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$CursiveSubtableFormat1"/>
+ <Method name="getExitEntryAnchors"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$DeviceTable"/>
+ <Method name="getDeltas"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphPositioningTable$DeviceTable"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
<Match>
<Class name="org.apache.fop.fonts.GlyphSequence"/>
<Method name="getAssociations"/>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSequence$CharAssociation"/>
+ <Method name="getSubIntervals"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionState"/>
+ <Method name="setAlternates"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$AlternateSubtableFormat1"/>
+ <Method name="getAlternatesForCoverageIndex"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ChainedContextualSubtableFormat1"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ChainedContextualSubtableFormat2"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ChainedContextualSubtableFormat3"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ContextualSubtableFormat1"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ContextualSubtableFormat2"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$ContextualSubtableFormat3"/>
+ <Method name="getLookups"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphSubstitutionTable$MultipleSubtableFormat1"/>
+ <Method name="getGlyphsForCoverageIndex"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
<Match>
<Class name="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature"/>
<Or>
</Match>
<Match>
<Class name="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature"/>
- <Or>
- <Method name="<init>" params="int, int[]" returns="void"/>
- </Or>
+ <Method name="<init>" params="int, int[]" returns="void"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
<Match>
<Method name="<init>" params="org.apache.fop.fonts.GlyphSubstitutionTable$Ligature[]" returns="void"/>
<Bug pattern="EI_EXPOSE_REP2"/>
</Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedClassSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedClassSequenceRule"/>
+ <Or>
+ <Method name="getBacktrackClasses"/>
+ <Method name="getLookaheadClasses"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedCoverageSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedCoverageSequenceRule"/>
+ <Or>
+ <Method name="getBacktrackCoverages"/>
+ <Method name="getLookaheadCoverages"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedGlyphSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ChainedGlyphSequenceRule"/>
+ <Or>
+ <Method name="getBacktrackGlyphs"/>
+ <Method name="getLookaheadGlyphs"/>
+ </Or>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ClassSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$ClassSequenceRule"/>
+ <Method name="getClasses"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$CoverageSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$CoverageSequenceRule"/>
+ <Method name="getCoverages"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$GlyphSequenceRule"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$GlyphSequenceRule"/>
+ <Method name="getGlyphs"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$LookupTable"/>
+ <Method name="getSubtables"/>
+ <Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$Rule"/>
+ <Method name="getLookups"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$RuleSet"/>
+ <Method name="<init>"/>
+ <Bug pattern="EI_EXPOSE_REP2"/>
+ </Match>
+ <Match>
+ <Class name="org.apache.fop.fonts.GlyphTable$RuleSet"/>
+ <Method name="getRules"/>
+ <Bug pattern="EI_EXPOSE_REP"/>
+ </Match>
<Match>
<Class name="org.apache.fop.area.inline.WordArea"/>
- <Method name="getBidiLevels"/>
+ <Or>
+ <Method name="getBidiLevels"/>
+ <Method name="getGlyphPositionAdjustments"/>
+ </Or>
<Bug pattern="EI_EXPOSE_REP"/>
</Match>
<Match>
= ConversionUtils.toIntArray(
lastAttributes.getValue("letter-adjust"), "\\s");
int level = XMLUtil.getAttributeAsInt(lastAttributes, "level", -1);
+ int[][] gposAdjustments
+ = XMLUtil.getAttributeAsPositionAdjustments(lastAttributes, "position-adjust");
content.flip();
WordArea word = new WordArea
- ( offset, level, content.toString().trim(), letterAdjust, null );
+ ( offset, level, content.toString().trim(), letterAdjust,
+ null, gposAdjustments );
AbstractTextArea text = getCurrentText();
word.setParentArea(text);
text.addChildArea(word);
* @param offset the offset for the next area
*/
public void addWord(String word, int offset) {
- addWord(word, 0, null, null, offset);
+ addWord(word, 0, null, null, null, offset);
}
/**
* @param letterAdjust the letter adjustment array (may be null)
* @param levels array of resolved bidirection levels of word characters,
* or null if default level
+ * @param gposAdjustments array of general position adjustments or null if none apply
* @param blockProgressionOffset the offset for the next area
*/
public void addWord
- ( String word, int ipd, int[] letterAdjust, int[] levels, int blockProgressionOffset ) {
+ ( String word, int ipd, int[] letterAdjust, int[] levels,
+ int[][] gposAdjustments, int blockProgressionOffset ) {
int minWordLevel = findMinLevel ( levels );
WordArea wordArea = new WordArea
- ( blockProgressionOffset, minWordLevel, word, letterAdjust, levels );
+ ( blockProgressionOffset, minWordLevel, word, letterAdjust, levels, gposAdjustments );
wordArea.setIPD ( ipd );
addChildArea(wordArea);
wordArea.setParentArea(this);
*/
protected int[] levels;
+ /**
+ * An array of glyph positioning adjustments to apply to each glyph 'char' in word (optional)
+ */
+ protected int[][] gposAdjustments;
+
/**
* A flag indicating whether the content of word is reversed in relation to
* its original logical order.
* @param letterAdjust the letter adjust array (may be null)
* @param levels array of per-character (glyph) bidirectional levels,
* in case word area is heterogenously leveled
+ * @param gposAdjustments array of general position adjustments or null if none apply
*/
public WordArea
- ( int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels ) {
+ ( int blockProgressionOffset, int level, String word, int[] letterAdjust, int[] levels,
+ int[][] gposAdjustments ) {
super ( blockProgressionOffset, level );
- assert word != null;
+ int length = ( word != null ) ? word.length() : 0;
this.word = word;
- this.letterAdjust = letterAdjust;
- this.levels = maybePopulateLevels ( levels, level, word.length() );
+ this.letterAdjust = maybeAdjustLength ( letterAdjust, length );
+ this.levels = maybePopulateLevels ( levels, level, length );
+ this.gposAdjustments = maybeAdjustLength ( gposAdjustments, length );
this.reversed = false;
}
}
/**
- * <p>Reverse characters and corresponding per-character levels if word's length is greater
- * than one.</p>
+ * Obtain per-character (glyph) position adjustments.
+ * @return a (possibly empty) array of adjustments, each having four elements, or null
+ * if no adjustments apply
+ */
+ public int[][] getGlyphPositionAdjustments() {
+ return gposAdjustments;
+ }
+
+ /**
+ * <p>Obtain per-character (glyph) position adjustments at a specified index position.</p>
+ * <p>If word has been reversed, then the position is relative to the reversed word.</p>
+ * @param position the index of the (possibly reversed) character from which to obtain the
+ * level
+ * @return an array of adjustments or null if none applies
+ */
+ public int[] glyphPositionAdjustmentsAt ( int position ) {
+ if ( position > word.length() ) {
+ throw new IndexOutOfBoundsException();
+ } else if ( gposAdjustments != null ) {
+ return gposAdjustments [ position ];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * <p>Reverse characters and corresponding per-character levels and glyph position
+ * adjustments.</p>
* @param mirror if true, then perform mirroring if mirrorred characters
*/
public void reverse ( boolean mirror ) {
if ( levels != null ) {
reverse ( levels );
}
+ if ( gposAdjustments != null ) {
+ reverse ( gposAdjustments );
+ }
reversed = !reversed;
if ( mirror ) {
word = CharUtilities.mirror ( word );
return reversed;
}
+ /*
+ * If int[] array is not of specified length, then create
+ * a new copy of the first length entries.
+ */
+ private static int[] maybeAdjustLength ( int[] ia, int length ) {
+ if ( ia != null ) {
+ if ( ia.length == length ) {
+ return ia;
+ } else {
+ int[] iaNew = new int [ length ];
+ for ( int i = 0, n = ia.length; i < n; i++ ) {
+ if ( i < length ) {
+ iaNew [ i ] = ia [ i ];
+ } else {
+ break;
+ }
+ }
+ return iaNew;
+ }
+ } else {
+ return ia;
+ }
+ }
+
+ /*
+ * If int[][] matrix is not of specified length, then create
+ * a new shallow copy of the first length entries.
+ */
+ private static int[][] maybeAdjustLength ( int[][] im, int length ) {
+ if ( im != null ) {
+ if ( im.length == length ) {
+ return im;
+ } else {
+ int[][] imNew = new int [ length ][];
+ for ( int i = 0, n = im.length; i < n; i++ ) {
+ if ( i < length ) {
+ imNew [ i ] = im [ i ];
+ } else {
+ break;
+ }
+ }
+ return imNew;
+ }
+ } else {
+ return im;
+ }
+ }
+
private static int[] maybePopulateLevels ( int[] levels, int level, int count ) {
if ( ( levels == null ) && ( level >= 0 ) ) {
levels = new int[count];
Arrays.fill ( levels, level );
}
- return levels;
+ return maybeAdjustLength ( levels, count );
}
private static void reverse ( int[] a ) {
}
}
+ private static void reverse ( int[][] aa ) {
+ for ( int i = 0, n = aa.length, m = n / 2; i < m; i++ ) {
+ int k = n - i - 1;
+ int[] t = aa [ k ];
+ aa [ k ] = aa [ i ];
+ aa [ i ] = t;
+ }
+ }
+
}
}
}
+ /**
+ * Obtain length of mapping of characters over specific interval.
+ * @param start index in character buffer
+ * @param end index in character buffer
+ * @return the length of the mapping (if present) or zero
+ */
+ public int getMappingLength ( int start, int end ) {
+ if ( mappings != null ) {
+ return ( (String) mappings.get ( new MapRange ( start, end ) ) ) .length();
+ } else {
+ return 0;
+ }
+ }
+
/**
* Obtain bidirectional levels of mapping of characters over specific interval.
* @param start index in character buffer
* in case no bidi levels have been assigned
*/
public int[] getMappingBidiLevels ( int start, int end ) {
- if ( mappings != null ) {
- return getBidiLevels ( start, end ); // [TBD] FIX ME
+ if ( hasMapping ( start, end ) ) {
+ int nc = end - start;
+ int nm = getMappingLength ( start, end );
+ int[] la = getBidiLevels ( start, end );
+ if ( nm == nc ) { // mapping is same length as mapped range
+ return la;
+ } else if ( nm > nc ) { // mapping is longer than mapped range
+ int[] ma = new int [ nm ];
+ System.arraycopy ( la, 0, ma, 0, la.length );
+ for ( int i = la.length,
+ n = ma.length, l = ( i > 0 ) ? la [ i - 1 ] : 0; i < n; i++ ) {
+ ma [ i ] = l;
+ }
+ return ma;
+ } else { // mapping is shorter than mapped range
+ int[] ma = new int [ nm ];
+ System.arraycopy ( la, 0, ma, 0, ma.length );
+ return ma;
+ }
} else {
return getBidiLevels ( start, end );
}
package org.apache.fop.fonts;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
// CSOFF: LineLengthCheck
/**
- * <p>The <code>ArabicScriptProcessor</code> class implements script processor for
- * performing glypph substitution and positioning operations on content associated with the Arabic script.</p>
+ * <p>The <code>ArabicScriptProcessor</code> class implements a script processor for
+ * performing glyph substitution and positioning operations on content associated with the Arabic script.</p>
* @author Glenn Adams
*/
-public class ArabicScriptProcessor extends ScriptProcessor {
-
- /**
- * logging instance
- */
- protected static final Log log = LogFactory.getLog(ArabicScriptProcessor.class); // CSOK: ConstantNameCheck
-
- ArabicScriptProcessor ( String script ) {
- super ( script );
- }
-
- /** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
- // finals
- gs = subFina ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "fina" ) ) );
-
- // medials
- gs = subMedi ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "medi" ) ) );
-
- // initials
- gs = subInit ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "init" ) ) );
-
- // isolates
- gs = subIsol ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "isol" ) ) );
-
- // required ligatures
- gs = subLiga ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "rlig" ) ) );
-
- // standard ligatures
- gs = subLiga ( gs, script, language, (GlyphSubtable[]) lookups.get ( new GlyphTable.LookupSpec ( script, language, "liga" ) ) );
-
- return gs;
- }
-
- /** {@inheritDoc} */
- public int[] position ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
- return null;
- }
+public class ArabicScriptProcessor extends DefaultScriptProcessor {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(ArabicScriptProcessor.class); // CSOK: ConstantNameCheck
+
+ /** features to use for substitutions */
+ private static final String[] gsubFeatures = // CSOK: ConstantNameCheck
+ {
+ "calt", // contextual alternates
+ "ccmp", // glyph composition/decomposition
+ "fina", // final (terminal) forms
+ "init", // initial forms
+ "isol", // isolated formas
+ "liga", // standard ligatures
+ "medi", // medial forms
+ "rlig" // required ligatures
+ };
- private static GlyphContextTester finalContextTester
- = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inFinalContext ( gs, ca ); } };
+ /** features to use for positioning */
+ private static final String[] gposFeatures = // CSOK: ConstantNameCheck
+ {
+ "curs", // cursive positioning
+ "kern", // kerning
+ "mark", // mark to base or ligature positioning
+ "mkmk" // mark to mark positioning
+ };
- private GlyphSequence subFina ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- return substituteSingle ( gs, script, language, "fina", sta, finalContextTester, false );
+ private static class SubstitutionScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ static {
+ testerMap.put ( "fina", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inFinalContext ( gs, index ); } } );
+ testerMap.put ( "init", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inInitialContext ( gs, index ); } } );
+ testerMap.put ( "isol", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inIsolateContext ( gs, index ); } } );
+ testerMap.put ( "medi", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inMedialContext ( gs, index ); } } );
+ testerMap.put ( "liga", new GlyphContextTester() { public boolean test ( GlyphSequence gs, int index ) { return inLigatureContext ( gs, index ); } } );
+ }
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
}
- private static GlyphContextTester medialContextTester
- = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inMedialContext ( gs, ca ); } };
-
- private GlyphSequence subMedi ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- return substituteSingle ( gs, script, language, "medi", sta, medialContextTester, false );
+ private static class PositioningScriptContextTester implements ScriptContextTester {
+ private static Map/*<String,GlyphContextTester>*/ testerMap = new HashMap/*<String,GlyphContextTester>*/();
+ public GlyphContextTester getTester ( String feature ) {
+ return (GlyphContextTester) testerMap.get ( feature );
+ }
}
-
- private static GlyphContextTester initialContextTester
- = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inInitialContext ( gs, ca ); } };
- private GlyphSequence subInit ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- return substituteSingle ( gs, script, language, "init", sta, initialContextTester, false );
- }
-
- private static GlyphContextTester isolateContextTester
- = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inIsolateContext ( gs, ca ); } };
+ private final ScriptContextTester subContextTester;
+ private final ScriptContextTester posContextTester;
- private GlyphSequence subIsol ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- return substituteSingle ( gs, script, language, "isol", sta, isolateContextTester, false );
+ ArabicScriptProcessor ( String script ) {
+ super ( script );
+ this.subContextTester = new SubstitutionScriptContextTester();
+ this.posContextTester = new PositioningScriptContextTester();
}
-
- private static GlyphContextTester ligatureContextTester
- = new GlyphContextTester() { public boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca ) { return inLigatureContext ( gs, ca ); } };
- private GlyphSequence subLiga ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- return substituteMultiple ( gs, script, language, "liga", sta, ligatureContextTester, false );
+ /** {@inheritDoc} */
+ public String[] getSubstitutionFeatures() {
+ return gsubFeatures;
}
- private GlyphSequence substituteSingle ( GlyphSequence gs, String script, String language, String feature, GlyphSubtable[] sta, GlyphContextTester tester, boolean reverse ) {
- if ( ( sta != null ) && ( sta.length > 0 ) ) {
- // enforce subtable type constraints
- for ( int i = 0, n = sta.length; i < n; i++ ) {
- GlyphSubtable st = sta [ i ];
- if ( ! ( st instanceof GlyphSubstitutionSubtable ) ) {
- throw new IncompatibleSubtableException ( "'" + feature + "' feature requires glyph substitution subtable" );
- }
- }
- CharSequence ga = gs.getGlyphs();
- GlyphSequence.CharAssociation[] aa = gs.getAssociations();
- List gsl = new ArrayList();
- List cal = new ArrayList();
- for ( int i = 0, n = ga.length(); i < n; i++ ) {
- int k = reverse ? ( n - i - 1 ) : i;
- GlyphSequence.CharAssociation a = aa [ k ];
- GlyphSequence iss = gs.getGlyphSubsequence ( k, k + 1 );
- GlyphSequence oss;
- if ( tester.test ( iss, a ) ) {
- oss = doSubstitutions ( iss, script, language, sta );
- } else {
- oss = iss;
- }
- gsl.add ( oss );
- cal.add ( a );
- }
- gs = new GlyphSequence ( gs.getCharacters(), gsl, cal, reverse );
- }
- return gs;
+ /** {@inheritDoc} */
+ public ScriptContextTester getSubstitutionContextTester() {
+ return subContextTester;
}
- private GlyphSequence substituteMultiple ( GlyphSequence gs, String script, String language, String feature, GlyphSubtable[] sta, GlyphContextTester tester, boolean reverse ) {
- if ( ( sta != null ) && ( sta.length > 0 ) ) {
- gs = doSubstitutions ( gs, script, language, sta );
- }
- return gs;
+ /** {@inheritDoc} */
+ public String[] getPositioningFeatures() {
+ return gposFeatures;
}
- private GlyphSequence doSubstitutions ( GlyphSequence gs, String script, String language, GlyphSubtable[] sta ) {
- for ( int i = 0, n = sta.length; i < n; i++ ) {
- GlyphSubtable st = sta [ i ];
- assert st instanceof GlyphSubstitutionSubtable;
- gs = ( (GlyphSubstitutionSubtable) st ) . substitute ( gs, script, language );
- }
- return gs;
+ /** {@inheritDoc} */
+ public ScriptContextTester getPositioningContextTester() {
+ return posContextTester;
}
- private static boolean inFinalContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
- CharSequence cs = gs.getCharacters();
- if ( cs.length() == 0 ) {
+ private static boolean inFinalContext ( GlyphSequence gs, int index ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
return false;
} else {
int s = a.getStart();
int e = a.getEnd();
- if ( ! hasFinalPrecedingContext ( cs, s, e ) ) {
+ if ( ! hasFinalPrecedingContext ( ca, nc, s, e ) ) {
return false;
- } else if ( forcesFinalThisContext ( cs, s, e ) ) {
- if (log.isDebugEnabled()) {
- log.debug ( "+FIN: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
+ } else if ( forcesFinalThisContext ( ca, nc, s, e ) ) {
return true;
- } else if ( ! hasFinalFollowingContext ( cs, s, e ) ) {
+ } else if ( ! hasFinalFollowingContext ( ca, nc, s, e ) ) {
return false;
} else {
- if (log.isDebugEnabled()) {
- log.debug ( "+FIN: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
return true;
}
}
}
- private static boolean inMedialContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
- CharSequence cs = gs.getCharacters();
- if ( cs.length() == 0 ) {
+ private static boolean inMedialContext ( GlyphSequence gs, int index ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
return false;
} else {
int s = a.getStart();
int e = a.getEnd();
- if ( ! hasMedialPrecedingContext ( cs, s, e ) ) {
+ if ( ! hasMedialPrecedingContext ( ca, nc, s, e ) ) {
return false;
- } else if ( ! hasMedialThisContext ( cs, s, e ) ) {
+ } else if ( ! hasMedialThisContext ( ca, nc, s, e ) ) {
return false;
- } else if ( ! hasMedialFollowingContext ( cs, s, e ) ) {
+ } else if ( ! hasMedialFollowingContext ( ca, nc, s, e ) ) {
return false;
} else {
- if (log.isDebugEnabled()) {
- log.debug ( "+MED: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
return true;
}
}
}
- private static boolean inInitialContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
- CharSequence cs = gs.getCharacters();
- if ( cs.length() == 0 ) {
+ private static boolean inInitialContext ( GlyphSequence gs, int index ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
return false;
} else {
int s = a.getStart();
int e = a.getEnd();
- if ( ! hasInitialPrecedingContext ( cs, s, e ) ) {
+ if ( ! hasInitialPrecedingContext ( ca, nc, s, e ) ) {
return false;
- } else if ( ! hasInitialFollowingContext ( cs, s, e ) ) {
+ } else if ( ! hasInitialFollowingContext ( ca, nc, s, e ) ) {
return false;
} else {
- if (log.isDebugEnabled()) {
- log.debug ( "+INI: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
return true;
}
}
}
- private static boolean inIsolateContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
- CharSequence cs = gs.getCharacters();
- int n;
- if ( ( n = cs.length() ) == 0 ) {
+ private static boolean inIsolateContext ( GlyphSequence gs, int index ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
return false;
- } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == n ) ) {
- if (log.isDebugEnabled()) {
- log.debug ( "+ISO: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
+ } else if ( ( a.getStart() == 0 ) && ( a.getEnd() == nc ) ) {
return true;
} else {
return false;
}
}
- private static boolean inLigatureContext ( GlyphSequence gs, GlyphSequence.CharAssociation a ) {
- CharSequence cs = gs.getCharacters();
- if ( cs.length() == 0 ) {
+ private static boolean inLigatureContext ( GlyphSequence gs, int index ) {
+ GlyphSequence.CharAssociation a = gs.getAssociation ( index );
+ int[] ca = gs.getCharacterArray ( false );
+ int nc = gs.getCharacterCount();
+ if ( nc == 0 ) {
return false;
} else {
int s = a.getStart();
int e = a.getEnd();
- if ( ! hasLigaturePrecedingContext ( cs, s, e ) ) {
+ if ( ! hasLigaturePrecedingContext ( ca, nc, s, e ) ) {
return false;
- } else if ( ! hasLigatureFollowingContext ( cs, s, e ) ) {
+ } else if ( ! hasLigatureFollowingContext ( ca, nc, s, e ) ) {
return false;
} else {
- if (log.isDebugEnabled()) {
- log.debug ( "+LIG: [" + a.getStart() + "," + a.getEnd() + "]: " + GlyphUtils.toString ( (CharSequence) gs ) );
- }
return true;
}
}
}
- private static boolean hasFinalPrecedingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasFinalPrecedingContext ( int[] ca, int nc, int s, int e ) {
int chp = 0;
int clp = 0;
for ( int i = s; i > 0; i-- ) {
- chp = cs.charAt ( i - 1 );
- clp = BidiClassUtils.getBidiClass ( chp );
- if ( clp != BidiConstants.NSM ) {
- break;
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClassUtils.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( clp != BidiConstants.AL ) {
}
}
- private static boolean forcesFinalThisContext ( CharSequence cs, int s, int e ) {
+ private static boolean forcesFinalThisContext ( int[] ca, int nc, int s, int e ) {
int chl = 0;
int cll = 0;
for ( int i = 0, n = e - s; i < n; i++ ) {
int k = n - i - 1;
- chl = cs.charAt ( s + k );
- cll = BidiClassUtils.getBidiClass ( chl );
- if ( cll != BidiConstants.NSM ) {
- break;
+ int j = s + k;
+ if ( ( j >= 0 ) && ( j < nc ) ) {
+ chl = ca [ j ];
+ cll = BidiClassUtils.getBidiClass ( chl );
+ if ( cll != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( cll != BidiConstants.AL ) {
}
}
- private static boolean hasFinalFollowingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasFinalFollowingContext ( int[] ca, int nc, int s, int e ) {
int chf = 0;
int clf = 0;
- for ( int i = e, n = cs.length(); i < n; i++ ) {
- chf = cs.charAt ( i );
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
clf = BidiClassUtils.getBidiClass ( chf );
if ( clf != BidiConstants.NSM ) {
break;
}
}
- private static boolean hasInitialPrecedingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasInitialPrecedingContext ( int[] ca, int nc, int s, int e ) {
int chp = 0;
int clp = 0;
for ( int i = s; i > 0; i-- ) {
- chp = cs.charAt ( i - 1 );
- clp = BidiClassUtils.getBidiClass ( chp );
- if ( clp != BidiConstants.NSM ) {
- break;
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClassUtils.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( clp != BidiConstants.AL ) {
}
}
- private static boolean hasInitialFollowingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasInitialFollowingContext ( int[] ca, int nc, int s, int e ) {
int chf = 0;
int clf = 0;
- for ( int i = e, n = cs.length(); i < n; i++ ) {
- chf = cs.charAt ( i );
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
clf = BidiClassUtils.getBidiClass ( chf );
if ( clf != BidiConstants.NSM ) {
break;
}
}
- private static boolean hasMedialPrecedingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasMedialPrecedingContext ( int[] ca, int nc, int s, int e ) {
int chp = 0;
int clp = 0;
for ( int i = s; i > 0; i-- ) {
- chp = cs.charAt ( i - 1 );
- clp = BidiClassUtils.getBidiClass ( chp );
- if ( clp != BidiConstants.NSM ) {
- break;
+ int k = i - 1;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chp = ca [ k ];
+ clp = BidiClassUtils.getBidiClass ( chp );
+ if ( clp != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( clp != BidiConstants.AL ) {
}
}
- private static boolean hasMedialThisContext ( CharSequence cs, int s, int e ) {
- int chf = 0;
+ private static boolean hasMedialThisContext ( int[] ca, int nc, int s, int e ) {
+ int chf = 0; // first non-NSM char in [s,e)
int clf = 0;
for ( int i = 0, n = e - s; i < n; i++ ) {
- chf = cs.charAt ( s + i );
- clf = BidiClassUtils.getBidiClass ( chf );
- if ( clf != BidiConstants.NSM ) {
- break;
+ int k = s + i;
+ if ( ( k >= 0 ) && ( k < nc ) ) {
+ chf = ca [ s + i ];
+ clf = BidiClassUtils.getBidiClass ( chf );
+ if ( clf != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( clf != BidiConstants.AL ) {
return false;
}
- int chl = 0;
+ int chl = 0; // last non-NSM char in [s,e)
int cll = 0;
for ( int i = 0, n = e - s; i < n; i++ ) {
int k = n - i - 1;
- chl = cs.charAt ( s + k );
- cll = BidiClassUtils.getBidiClass ( chl );
- if ( cll != BidiConstants.NSM ) {
- break;
+ int j = s + k;
+ if ( ( j >= 0 ) && ( j < nc ) ) {
+ chl = ca [ j ];
+ cll = BidiClassUtils.getBidiClass ( chl );
+ if ( cll != BidiConstants.NSM ) {
+ break;
+ }
}
}
if ( cll != BidiConstants.AL ) {
}
}
- private static boolean hasMedialFollowingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasMedialFollowingContext ( int[] ca, int nc, int s, int e ) {
int chf = 0;
int clf = 0;
- for ( int i = e, n = cs.length(); i < n; i++ ) {
- chf = cs.charAt ( i );
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
clf = BidiClassUtils.getBidiClass ( chf );
if ( clf != BidiConstants.NSM ) {
break;
}
}
- private static boolean hasLigaturePrecedingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasLigaturePrecedingContext ( int[] ca, int nc, int s, int e ) {
return true;
}
- private static boolean hasLigatureFollowingContext ( CharSequence cs, int s, int e ) {
+ private static boolean hasLigatureFollowingContext ( int[] ca, int nc, int s, int e ) {
int chf = 0;
int clf = 0;
- for ( int i = e, n = cs.length(); i < n; i++ ) {
- chf = cs.charAt ( i );
+ for ( int i = e, n = nc; i < n; i++ ) {
+ chf = ca [ i ];
clf = BidiClassUtils.getBidiClass ( chf );
if ( clf != BidiConstants.NSM ) {
break;
return glyphStartIndex;
}
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ UC[" );
+ sb.append ( unicodeStart );
+ sb.append ( ',' );
+ sb.append ( unicodeEnd );
+ sb.append ( "]: GC[" );
+ sb.append ( glyphStartIndex );
+ sb.append ( ',' );
+ sb.append ( glyphStartIndex + ( unicodeEnd - unicodeStart ) );
+ sb.append ( "] }" );
+ return sb.toString();
+ }
+
}
*/
public class DefaultScriptProcessor extends ScriptProcessor {
+ /** features to use for substitutions */
+ private static final String[] gsubFeatures = // CSOK: ConstantNameCheck
+ {
+ "ccmp", // glyph composition/decomposition
+ "liga", // common ligatures
+ "locl" // localized forms
+ };
+
+ /** features to use for positioning */
+ private static final String[] gposFeatures = // CSOK: ConstantNameCheck
+ {
+ "kern" // kerning
+ };
+
DefaultScriptProcessor ( String script ) {
super ( script );
}
/** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
- return gs;
+ public String[] getSubstitutionFeatures() {
+ return gsubFeatures;
+ }
+
+ /** {@inheritDoc} */
+ public ScriptContextTester getSubstitutionContextTester() {
+ return null;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getPositioningFeatures() {
+ return gposFeatures;
}
/** {@inheritDoc} */
- public int[] position ( GlyphSequence cs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups ) {
+ public ScriptContextTester getPositioningContextTester() {
return null;
}
}
/** {@inheritDoc} */
- public int[] performPositioning ( CharSequence cs, String script, String language ) {
+ public int[][] performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
if ( metric instanceof Positionable ) {
Positionable p = (Positionable) metric;
- return p.performPositioning ( cs, script, language );
+ return p.performPositioning ( cs, script, language, fontSize );
} else {
throw new UnsupportedOperationException();
}
}
+ /** {@inheritDoc} */
+ public int[][] performPositioning ( CharSequence cs, String script, String language ) {
+ return performPositioning ( cs, script, language, fontSize );
+ }
+
}
}
}
+ // [GA] permit configuration control over base14 kerning; without this,
+ // there is no way for a user to enable base14 kerning other than by
+ // programmatic API;
+ if (cfg.getChild("base14-kerning", false) != null) {
+ try {
+ fontManager
+ .setBase14KerningEnabled(cfg.getChild("base14-kerning").getValueAsBoolean());
+ } catch (ConfigurationException e) {
+ LogUtil.handleException(log, e, true);
+ }
+ }
+
// global font configuration
Configuration fontsCfg = cfg.getChild("fonts", false);
if (fontsCfg != null) {
import org.apache.fop.apps.FOPException;
import org.apache.fop.fonts.apps.TTFReader;
-// CSOFF: LineLengthCheck
-
/**
* Class for reading a metric.xml file and creating a font object.
* Typical usage:
*/
public class FontReader extends DefaultHandler {
- private Locator locator = null;
+ // private Locator locator = null; // not used at present
private boolean isCID = false;
private CustomFont returnFont = null;
private MultiByteFont multiFont = null;
private List bfranges = null;
- /* advanced typographic (script extras) support */
- private boolean inScriptExtras = false;
- private int seTable = -1;
- private Map seLookups = null;
- private String seScript = null;
- private String seLanguage = null;
- private String seFeature = null;
- private String seUseLookup = null;
- private List seUseLookups = null;
- private String luID = null;
- private int luType = -1;
- private List ltSubtables = null;
- private int luSequence = -1;
- private int luFlags = 0;
- private int lstSequence = -1;
- private int lstFormat = -1;
- private List lstCoverage = null;
- private List lstGIDs = null;
- private List lstRanges = null;
- private List lstEntries = null;
- private List lstLIGSets = null;
- private List lstLIGs = null;
- private int ligGID = -1;
- /* end of script extras parse state */
-
private void createFont(InputSource source) throws FOPException {
XMLReader parser = null;
* {@inheritDoc}
*/
public void setDocumentLocator(Locator locator) {
- this.locator = locator;
+ // this.locator = locator; // not used at present
}
/**
*/
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
- if ( inScriptExtras ) {
- startElementScriptExtras ( uri, localName, qName, attributes );
- } else if (localName.equals("font-metrics")) {
+ if (localName.equals("font-metrics")) {
if ("TYPE0".equals(attributes.getValue("type"))) {
multiFont = new MultiByteFont();
returnFont = multiFont;
} else if ("pair".equals(localName)) {
currentKerning.put(new Integer(attributes.getValue("kpx2")),
new Integer(attributes.getValue("kern")));
- } else if ("script-extras".equals(localName)) {
- inScriptExtras = true;
}
}
*/
public void endElement(String uri, String localName, String qName) throws SAXException {
String content = text.toString().trim();
- if ( inScriptExtras ) {
- endElementScriptExtras ( uri, localName, qName, content );
- } else if ("font-name".equals(localName)) {
+ if ("font-name".equals(localName)) {
returnFont.setFontName(content);
} else if ("full-name".equals(localName)) {
returnFont.setFullName(content);
text.append(ch, start, length);
}
- private void validateScriptTag ( String tag )
- throws SAXException {
- }
-
- private void validateLanguageTag ( String tag, String script )
- throws SAXException {
- }
-
- private void validateFeatureTag ( String tag, int tableType, String script, String language )
- throws SAXException {
- }
-
- private int mapLookupType ( String type, int tableType ) {
- int t = -1;
- if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
- t = GlyphSubstitutionTable.getLookupTypeFromName ( type );
- } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
- t = GlyphPositioningTable.getLookupTypeFromName ( type );
- }
- return t;
- }
-
- private void validateLookupType ( String type, int tableType )
- throws SAXException {
- if ( mapLookupType ( type, tableType ) == -1 ) {
- throw new SAXParseException ( "invalid lookup type \'" + type + "\'", locator );
- }
- }
-
- private void startElementScriptExtras ( String uri, String localName, String qName, Attributes attributes )
- throws SAXException {
- if ( "gsub".equals(localName) ) {
- assert seLookups == null;
- seLookups = new java.util.HashMap();
- seTable = GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
- } else if ( "gpos".equals(localName) ) {
- assert seLookups == null;
- seLookups = new java.util.HashMap();
- seTable = GlyphTable.GLYPH_TABLE_TYPE_POSITIONING;
- } else if ( "script".equals(localName) ) {
- String tag = attributes.getValue("tag");
- if ( tag != null ) {
- assert seScript == null;
- validateScriptTag ( tag );
- seScript = tag;
- } else {
- throw new SAXParseException ( "missing tag attribute on <script/> element", locator );
- }
- } else if ( "lang".equals(localName) ) {
- String tag = attributes.getValue("tag");
- if ( tag != null ) {
- assert seLanguage == null;
- validateLanguageTag ( tag, seScript );
- seLanguage = tag;
- } else {
- throw new SAXParseException ( "missing tag attribute on <lang/> element", locator );
- }
- } else if ( "feature".equals(localName) ) {
- String tag = attributes.getValue("tag");
- if ( tag != null ) {
- validateFeatureTag ( tag, seTable, seScript, seLanguage );
- assert seFeature == null;
- seFeature = tag;
- } else {
- throw new SAXParseException ( "missing tag attribute on <feature/> element", locator );
- }
- } else if ( "use-lookup".equals(localName) ) {
- String ref = attributes.getValue("ref");
- if ( ref != null ) {
- assert seUseLookup == null;
- seUseLookup = ref;
- } else {
- throw new SAXParseException ( "missing ref attribute on <use-lookup/> element", locator );
- }
- } else if ( "lookup".equals(localName) ) {
- String id = attributes.getValue("id");
- if ( id != null ) {
- assert luID == null;
- luID = id; luSequence++; lstSequence = -1;
- } else {
- throw new SAXParseException ( "missing id attribute on <lookup/> element", locator );
- }
- String flags = attributes.getValue("flags");
- if ( flags != null ) {
- try {
- luFlags = Integer.parseInt ( flags );
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid flags attribute on <lookup/> element, must be integer", locator );
- }
- }
- String type = attributes.getValue("type");
- if ( type != null ) {
- validateLookupType ( type, seTable );
- assert luType == -1;
- luType = mapLookupType ( type, seTable );
- } else {
- throw new SAXParseException ( "missing type attribute on <lookup/> element", locator );
- }
- } else if ( "lst".equals(localName) ) {
- String format = attributes.getValue("format");
- if ( format != null ) {
- try {
- lstSequence++;
- lstFormat = Integer.parseInt ( format );
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid format attribute on <lst/> element, must be integer", locator );
- }
- assert lstCoverage == null;
- assert lstEntries == null;
- } else {
- throw new SAXParseException ( "missing format attribute on <lst/> element", locator );
- }
- } else if ( "coverage".equals(localName) ) {
- assert lstGIDs == null;
- assert lstRanges == null;
- lstGIDs = new java.util.ArrayList();
- lstRanges = new java.util.ArrayList();
- } else if ( "range".equals(localName) ) {
- String gs = attributes.getValue("gs");
- String ge = attributes.getValue("ge");
- String ci = attributes.getValue("ci");
- if ( ( gs != null ) && ( ge != null ) && ( ci != null ) ) {
- try {
- int s = Integer.parseInt ( gs );
- int e = Integer.parseInt ( ge );
- int i = Integer.parseInt ( ci );
- lstRanges.add ( new GlyphCoverageTable.CoverageRange ( s, e, i ) );
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid format attribute on <lst/> element, must be integer", locator );
- } catch ( IllegalArgumentException e ) {
- throw new SAXParseException ( "bad gs, ge, or ci attribute on <range/> element, must be non-negative integers, with gs <= ge", locator );
- }
- } else {
- throw new SAXParseException ( "missing gs, ge, or ci attribute on <range/> element", locator );
- }
- } else if ( "entries".equals(localName) ) {
- initEntriesState ( seTable, luType, lstFormat );
- } else if ( "ligs".equals(localName) ) {
- assert lstLIGs == null;
- lstLIGs = new java.util.ArrayList();
- } else if ( "lig".equals(localName) ) {
- if ( lstLIGs == null ) {
- throw new SAXParseException ( "missing container <ligs/> element for <lig/> element", locator );
- } else {
- String gid = attributes.getValue("gid");
- if ( gid != null ) {
- try {
- ligGID = Integer.parseInt ( gid );
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid gid attribute on <lig/> element, must be integer", locator );
- }
- } else {
- throw new SAXParseException ( "missing gid attribute on <lig/> element", locator );
- }
- }
- }
- }
-
- private void endElementScriptExtras ( String uri, String localName, String qName, String content )
- throws SAXException {
- if ( "script-extras".equals(localName) ) {
- inScriptExtras = false;
- } else if ( "gsub".equals(localName) ) {
- if ( ( ltSubtables != null ) && ( ltSubtables.size() > 0 ) ) {
- if ( multiFont.getGSUB() == null ) {
- multiFont.setGSUB ( new GlyphSubstitutionTable ( seLookups, ltSubtables ) );
- }
- }
- ltSubtables = null; seTable = -1; seLookups = null;
- } else if ( "gpos".equals(localName) ) {
- if ( ( ltSubtables != null ) && ( ltSubtables.size() > 0 ) ) {
- if ( multiFont.getGPOS() == null ) {
- multiFont.setGPOS ( new GlyphPositioningTable ( seLookups, ltSubtables ) );
- }
- }
- ltSubtables = null; seTable = -1; seLookups = null;
- } else if ( "script".equals(localName) ) {
- assert seUseLookups == null;
- assert seUseLookup == null;
- assert seFeature == null;
- assert seLanguage == null;
- seScript = null;
- } else if ( "lang".equals(localName) ) {
- assert seUseLookups == null;
- assert seUseLookup == null;
- assert seFeature == null;
- seLanguage = null;
- } else if ( "feature".equals(localName) ) {
- if ( ( seScript != null ) && ( seLanguage != null ) && ( seFeature != null ) ) {
- if ( ( seUseLookups != null ) && ( seUseLookups.size() > 0 ) ) {
- seLookups.put ( new GlyphTable.LookupSpec ( seScript, seLanguage, seFeature ), seUseLookups );
- }
- }
- seUseLookups = null; seFeature = null;
- } else if ( "use-lookup".equals(localName) ) {
- if ( seUseLookup != null ) {
- if ( seUseLookups == null ) {
- seUseLookups = new java.util.ArrayList();
- }
- seUseLookups.add ( seUseLookup );
- }
- seUseLookup = null;
- } else if ( "lookup".equals(localName) ) {
- luType = -1;
- luFlags = 0;
- } else if ( "lst".equals(localName) ) {
- assert lstCoverage != null;
- assert lstEntries != null;
- addLookupSubtable ( seTable, luType, luID, luSequence, luFlags, lstFormat, lstCoverage, lstEntries );
- lstFormat = -1;
- lstCoverage = null;
- lstEntries = null;
- } else if ( "coverage".equals(localName) ) {
- assert lstGIDs != null;
- assert lstRanges != null;
- assert lstCoverage == null;
- if ( lstGIDs.size() > 0 ) {
- lstCoverage = lstGIDs;
- } else if ( lstRanges.size() > 0 ) {
- lstCoverage = lstRanges;
- }
- lstGIDs = null; lstRanges = null;
- } else if ( "gid".equals(localName) ) {
- if ( lstGIDs != null ) {
- try {
- lstGIDs.add ( Integer.decode ( content ) );
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid <gid/> element content, must be integer", locator );
- }
- }
- } else if ( "entries".equals(localName) ) {
- finishEntriesState ( seTable, luType, lstFormat );
- } else if ( "ligs".equals(localName) ) {
- assert lstLIGSets != null;
- assert lstLIGs != null;
- lstLIGSets.add ( new GlyphSubstitutionTable.LigatureSet ( lstLIGs ) );
- lstLIGs = null;
- } else if ( "lig".equals(localName) ) {
- assert lstLIGs != null;
- assert ligGID >= 0;
- int[] ligComponents = parseLigatureComponents ( content );
- if ( ligComponents != null ) {
- lstLIGs.add ( new GlyphSubstitutionTable.Ligature ( ligGID, ligComponents ) );
- }
- ligGID = -1;
- }
- }
-
- private void initEntriesState ( int tableType, int lookupType, int subtableFormat ) {
- if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
- switch ( lookupType ) {
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
- assert lstGIDs == null;
- lstGIDs = new java.util.ArrayList();
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
- assert lstLIGSets == null;
- lstLIGSets = new java.util.ArrayList();
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
- throw new UnsupportedOperationException();
- default:
- break;
- }
- } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
- switch ( lookupType ) {
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CONTEXT:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
- throw new UnsupportedOperationException();
- default:
- break;
- }
- }
- }
-
- private void finishEntriesState ( int tableType, int lookupType, int subtableFormat ) {
- if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
- switch ( lookupType ) {
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
- assert lstGIDs != null;
- lstEntries = lstGIDs; lstGIDs = null;
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
- assert lstLIGSets != null;
- lstEntries = lstLIGSets; lstLIGSets = null;
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
- throw new UnsupportedOperationException();
- default:
- break;
- }
- } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
- switch ( lookupType ) {
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_SINGLE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_PAIR:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CURSIVE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_BASE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_MARK_TO_MARK:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CONTEXT:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
- case GlyphPositioningTable.GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
- throw new UnsupportedOperationException();
- default:
- break;
- }
- }
- }
-
- private void addLookupSubtable // CSOK: ParameterNumber
- ( int tableType, int lookupType, String lookupID, int lookupSequence, int lookupFlags, int subtableFormat, List coverage, List entries ) {
- GlyphSubtable st = null;
- if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
- st = GlyphSubstitutionTable.createSubtable ( lookupType, lookupID, lookupSequence, lookupFlags, subtableFormat, coverage, entries );
- } else if ( tableType == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
- st = GlyphPositioningTable.createSubtable ( lookupType, lookupID, lookupSequence, lookupFlags, subtableFormat, coverage, entries );
- }
- if ( st != null ) {
- if ( ltSubtables == null ) {
- ltSubtables = new java.util.ArrayList();
- }
- ltSubtables.add ( st );
- }
- }
-
- private int[] parseLigatureComponents ( String s )
- throws SAXParseException {
- String[] csa = s.split ( "\\s" );
- if ( ( csa == null ) || ( csa.length == 0 ) ) {
- throw new SAXParseException ( "invalid <lig/> element, must specify at least one component", locator );
- } else {
- int nc = csa.length;
- int[] components = new int [ nc ];
- for ( int i = 0, n = nc; i < n; i++ ) {
- String cs = csa [ i ];
- int c;
- try {
- c = Integer.parseInt ( cs );
- if ( ( c < 0 ) || ( c > 65535 ) ) {
- throw new SAXParseException ( "invalid component value (" + c + ") in <lig/> element, out of range", locator );
- } else {
- components [ i ] = c;
- }
- } catch ( NumberFormatException e ) {
- throw new SAXParseException ( "invalid component \"" + cs + "\" in <lig/> element, must be integer", locator );
- }
-
- }
- return components;
- }
- }
-
}
-
-
--- /dev/null
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphClassMapping</code> interface provides glyph identifier to class
+ * index mapping support.
+ * @author Glenn Adams
+ */
+public interface GlyphClassMapping {
+
+ /**
+ * Obtain size of class table, i.e., ciMax + 1, where ciMax is the maximum
+ * class index.
+ * @param set for coverage set based class mappings, indicates set index, otherwise ignored
+ * @return size of class table
+ */
+ int getClassSize ( int set );
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the class table.
+ * @param gid glyph identifier (code)
+ * @param set for coverage set based class mappings, indicates set index, otherwise ignored
+ * @return non-negative glyph class index or -1 if glyph identifiers is not mapped by table
+ */
+ int getClassIndex ( int gid, int set );
+
+}
--- /dev/null
+/*
+ * 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.fonts;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * Base class implementation of glyph class table.
+ * @author Glenn Adams
+ */
+public final class GlyphClassTable extends GlyphMappingTable implements GlyphClassMapping {
+
+ /** empty mapping table */
+ public static final int GLYPH_CLASS_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY;
+
+ /** mapped mapping table */
+ public static final int GLYPH_CLASS_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED;
+
+ /** range based mapping table */
+ public static final int GLYPH_CLASS_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE;
+
+ /** empty mapping table */
+ public static final int GLYPH_CLASS_TYPE_COVERAGE_SET = 3;
+
+ private GlyphClassMapping cm;
+
+ private GlyphClassTable ( GlyphClassMapping cm ) {
+ assert cm != null;
+ assert cm instanceof GlyphMappingTable;
+ this.cm = cm;
+ }
+
+ /** {@inheritDoc} */
+ public int getType() {
+ return ( (GlyphMappingTable) cm ) .getType();
+ }
+
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return ( (GlyphMappingTable) cm ) .getEntries();
+ }
+
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return cm.getClassSize ( set );
+ }
+
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return cm.getClassIndex ( gid, set );
+ }
+
+ /**
+ * Create glyph class table.
+ * @param entries list of mapped or ranged class entries, or null or empty list
+ * @return a new covera table instance
+ */
+ public static GlyphClassTable createClassTable ( List entries ) {
+ GlyphClassMapping cm;
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ cm = new EmptyClassTable ( entries );
+ } else if ( isMappedClass ( entries ) ) {
+ cm = new MappedClassTable ( entries );
+ } else if ( isRangeClass ( entries ) ) {
+ cm = new RangeClassTable ( entries );
+ } else if ( isCoverageSetClass ( entries ) ) {
+ cm = new CoverageSetClassTable ( entries );
+ } else {
+ cm = null;
+ }
+ assert cm != null : "unknown class type";
+ return new GlyphClassTable ( cm );
+ }
+
+ private static boolean isMappedClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof Integer ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static boolean isRangeClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof MappingRange ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static boolean isCoverageSetClass ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ return false;
+ } else {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( ! ( o instanceof GlyphCoverageTable ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ private static class EmptyClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping {
+ public EmptyClassTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return -1;
+ }
+ }
+
+ private static class MappedClassTable extends GlyphMappingTable.MappedMappingTable implements GlyphClassMapping {
+ private int firstGlyph;
+ private int[] gca;
+ private int gcMax = -1;
+ public MappedClassTable ( List entries ) {
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new java.util.ArrayList();
+ entries.add ( Integer.valueOf ( firstGlyph ) );
+ if ( gca != null ) {
+ for ( int i = 0, n = gca.length; i < n; i++ ) {
+ entries.add ( Integer.valueOf ( gca [ i ] ) );
+ }
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return gcMax + 1;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ int i = gid - firstGlyph;
+ if ( ( i >= 0 ) && ( i < gca.length ) ) {
+ return gca [ i ];
+ } else {
+ return -1;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return getMappedIndex ( gid );
+ }
+ private void populate ( List entries ) {
+ // obtain entries iterator
+ Iterator it = entries.iterator();
+ // extract first glyph
+ int firstGlyph = 0;
+ if ( it.hasNext() ) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ firstGlyph = ( (Integer) o ) . intValue();
+ } else {
+ throw new IllegalArgumentException ( "illegal entry, first entry must be Integer denoting first glyph value, but is: " + o );
+ }
+ }
+ // extract glyph class array
+ int i = 0, n = entries.size() - 1, gcMax = -1;
+ int[] gca = new int [ n ];
+ while ( it.hasNext() ) {
+ Object o = it.next();
+ if ( o instanceof Integer ) {
+ int gc = ( (Integer) o ) . intValue();
+ gca [ i++ ] = gc;
+ if ( gc > gcMax ) {
+ gcMax = gc;
+ }
+ } else {
+ throw new IllegalArgumentException ( "illegal mapping entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.gca == null;
+ this.firstGlyph = firstGlyph;
+ this.gca = gca;
+ this.gcMax = gcMax;
+ }
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append("{ firstGlyph = " + firstGlyph + ", classes = {");
+ for ( int i = 0, n = gca.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append ( Integer.toString ( gca [ i ] ) );
+ }
+ sb.append("} }");
+ return sb.toString();
+ }
+ }
+
+ private static class RangeClassTable extends GlyphMappingTable.RangeMappingTable implements GlyphClassMapping {
+ public RangeClassTable ( List entries ) {
+ super ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid, int s, int m ) {
+ return m;
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return getMappedIndex ( gid );
+ }
+ }
+
+ private static class CoverageSetClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping {
+ public CoverageSetClassTable ( List entries ) {
+ throw new UnsupportedOperationException ( "coverage set class table not yet supported" );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_CLASS_TYPE_COVERAGE_SET;
+ }
+ /** {@inheritDoc} */
+ public int getClassSize ( int set ) {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getClassIndex ( int gid, int set ) {
+ return -1;
+ }
+ }
+
+}
/**
* Perform a test on a glyph sequence in a specific (originating) character context.
* @param gs glyph sequence to test
- * @param ca character association defining the context of test
+ * @param index index into glyph sequence to test
* @return true if test is satisfied
*/
- boolean test ( GlyphSequence gs, GlyphSequence.CharAssociation ca );
+ boolean test ( GlyphSequence gs, int index );
}
--- /dev/null
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphCoverageMapping</code> interface provides glyph identifier to coverage
+ * index mapping support.
+ * @author Glenn Adams
+ */
+public interface GlyphCoverageMapping {
+
+ /**
+ * Obtain size of coverage table, i.e., ciMax + 1, where ciMax is the maximum
+ * coverage index.
+ * @return size of coverage table
+ */
+ int getCoverageSize();
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the coverage table.
+ * @param gid glyph identifier (code)
+ * @return non-negative glyph coverage index or -1 if glyph identifiers is not mapped by table
+ */
+ int getCoverageIndex ( int gid );
+
+}
import java.util.List;
import java.util.Iterator;
-// CSOFF: NoWhitespaceAfterCheck
-// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
/**
- * Abstract base class implementation of glyph coverage table.
+ * Base class implementation of glyph coverage table.
* @author Glenn Adams
*/
-public abstract class GlyphCoverageTable {
+public final class GlyphCoverageTable extends GlyphMappingTable implements GlyphCoverageMapping {
- /** empty coverage table */
- public static final int GLYPH_COVERAGE_TYPE_EMPTY = 0;
+ /** empty mapping table */
+ public static final int GLYPH_COVERAGE_TYPE_EMPTY = GLYPH_MAPPING_TYPE_EMPTY;
- /** mapped coverage table */
- public static final int GLYPH_COVERAGE_TYPE_MAPPED = 1;
+ /** mapped mapping table */
+ public static final int GLYPH_COVERAGE_TYPE_MAPPED = GLYPH_MAPPING_TYPE_MAPPED;
- /** range based coverage table */
- public static final int GLYPH_COVERAGE_TYPE_RANGE = 2;
+ /** range based mapping table */
+ public static final int GLYPH_COVERAGE_TYPE_RANGE = GLYPH_MAPPING_TYPE_RANGE;
- /**
- * Obtain coverage type.
- * @return coverage format type
- */
- public abstract int getType();
+ private GlyphCoverageMapping cm;
- /**
- * Obtain coverage entries.
- * @return list of coverage entries
- */
- public abstract List getEntries();
+ private GlyphCoverageTable ( GlyphCoverageMapping cm ) {
+ assert cm != null;
+ assert cm instanceof GlyphMappingTable;
+ this.cm = cm;
+ }
- /**
- * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
- * the coverage table.
- * @param gid glyph identifier (code)
- * @return non-negative glyph coverage index or -1 if glyph identifiers is not mapped by table
- */
- public abstract int getCoverageIndex ( int gid );
+ /** {@inheritDoc} */
+ public int getType() {
+ return ( (GlyphMappingTable) cm ) .getType();
+ }
+
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return ( (GlyphMappingTable) cm ) .getEntries();
+ }
+
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return cm.getCoverageSize();
+ }
+
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return cm.getCoverageIndex ( gid );
+ }
/**
* Create glyph coverage table.
- * @param coverage list of mapped or ranged coverage entries, or null or empty list
+ * @param entries list of mapped or ranged coverage entries, or null or empty list
* @return a new covera table instance
*/
- public static GlyphCoverageTable createCoverageTable ( List coverage ) {
- GlyphCoverageTable ct;
- if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
- ct = new EmptyCoverageTable ( coverage );
- } else if ( isMappedCoverage ( coverage ) ) {
- ct = new MappedCoverageTable ( coverage );
- } else if ( isRangeCoverage ( coverage ) ) {
- ct = new RangeCoverageTable ( coverage );
+ public static GlyphCoverageTable createCoverageTable ( List entries ) {
+ GlyphCoverageMapping cm;
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
+ cm = new EmptyCoverageTable ( entries );
+ } else if ( isMappedCoverage ( entries ) ) {
+ cm = new MappedCoverageTable ( entries );
+ } else if ( isRangeCoverage ( entries ) ) {
+ cm = new RangeCoverageTable ( entries );
} else {
- ct = null;
+ cm = null;
}
- assert ct != null : "unknown coverage type";
- return ct;
+ assert cm != null : "unknown coverage type";
+ return new GlyphCoverageTable ( cm );
}
- private static boolean isMappedCoverage ( List coverage ) {
- if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
+ private static boolean isMappedCoverage ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
return false;
} else {
- for ( Iterator it = coverage.iterator(); it.hasNext();) {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
Object o = it.next();
if ( ! ( o instanceof Integer ) ) {
return false;
}
}
- private static boolean isRangeCoverage ( List coverage ) {
- if ( ( coverage == null ) || ( coverage.size() == 0 ) ) {
+ private static boolean isRangeCoverage ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() == 0 ) ) {
return false;
} else {
- for ( Iterator it = coverage.iterator(); it.hasNext();) {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
Object o = it.next();
- if ( ! ( o instanceof CoverageRange ) ) {
+ if ( ! ( o instanceof MappingRange ) ) {
return false;
}
}
}
}
- private static class EmptyCoverageTable extends GlyphCoverageTable {
- public EmptyCoverageTable ( List coverage ) {
+ private static class EmptyCoverageTable extends GlyphMappingTable.EmptyMappingTable implements GlyphCoverageMapping {
+ public EmptyCoverageTable ( List entries ) {
+ super ( entries );
}
- public int getType() {
- return GLYPH_COVERAGE_TYPE_EMPTY;
- }
- public List getEntries() {
- return new java.util.ArrayList();
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return 0;
}
+ /** {@inheritDoc} */
public int getCoverageIndex ( int gid ) {
return -1;
}
}
- private static class MappedCoverageTable extends GlyphCoverageTable {
- private int[] map = null;
- public MappedCoverageTable ( List coverage ) {
- populate ( coverage );
- }
- public int getType() {
- return GLYPH_COVERAGE_TYPE_MAPPED;
+ private static class MappedCoverageTable extends GlyphMappingTable.MappedMappingTable implements GlyphCoverageMapping {
+ private int[] map;
+ public MappedCoverageTable ( List entries ) {
+ populate ( entries );
}
+ /** {@inheritDoc} */
public List getEntries() {
List entries = new java.util.ArrayList();
if ( map != null ) {
}
return entries;
}
- public int getCoverageIndex ( int gid ) {
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return ( map != null ) ? map.length : 0;
+ }
+ public int getMappedIndex ( int gid ) {
int i;
if ( ( i = Arrays.binarySearch ( map, gid ) ) >= 0 ) {
return i;
return -1;
}
}
- private void populate ( List coverage ) {
- int i = 0, n = coverage.size(), gidMax = -1;
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return getMappingSize();
+ }
+ /** {@inheritDoc} */
+ public int getCoverageIndex ( int gid ) {
+ return getMappedIndex ( gid );
+ }
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size(), gidMax = -1;
int[] map = new int [ n ];
- for ( Iterator it = coverage.iterator(); it.hasNext();) {
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
Object o = it.next();
if ( o instanceof Integer ) {
int gid = ( (Integer) o ) . intValue();
assert this.map == null;
this.map = map;
}
+ /** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append('{');
}
}
- private static class RangeCoverageTable extends GlyphCoverageTable {
- private int[] sa = null; // array of ranges starts
- private int[] ea = null; // array of range ends
- private int[] ca = null; // array of range coverage (start) indices
- public RangeCoverageTable ( List coverage ) {
- populate ( coverage );
+ private static class RangeCoverageTable extends GlyphMappingTable.RangeMappingTable implements GlyphCoverageMapping {
+ public RangeCoverageTable ( List entries ) {
+ super ( entries );
}
- public int getType() {
- return GLYPH_COVERAGE_TYPE_RANGE;
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid, int s, int m ) {
+ return m + gid - s;
}
- public List getEntries() {
- List entries = new java.util.ArrayList();
- if ( sa != null ) {
- for ( int i = 0, n = sa.length; i < n; i++ ) {
- entries.add ( new CoverageRange ( sa [ i ], ea [ i ], ca [ i ] ) );
- }
- }
- return entries;
+ /** {@inheritDoc} */
+ public int getCoverageSize() {
+ return getMappingSize();
}
+ /** {@inheritDoc} */
public int getCoverageIndex ( int gid ) {
- int i, ci;
- if ( ( i = Arrays.binarySearch ( sa, gid ) ) >= 0 ) {
- ci = ca [ i ] + gid - sa [ i ]; // matches start of (some) range
- } else if ( ( i = - ( i + 1 ) ) == 0 ) {
- ci = -1; // precedes first range
- } else if ( gid > ea [ --i ] ) {
- ci = -1; // follows preceding (or last) range
- } else {
- ci = ca [ i ] + gid - sa [ i ]; // intersects (some) range
- }
- return ci;
- }
- private void populate ( List coverage ) {
- int i = 0, n = coverage.size(), gidMax = -1;
- int[] sa = new int [ n ];
- int[] ea = new int [ n ];
- int[] ca = new int [ n ];
- for ( Iterator it = coverage.iterator(); it.hasNext();) {
- Object o = it.next();
- if ( o instanceof CoverageRange ) {
- CoverageRange r = (CoverageRange) o;
- int gs = r.getStart();
- int ge = r.getEnd();
- int ci = r.getIndex();
- if ( ( gs < 0 ) || ( gs > 65535 ) ) {
- throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad start index" );
- } else if ( ( ge < 0 ) || ( ge > 65535 ) ) {
- throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad end index" );
- } else if ( gs > ge ) {
- throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index" );
- } else if ( gs < gidMax ) {
- throw new IllegalArgumentException ( "out of order glyph range: [" + gs + "," + ge + "]" );
- } else if ( ci < 0 ) {
- throw new IllegalArgumentException ( "illegal coverage index: " + ci );
- } else {
- sa [ i ] = gs;
- ea [ i ] = gidMax = ge;
- ca [ i ] = ci;
- i++;
- }
- } else {
- throw new IllegalArgumentException ( "illegal coverage entry, must be Integer: " + o );
- }
- }
- assert i == n;
- assert this.sa == null;
- assert this.ea == null;
- assert this.ca == null;
- this.sa = sa;
- this.ea = ea;
- this.ca = ca;
- }
- public String toString() {
- StringBuffer sb = new StringBuffer();
- sb.append('{');
- for ( int i = 0, n = sa.length; i < n; i++ ) {
- if ( i > 0 ) {
- sb.append(',');
- }
- sb.append ( '[' );
- sb.append ( Integer.toString ( sa [ i ] ) );
- sb.append ( Integer.toString ( ea [ i ] ) );
- sb.append ( "]:" );
- sb.append ( Integer.toString ( ca [ i ] ) );
- }
- sb.append('}');
- return sb.toString();
- }
- }
-
- /**
- * The <code>CoverageRange</code> class encapsulates a glyph [start,end] range and
- * a coverage index.
- */
- public static class CoverageRange {
-
- private final int gidStart; // first glyph in range (inclusive)
- private final int gidEnd; // last glyph in range (inclusive)
- private final int index; // coverage index;
-
- /**
- * Instantiate a coverage range.
- */
- public CoverageRange() {
- this ( 0, 0, 0 );
+ return getMappedIndex ( gid );
}
-
- /**
- * Instantiate a specific coverage range.
- * @param gidStart start of range
- * @param gidEnd end of range
- * @param index coverage index
- */
- public CoverageRange ( int gidStart, int gidEnd, int index ) {
- if ( ( gidStart < 0 ) || ( gidEnd < 0 ) || ( index < 0 ) ) {
- throw new IllegalArgumentException();
- } else if ( gidStart > gidEnd ) {
- throw new IllegalArgumentException();
- } else {
- this.gidStart = gidStart;
- this.gidEnd = gidEnd;
- this.index = index;
- }
- }
-
- /** @return start of range */
- public int getStart() {
- return gidStart;
- }
-
- /** @return end of range */
- public int getEnd() {
- return gidEnd;
- }
-
- /** @return coverage index */
- public int getIndex() {
- return index;
- }
-
- /** @return interval as a pair of integers */
- public int[] getInterval() {
- return new int[] { gidStart, gidEnd };
- }
-
- /**
- * Obtain interval, filled into first two elements of specified array, or returning new array.
- * @param interval an array of length two or greater or null
- * @return interval as a pair of integers, filled into specified array
- */
- public int[] getInterval ( int[] interval ) {
- if ( ( interval == null ) || ( interval.length != 2 ) ) {
- throw new IllegalArgumentException();
- } else {
- interval[0] = gidStart;
- interval[1] = gidEnd;
- }
- return interval;
- }
-
- /** @return length of interval */
- public int getLength() {
- return gidStart - gidEnd;
- }
-
}
}
--- /dev/null
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphDefinition</code> interface is a marker interface implemented by a glyph definition
+ * subtable.
+ * @author Glenn Adams
+ */
+public interface GlyphDefinition {
+
+ /**
+ * Determine if some definition is available for a specific glyph.
+ * @param gi a glyph index
+ * @return true if some (unspecified) definition is available for the specified glyph
+ */
+ boolean hasDefinition ( int gi );
+
+}
--- /dev/null
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+
+/**
+ * The <code>GlyphDefinitionSubtable</code> implements an abstract base of a glyph definition subtable,
+ * providing a default implementation of the <code>GlyphDefinition</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphDefinitionSubtable extends GlyphSubtable implements GlyphDefinition {
+
+ /**
+ * Instantiate a <code>GlyphDefinitionSubtable</code>.
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param mapping subtable coverage table
+ */
+ protected GlyphDefinitionSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+
+ /** {@inheritDoc} */
+ public int getTableType() {
+ return GlyphTable.GLYPH_TABLE_TYPE_DEFINITION;
+ }
+
+ /** {@inheritDoc} */
+ public String getTypeName() {
+ return GlyphDefinitionTable.getLookupTypeName ( getType() );
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean hasDefinition ( int gi ) {
+ GlyphCoverageMapping cvm;
+ if ( ( cvm = getCoverage() ) != null ) {
+ if ( cvm.getCoverageIndex ( gi ) >= 0 ) {
+ return true;
+ }
+ }
+ GlyphClassMapping clm;
+ if ( ( clm = getClasses() ) != null ) {
+ if ( clm.getClassIndex ( gi, 0 ) >= 0 ) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
--- /dev/null
+/*
+ * 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.fonts;
+
+import java.nio.CharBuffer;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * The <code>GlyphDefinitionTable</code> class is a glyph table that implements
+ * glyph definition functionality according to the OpenType GDEF table.
+ * @author Glenn Adams
+ */
+public class GlyphDefinitionTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphDefinitionTable.class); // CSOK: ConstantNameCheck
+
+ /** glyph class subtable type */
+ public static final int GDEF_LOOKUP_TYPE_GLYPH_CLASS = 1;
+ /** attachment point subtable type */
+ public static final int GDEF_LOOKUP_TYPE_ATTACHMENT_POINT = 2;
+ /** ligature caret subtable type */
+ public static final int GDEF_LOOKUP_TYPE_LIGATURE_CARET = 3;
+ /** mark attachment subtable type */
+ public static final int GDEF_LOOKUP_TYPE_MARK_ATTACHMENT = 4;
+
+ /** pre-defined glyph class - base glyph */
+ public static final int GLYPH_CLASS_BASE = 1;
+ /** pre-defined glyph class - ligature glyph */
+ public static final int GLYPH_CLASS_LIGATURE = 2;
+ /** pre-defined glyph class - mark glyph */
+ public static final int GLYPH_CLASS_MARK = 3;
+ /** pre-defined glyph class - component glyph */
+ public static final int GLYPH_CLASS_COMPONENT = 4;
+
+ /** singleton glyph class table */
+ private GlyphClassSubtable gct;
+ /** singleton attachment point table */
+ // private AttachmentPointSubtable apt;
+ /** singleton ligature caret table */
+ // private LigatureCaretSubtable lct;
+ /** singleton mark attachment table */
+ // private MarkAttachmentSubtable mat;
+
+ /**
+ * Instantiate a <code>GlyphDefinitionTable</code> object using the specified subtables.
+ * @param subtables a list of identified subtables
+ */
+ public GlyphDefinitionTable ( List subtables ) {
+ super ( null, new HashMap(0) );
+ if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
+ throw new IllegalArgumentException ( "subtables must be non-empty" );
+ } else {
+ for ( Iterator it = subtables.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof GlyphDefinitionSubtable ) {
+ addSubtable ( (GlyphSubtable) o );
+ } else {
+ throw new IllegalArgumentException ( "subtable must be a glyph definition subtable" );
+ }
+ }
+ freezeSubtables();
+ }
+ }
+
+ /** {@inheritDoc} */
+ protected void addSubtable ( GlyphSubtable subtable ) {
+ if ( subtable instanceof GlyphClassSubtable ) {
+ this.gct = (GlyphClassSubtable) subtable;
+ } else if ( subtable instanceof AttachmentPointSubtable ) {
+ // TODO - not yet used
+ // this.apt = (AttachmentPointSubtable) subtable;
+ } else if ( subtable instanceof LigatureCaretSubtable ) {
+ // TODO - not yet used
+ // this.lct = (LigatureCaretSubtable) subtable;
+ } else if ( subtable instanceof MarkAttachmentSubtable ) {
+ // TODO - not yet used
+ // this.mat = (MarkAttachmentSubtable) subtable;
+ } else {
+ throw new UnsupportedOperationException ( "unsupported glyph definition subtable type: " + subtable );
+ }
+ }
+
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public boolean isGlyphClass ( int gid, int gc ) {
+ if ( gct != null ) {
+ return gct.isGlyphClass ( gid, gc );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Map a lookup type name to its constant (integer) value.
+ * @param name lookup type name
+ * @return lookup type
+ */
+ public static int getLookupTypeFromName ( String name ) {
+ int t;
+ String s = name.toLowerCase();
+ if ( "glyphclass".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ } else if ( "attachmentpoint".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ } else if ( "ligaturecaret".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ } else if ( "markattachment".equals ( s ) ) {
+ t = GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ } else {
+ t = -1;
+ }
+ return t;
+ }
+
+ /**
+ * Map a lookup type constant (integer) value to its name.
+ * @param type lookup type
+ * @return lookup type name
+ */
+ public static String getLookupTypeName ( int type ) {
+ String tn = null;
+ switch ( type ) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ tn = "glyphclass";
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ tn = "attachmentpoint";
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ tn = "ligaturecaret";
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ tn = "markattachment";
+ break;
+ default:
+ tn = "unknown";
+ break;
+ }
+ return tn;
+ }
+
+ /**
+ * Create a definition subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags (must be zero)
+ * @param format subtable format
+ * @param mapping subtable mapping table
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ GlyphSubtable st = null;
+ switch ( type ) {
+ case GDEF_LOOKUP_TYPE_GLYPH_CLASS:
+ st = GlyphClassSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_ATTACHMENT_POINT:
+ st = AttachmentPointSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_LIGATURE_CARET:
+ st = LigatureCaretSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ case GDEF_LOOKUP_TYPE_MARK_ATTACHMENT:
+ st = MarkAttachmentSubtable.create ( id, sequence, flags, format, mapping, entries );
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ private abstract static class GlyphClassSubtable extends GlyphDefinitionSubtable {
+ GlyphClassSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ }
+ /**
+ * Determine if glyph belongs to pre-defined glyph class.
+ * @param gid a glyph identifier (index)
+ * @param gc a pre-defined glyph class (GLYPH_CLASS_BASE|GLYPH_CLASS_LIGATURE|GLYPH_CLASS_MARK|GLYPH_CLASS_COMPONENT).
+ * @return true if glyph belongs to specified glyph class
+ */
+ public abstract boolean isGlyphClass ( int gid, int gc );
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new GlyphClassSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class GlyphClassSubtableFormat1 extends GlyphClassSubtable {
+ GlyphClassSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphClassSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean isGlyphClass ( int gid, int gc ) {
+ GlyphClassMapping cm = getClasses();
+ if ( cm != null ) {
+ return cm.getClassIndex ( gid, 0 ) == gc;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private abstract static class AttachmentPointSubtable extends GlyphDefinitionSubtable {
+ AttachmentPointSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ }
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new AttachmentPointSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class AttachmentPointSubtableFormat1 extends AttachmentPointSubtable {
+ AttachmentPointSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof AttachmentPointSubtable;
+ }
+ }
+
+ private abstract static class LigatureCaretSubtable extends GlyphDefinitionSubtable {
+ LigatureCaretSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ }
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new LigatureCaretSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class LigatureCaretSubtableFormat1 extends LigatureCaretSubtable {
+ LigatureCaretSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof LigatureCaretSubtable;
+ }
+ }
+
+ private abstract static class MarkAttachmentSubtable extends GlyphDefinitionSubtable {
+ MarkAttachmentSubtable ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ }
+ static GlyphDefinitionSubtable create ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ if ( format == 1 ) {
+ return new MarkAttachmentSubtableFormat1 ( id, sequence, flags, format, mapping, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkAttachmentSubtableFormat1 extends MarkAttachmentSubtable {
+ MarkAttachmentSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphMappingTable mapping, List entries ) {
+ super ( id, sequence, flags, format, mapping, entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return null;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkAttachmentSubtable;
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.fonts;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+
+/**
+ * Base class implementation of glyph mapping table. This base
+ * class maps glyph indices to arbitrary integers (mappping indices), and
+ * is used to implement both glyph coverage and glyph class maps.
+ * @author Glenn Adams
+ */
+public class GlyphMappingTable {
+
+ /** empty mapping table */
+ public static final int GLYPH_MAPPING_TYPE_EMPTY = 0;
+
+ /** mapped mapping table */
+ public static final int GLYPH_MAPPING_TYPE_MAPPED = 1;
+
+ /** range based mapping table */
+ public static final int GLYPH_MAPPING_TYPE_RANGE = 2;
+
+ /**
+ * Obtain mapping type.
+ * @return mapping format type
+ */
+ public int getType() {
+ return -1;
+ }
+
+ /**
+ * Obtain mapping entries.
+ * @return list of mapping entries
+ */
+ public List getEntries() {
+ return null;
+ }
+
+ /**
+ * Obtain size of mapping table, i.e., ciMax + 1, where ciMax is the maximum
+ * mapping index.
+ * @return size of mapping table
+ */
+ public int getMappingSize() {
+ return 0;
+ }
+
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the mapping table.
+ * @param gid glyph identifier (code)
+ * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
+ */
+ public int getMappedIndex ( int gid ) {
+ return -1;
+ }
+
+ /** empty mapping table base class */
+ protected static class EmptyMappingTable extends GlyphMappingTable {
+ /**
+ * Construct empty mapping table.
+ */
+ public EmptyMappingTable() {
+ this ( (List) null );
+ }
+ /**
+ * Construct empty mapping table with entries (ignored).
+ * @param entries list of entries (ignored)
+ */
+ public EmptyMappingTable ( List entries ) {
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_EMPTY;
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ return new java.util.ArrayList();
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return 0;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ return -1;
+ }
+ }
+
+ /** mapped mapping table base class */
+ protected static class MappedMappingTable extends GlyphMappingTable {
+ /**
+ * Construct mapped mapping table.
+ */
+ public MappedMappingTable() {
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_MAPPED;
+ }
+ }
+
+ /** range mapping table base class */
+ protected abstract static class RangeMappingTable extends GlyphMappingTable {
+ private int[] sa = null; // array of range (inclusive) starts
+ private int[] ea = null; // array of range (inclusive) ends
+ private int[] ma = null; // array of range mapped values
+ private int miMax = -1;
+ /**
+ * Construct range mapping table.
+ * @param entries of mapping ranges
+ */
+ public RangeMappingTable ( List entries ) {
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GLYPH_MAPPING_TYPE_RANGE;
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ List entries = new java.util.ArrayList();
+ if ( sa != null ) {
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ entries.add ( new MappingRange ( sa [ i ], ea [ i ], ma [ i ] ) );
+ }
+ }
+ return entries;
+ }
+ /** {@inheritDoc} */
+ public int getMappingSize() {
+ return miMax + 1;
+ }
+ /** {@inheritDoc} */
+ public int getMappedIndex ( int gid ) {
+ int i, mi;
+ if ( ( i = Arrays.binarySearch ( sa, gid ) ) >= 0 ) {
+ mi = getMappedIndex ( gid, sa [ i ], ma [ i ] ); // matches start of (some) range
+ } else if ( ( i = - ( i + 1 ) ) == 0 ) {
+ mi = -1; // precedes first range
+ } else if ( gid > ea [ --i ] ) {
+ mi = -1; // follows preceding (or last) range
+ } else {
+ mi = getMappedIndex ( gid, sa [ i ], ma [ i ] ); // intersects (some) range
+ }
+ return mi;
+ }
+ /**
+ * Map glyph identifier (code) to coverge index. Returns -1 if glyph identifier is not in the domain of
+ * the mapping table.
+ * @param gid glyph identifier (code)
+ * @param s start of range
+ * @param m mapping value
+ * @return non-negative glyph mapping index or -1 if glyph identifiers is not mapped by table
+ */
+ public abstract int getMappedIndex ( int gid, int s, int m );
+ private void populate ( List entries ) {
+ int i = 0, n = entries.size(), gidMax = -1, miMax = -1;
+ int[] sa = new int [ n ];
+ int[] ea = new int [ n ];
+ int[] ma = new int [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof MappingRange ) {
+ MappingRange r = (MappingRange) o;
+ int gs = r.getStart();
+ int ge = r.getEnd();
+ int mi = r.getIndex();
+ if ( ( gs < 0 ) || ( gs > 65535 ) ) {
+ throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad start index" );
+ } else if ( ( ge < 0 ) || ( ge > 65535 ) ) {
+ throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: bad end index" );
+ } else if ( gs > ge ) {
+ throw new IllegalArgumentException ( "illegal glyph range: [" + gs + "," + ge + "]: start index exceeds end index" );
+ } else if ( gs < gidMax ) {
+ throw new IllegalArgumentException ( "out of order glyph range: [" + gs + "," + ge + "]" );
+ } else if ( mi < 0 ) {
+ throw new IllegalArgumentException ( "illegal mapping index: " + mi );
+ } else {
+ int miLast;
+ sa [ i ] = gs;
+ ea [ i ] = gidMax = ge;
+ ma [ i ] = mi;
+ if ( ( miLast = mi + ( ge - gs ) ) > miMax ) {
+ miMax = miLast;
+ }
+ i++;
+ }
+ } else {
+ throw new IllegalArgumentException ( "illegal mapping entry, must be Integer: " + o );
+ }
+ }
+ assert i == n;
+ assert this.sa == null;
+ assert this.ea == null;
+ assert this.ma == null;
+ this.sa = sa;
+ this.ea = ea;
+ this.ma = ma;
+ this.miMax = miMax;
+ }
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append('{');
+ for ( int i = 0, n = sa.length; i < n; i++ ) {
+ if ( i > 0 ) {
+ sb.append(',');
+ }
+ sb.append ( '[' );
+ sb.append ( Integer.toString ( sa [ i ] ) );
+ sb.append ( Integer.toString ( ea [ i ] ) );
+ sb.append ( "]:" );
+ sb.append ( Integer.toString ( ma [ i ] ) );
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+ }
+
+ /**
+ * The <code>MappingRange</code> class encapsulates a glyph [start,end] range and
+ * a mapping index.
+ */
+ public static class MappingRange {
+
+ private final int gidStart; // first glyph in range (inclusive)
+ private final int gidEnd; // last glyph in range (inclusive)
+ private final int index; // mapping index;
+
+ /**
+ * Instantiate a mapping range.
+ */
+ public MappingRange() {
+ this ( 0, 0, 0 );
+ }
+
+ /**
+ * Instantiate a specific mapping range.
+ * @param gidStart start of range
+ * @param gidEnd end of range
+ * @param index mapping index
+ */
+ public MappingRange ( int gidStart, int gidEnd, int index ) {
+ if ( ( gidStart < 0 ) || ( gidEnd < 0 ) || ( index < 0 ) ) {
+ throw new IllegalArgumentException();
+ } else if ( gidStart > gidEnd ) {
+ throw new IllegalArgumentException();
+ } else {
+ this.gidStart = gidStart;
+ this.gidEnd = gidEnd;
+ this.index = index;
+ }
+ }
+
+ /** @return start of range */
+ public int getStart() {
+ return gidStart;
+ }
+
+ /** @return end of range */
+ public int getEnd() {
+ return gidEnd;
+ }
+
+ /** @return mapping index */
+ public int getIndex() {
+ return index;
+ }
+
+ /** @return interval as a pair of integers */
+ public int[] getInterval() {
+ return new int[] { gidStart, gidEnd };
+ }
+
+ /**
+ * Obtain interval, filled into first two elements of specified array, or returning new array.
+ * @param interval an array of length two or greater or null
+ * @return interval as a pair of integers, filled into specified array
+ */
+ public int[] getInterval ( int[] interval ) {
+ if ( ( interval == null ) || ( interval.length != 2 ) ) {
+ throw new IllegalArgumentException();
+ } else {
+ interval[0] = gidStart;
+ interval[1] = gidEnd;
+ }
+ return interval;
+ }
+
+ /** @return length of interval */
+ public int getLength() {
+ return gidStart - gidEnd;
+ }
+
+ }
+
+}
// CSOFF: LineLengthCheck
/**
- * The <code>GlyphPositioning</code> interface is implemented by a font related object
+ * The <code>GlyphPositioning</code> interface is implemented by a glyph positioning subtable
* that supports the determination of glyph positioning information based on script and
* language of the corresponding character content.
* @author Glenn Adams
public interface GlyphPositioning {
/**
- * Perform glyph positioning.
- * @param gs sequence to map to output glyph sequence
- * @param script the script associated with the characters corresponding to the glyph sequence
- * @param language the language associated with the characters corresponding to the glyph sequence
- * @return array (sequence) of pairs of position [DX,DY] offsets, one pair for each element of
- * glyph sequence, or null if no non-zero offset applies
+ * Perform glyph positioning at the current index, mutating the positioning state object as required.
+ * Only the context associated with the current index is processed.
+ * @param ps glyph positioning state object
+ * @return true if the glyph subtable applies, meaning that the current context matches the
+ * associated input context glyph coverage table; note that returning true does not mean any position
+ * adjustment occurred; it only means that no further glyph subtables for the current lookup table
+ * should be applied.
*/
- int[] position ( GlyphSequence gs, String script, String language );
+ boolean position ( GlyphPositioningState ps );
}
--- /dev/null
+/*
+ * 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.fonts;
+
+// CSOFF: LineLengthCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>GlyphPositioningState</code> implements an state object used during glyph positioning
+ * processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphPositioningState extends GlyphProcessingState {
+
+ /** font size */
+ private int fontSize;
+ /** default advancements */
+ private int[] widths;
+ /** current adjustments */
+ private int[][] adjustments;
+ /** if true, then some adjustment was applied */
+ private boolean adjusted;
+
+ /**
+ * Construct glyph positioning state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param fontSize font size (in micropoints)
+ * @param widths array of design advancements (in glyph index order)
+ * @param adjustments positioning adjustments to which positioning is applied
+ * @param sct script context tester (or null)
+ */
+ public GlyphPositioningState ( GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ super ( gs, script, language, feature, sct );
+ this.fontSize = fontSize;
+ this.widths = widths;
+ this.adjustments = adjustments;
+ }
+
+ /**
+ * Construct glyph positioning state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param ps existing positioning state to copy from
+ */
+ public GlyphPositioningState ( GlyphPositioningState ps ) {
+ super ( ps );
+ this.fontSize = ps.fontSize;
+ this.widths = ps.widths;
+ this.adjustments = ps.adjustments;
+ }
+
+ /**
+ * Obtain design advancement (width) of glyph at specified index.
+ * @param gi glyph index
+ * @return design advancement, or zero if glyph index is not present
+ */
+ public int getWidth ( int gi ) {
+ if ( ( widths != null ) && ( gi < widths.length ) ) {
+ return widths [ gi ];
+ } else {
+ return 0;
+ }
+ }
+
+ /**
+ * Perform adjustments at current position index.
+ * @param v value containing adjustments
+ * @return true if a non-zero adjustment was made
+ */
+ public boolean adjust ( GlyphPositioningTable.Value v ) {
+ return adjust ( v, 0 );
+ }
+
+ /**
+ * Perform adjustments at specified offset from current position index.
+ * @param v value containing adjustments
+ * @param offset from current position index
+ * @return true if a non-zero adjustment was made
+ */
+ public boolean adjust ( GlyphPositioningTable.Value v, int offset ) {
+ assert v != null;
+ if ( ( index + offset ) < indexLast ) {
+ return v.adjust ( adjustments [ index + offset ], fontSize );
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Obtain current adjustments at current position index.
+ * @return array of adjustments (int[4]) at current position
+ */
+ public int[] getAdjustment() {
+ return getAdjustment ( 0 );
+ }
+
+ /**
+ * Obtain current adjustments at specified offset from current position index.
+ * @param offset from current position index
+ * @return array of adjustments (int[4]) at specified offset
+ * @throws IndexOutOfBoundsException if offset is invalid
+ */
+ public int[] getAdjustment ( int offset ) throws IndexOutOfBoundsException {
+ if ( ( index + offset ) < indexLast ) {
+ return adjustments [ index + offset ];
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Apply positioning subtable to current state at current position (only),
+ * resulting in the consumption of zero or more input glyphs.
+ * @param st the glyph positioning subtable to apply
+ * @return true if subtable applied, or false if it did not (e.g., its
+ * input coverage table did not match current input context)
+ */
+ public boolean apply ( GlyphPositioningSubtable st ) {
+ assert st != null;
+ updateSubtableState ( st );
+ boolean applied = st.position ( this );
+ resetSubtableState();
+ return applied;
+ }
+
+ /**
+ * Apply a sequence of matched rule lookups to the <code>nig</code> input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by <code>nig</code> are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply ( GlyphTable.RuleLookup[] lookups, int nig ) {
+ if ( ( lookups != null ) && ( lookups.length > 0 ) ) {
+ // apply each rule lookup to extracted input glyph array
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ GlyphTable.RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ GlyphTable.LookupTable lt = l.getLookup();
+ if ( lt != null ) {
+ // perform positioning on a copy of previous state
+ GlyphPositioningState ps = new GlyphPositioningState ( this );
+ // apply lookup table positioning
+ if ( lt.position ( ps, l.getSequenceIndex() ) ) {
+ setAdjusted ( true );
+ }
+ }
+ }
+ }
+ consume ( nig );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph.
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ }
+
+ /**
+ * Set adjusted state, used to record effect of non-zero adjustment.
+ * @param adjusted true if to set adjusted state, otherwise false to
+ * clear adjusted state
+ */
+ public void setAdjusted ( boolean adjusted ) {
+ this.adjusted = adjusted;
+ }
+
+ /**
+ * Get adjusted state.
+ * @return adjusted true if some non-zero adjustment occurred and
+ * was recorded by {@link #setAdjusted}; otherwise, false.
+ */
+ public boolean getAdjusted() {
+ return adjusted;
+ }
+
+}
package org.apache.fop.fonts;
+import java.util.List;
+
// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
/**
* The <code>GlyphPositioningSubtable</code> implements an abstract base of a glyph subtable,
}
/** {@inheritDoc} */
- public int[] position ( GlyphSequence gs, String script, String language ) {
- if ( gs == null ) {
- throw new IllegalArgumentException ( "invalid glyph sequence: must not be null" );
- } else {
- return null;
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphPositioningSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ return false;
+ }
+
+ /**
+ * Apply positioning using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (no adjustments and advance).
+ * If <code>sequenceIndex</code> is non-negative, then apply subtables only when current position
+ * matches <code>sequenceIndex</code> in relation to the starting position. Furthermore, upon
+ * successful application at <code>sequenceIndex</code>, then discontinue processing the remaining
+ * @param ps positioning state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return true if a non-zero adjustment occurred
+ */
+ static final boolean position ( GlyphPositioningState ps, GlyphPositioningSubtable[] sta, int sequenceIndex ) {
+ int sequenceStart = ps.getPosition();
+ boolean appliedOneShot = false;
+ while ( ps.hasNext() ) {
+ boolean applied = false;
+ if ( ! appliedOneShot && ps.maybeApplicable() ) {
+ for ( int i = 0, n = sta.length; ! applied && ( i < n ); i++ ) {
+ if ( sequenceIndex < 0 ) {
+ applied = ps.apply ( sta [ i ] );
+ } else if ( ps.getPosition() == ( sequenceStart + sequenceIndex ) ) {
+ applied = ps.apply ( sta [ i ] );
+ if ( applied ) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if ( ! applied || ! ps.didConsume() ) {
+ ps.applyDefault();
+ }
+ ps.next();
}
+ return ps.getAdjusted();
+ }
+
+ static final boolean position ( GlyphSequence gs, String script, String language, String feature, int fontSize, GlyphPositioningSubtable[] sta, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ return position ( new GlyphPositioningState ( gs, script, language, feature, fontSize, widths, adjustments, sct ), sta, -1 );
}
}
package org.apache.fop.fonts;
+import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
// CSOFF: LineLengthCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
/**
* The <code>GlyphPositioningTable</code> class is a glyph table that implements
* <code>GlyphPositioning</code> functionality.
* @author Glenn Adams
*/
-public class GlyphPositioningTable extends GlyphTable implements GlyphPositioning {
+public class GlyphPositioningTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphPositioningTable.class); // CSOK: ConstantNameCheck
/** single positioning subtable type */
public static final int GPOS_LOOKUP_TYPE_SINGLE = 1;
public static final int GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE = 5;
/** mark to mark positioning subtable type */
public static final int GPOS_LOOKUP_TYPE_MARK_TO_MARK = 6;
- /** context positioning subtable type */
- public static final int GPOS_LOOKUP_TYPE_CONTEXT = 7;
- /** chained context positioning subtable type */
- public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXT = 8;
+ /** contextual positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_CONTEXTUAL = 7;
+ /** chained contextual positioning subtable type */
+ public static final int GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL = 8;
/** extension positioning subtable type */
public static final int GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING = 9;
/**
* Instantiate a <code>GlyphPositioningTable</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 GlyphPositioningTable ( Map lookups, List subtables ) {
- super ( lookups );
+ public GlyphPositioningTable ( GlyphDefinitionTable gdef, Map lookups, List subtables ) {
+ super ( gdef, lookups );
if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
throw new IllegalArgumentException ( "subtables must be non-empty" );
} else {
throw new IllegalArgumentException ( "subtable must be a glyph positioning subtable" );
}
}
+ freezeSubtables();
}
}
t = GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
} else if ( "marktomark".equals ( s ) ) {
t = GPOS_LOOKUP_TYPE_MARK_TO_MARK;
- } else if ( "context".equals ( s ) ) {
- t = GPOS_LOOKUP_TYPE_CONTEXT;
- } else if ( "chainedcontext".equals ( s ) ) {
- t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXT;
+ } else if ( "contextual".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ } else if ( "chainedcontextual".equals ( s ) ) {
+ t = GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
} else if ( "extensionpositioning".equals ( s ) ) {
t = GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING;
} else {
case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
tn = "marktomark";
break;
- case GPOS_LOOKUP_TYPE_CONTEXT:
- tn = "context";
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ tn = "contextual";
break;
- case GPOS_LOOKUP_TYPE_CHAINED_CONTEXT:
- tn = "chainedcontext";
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ tn = "chainedcontextual";
break;
case GPOS_LOOKUP_TYPE_EXTENSION_POSITIONING:
tn = "extensionpositioning";
* @param entries subtable entries
* @return a glyph subtable instance
*/
+ public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ GlyphSubtable st = null;
+ switch ( type ) {
+ case GPOS_LOOKUP_TYPE_SINGLE:
+ st = SingleSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_PAIR:
+ st = PairSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CURSIVE:
+ st = CursiveSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_BASE:
+ st = MarkToBaseSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE:
+ st = MarkToLigatureSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_MARK_TO_MARK:
+ st = MarkToMarkSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CONTEXTUAL:
+ st = ContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ case GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ st = ChainedContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
+ break;
+ default:
+ break;
+ }
+ return st;
+ }
+
+ /**
+ * Create a positioning subtable according to the specified arguments.
+ * @param type subtable type
+ * @param id subtable identifier
+ * @param sequence subtable sequence
+ * @param flags subtable flags
+ * @param format subtable format
+ * @param coverage list of coverage table entries
+ * @param entries subtable entries
+ * @return a glyph subtable instance
+ */
public static GlyphSubtable createSubtable ( int type, String id, int sequence, int flags, int format, List coverage, List entries ) {
- return null;
+ return createSubtable ( type, id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ), entries );
+ }
+
+ /**
+ * Perform positioning processing using all matching lookups.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments ) {
+ Map/*<LookupSpec,List<LookupTable>>*/ lookups = matchLookups ( script, language, "*" );
+ if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
+ ScriptProcessor sp = ScriptProcessor.getInstance ( script );
+ return sp.position ( this, gs, script, language, fontSize, lookups, widths, adjustments );
+ } else {
+ return false;
+ }
+ }
+
+ private abstract static class SingleSubtable extends GlyphPositioningSubtable {
+ SingleSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_SINGLE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof SingleSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ Value v = getValue ( ci, gi );
+ if ( v != null ) {
+ if ( ps.adjust(v) ) {
+ ps.setAdjusted ( true );
+ }
+ ps.consume(1);
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain positioning value for coverage index.
+ * @param ci coverage index
+ * @param gi input glyph index
+ * @return positioning value or null if none applies
+ */
+ public abstract Value getValue ( int ci, int gi );
+ static GlyphPositioningSubtable 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 Value value;
+ 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() {
+ if ( value != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( value );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Value getValue ( int ci, int gi ) {
+ if ( ( value != null ) && ( ci <= ciMax ) ) {
+ return value;
+ } else {
+ return null;
+ }
+ }
+ private void populate ( List entries ) {
+ if ( ( entries == null ) || ( entries.size() != 1 ) ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null and contain exactly one entry" );
+ } else {
+ Value v;
+ Object o = entries.get(0);
+ if ( o instanceof Value ) {
+ v = (Value) o;
+ } else {
+ throw new IllegalArgumentException ( "illegal entries entry, must be Value, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ }
+ assert this.value == null;
+ this.value = v;
+ this.ciMax = getCoverageSize() - 1;
+ }
+ }
+ }
+
+ private static class SingleSubtableFormat2 extends SingleSubtable {
+ private Value[] values;
+ 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() {
+ if ( values != null ) {
+ List entries = new ArrayList ( values.length );
+ for ( int i = 0, n = values.length; i < n; i++ ) {
+ entries.add ( values[i] );
+ }
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Value getValue ( int ci, int gi ) {
+ if ( ( values != null ) && ( ci < values.length ) ) {
+ return values [ ci ];
+ } else {
+ return null;
+ }
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof Value[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, single entry must be a Value[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ Value[] va = (Value[]) o;
+ if ( va.length != getCoverageSize() ) {
+ throw new IllegalArgumentException ( "illegal values array, " + entries.size() + " values present, but requires " + getCoverageSize() + " values" );
+ } else {
+ assert this.values == null;
+ this.values = va;
+ }
+ }
+ }
+ }
+ }
+
+ private abstract static class PairSubtable extends GlyphPositioningSubtable {
+ PairSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_PAIR;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof PairSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(0), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] counts = ps.getGlyphsAvailable ( 0 );
+ int nga = counts[0];
+ if ( nga > 1 ) {
+ int[] iga = ps.getGlyphs ( 0, 2, null, counts );
+ if ( ( iga != null ) && ( iga.length == 2 ) ) {
+ PairValues pv = getPairValues ( ci, iga[0], iga[1] );
+ if ( pv != null ) {
+ Value v1 = pv.getValue1();
+ if ( v1 != null ) {
+ if ( ps.adjust(v1, 0) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ Value v2 = pv.getValue2();
+ if ( v2 != null ) {
+ if ( ps.adjust(v2, 1) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume ( counts[0] + counts[1] );
+ }
+ }
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain associated pair values.
+ * @param ci coverage index
+ * @param gi1 first input glyph index
+ * @param gi2 second input glyph index
+ * @return pair values or null if none applies
+ */
+ public abstract PairValues getPairValues ( int ci, int gi1, int gi2 );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new PairSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new PairSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class PairSubtableFormat1 extends PairSubtable {
+ private PairValues[][] pvm; // pair values matrix
+ PairSubtableFormat1 ( 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 ( pvm != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( pvm );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public PairValues getPairValues ( int ci, int gi1, int gi2 ) {
+ if ( ( pvm != null ) && ( ci < pvm.length ) ) {
+ PairValues[] pvt = pvm [ ci ];
+ for ( int i = 0, n = pvt.length; i < n; i++ ) {
+ PairValues pv = pvt [ i ];
+ if ( pv != null ) {
+ int g = pv.getGlyph();
+ if ( g < gi2 ) {
+ continue;
+ } else if ( g == gi2 ) {
+ return pv;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof PairValues[][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first (and only) entry must be a PairValues[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ pvm = (PairValues[][]) o;
+ }
+ }
+ }
+ }
+
+ private static class PairSubtableFormat2 extends PairSubtable {
+ private GlyphClassTable cdt1; // class def table 1
+ private GlyphClassTable cdt2; // class def table 2
+ private int nc1; // class 1 count
+ private int nc2; // class 2 count
+ private PairValues[][] pvm; // pair values matrix
+ PairSubtableFormat2 ( 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 ( pvm != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( cdt1 );
+ entries.add ( cdt2 );
+ entries.add ( Integer.valueOf ( nc1 ) );
+ entries.add ( Integer.valueOf ( nc2 ) );
+ entries.add ( pvm );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public PairValues getPairValues ( int ci, int gi1, int gi2 ) {
+ if ( pvm != null ) {
+ int c1 = cdt1.getClassIndex ( gi1, 0 );
+ if ( ( c1 < nc1 ) && ( c1 < pvm.length ) ) {
+ PairValues[] pvt = pvm [ c1 ];
+ if ( pvt != null ) {
+ int c2 = cdt2.getClassIndex ( gi2, 0 );
+ if ( ( c2 < nc2 ) && ( c2 < pvt.length ) ) {
+ return pvt [ c2 ];
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt1 = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "illegal entries, second entry must be an GlyphClassTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ cdt2 = (GlyphClassTable) o;
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, third entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nc1 = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nc2 = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof PairValues[][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fifth entry must be a PairValues[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ pvm = (PairValues[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class CursiveSubtable extends GlyphPositioningSubtable {
+ CursiveSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CURSIVE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof CursiveSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(0), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] counts = ps.getGlyphsAvailable ( 0 );
+ int nga = counts[0];
+ if ( nga > 1 ) {
+ int[] iga = ps.getGlyphs ( 0, 2, null, counts );
+ if ( ( iga != null ) && ( iga.length == 2 ) ) {
+ // int gi1 = gi;
+ int ci1 = ci;
+ int gi2 = iga [ 1 ];
+ int ci2 = getCoverageIndex ( gi2 );
+ Anchor[] aa = getExitEntryAnchors ( ci1, ci2 );
+ if ( aa != null ) {
+ Anchor exa = aa [ 0 ];
+ Anchor ena = aa [ 1 ];
+ // int exw = ps.getWidth ( gi1 );
+ int enw = ps.getWidth ( gi2 );
+ if ( ( exa != null ) && ( ena != null ) ) {
+ Value v = ena.getAlignmentAdjustment ( exa );
+ v.adjust ( - enw, 0, 0, 0 );
+ if ( ps.adjust ( v ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ // consume only first glyph of exit/entry glyph pair
+ ps.consume ( 1 );
+ }
+ }
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain exit anchor for first glyph with coverage index <code>ci1</code> and entry anchor for second
+ * glyph with coverage index <code>ci2</code>.
+ * @param ci1 coverage index of first glyph (may be negative)
+ * @param ci2 coverage index of second glyph (may be negative)
+ * @return array of two anchors or null if either coverage index is negative or corresponding anchor is
+ * missing, where the first entry is the exit anchor of the first glyph and the second entry is the
+ * entry anchor of the second glyph
+ */
+ public abstract Anchor[] getExitEntryAnchors ( int ci1, int ci2 );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new CursiveSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class CursiveSubtableFormat1 extends CursiveSubtable {
+ private Anchor[] aa; // anchor array, where even entries are entry anchors, and odd entries are exit anchors
+ CursiveSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( aa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( aa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor[] getExitEntryAnchors ( int ci1, int ci2 ) {
+ if ( ( ci1 >= 0 ) && ( ci2 >= 0 ) ) {
+ int ai1 = ( ci1 * 2 ) + 1; // ci1 denotes glyph with exit anchor
+ int ai2 = ( ci2 * 2 ) + 0; // ci2 denotes glyph with entry anchor
+ if ( ( aa != null ) && ( ai1 < aa.length ) && ( ai2 < aa.length ) ) {
+ Anchor exa = aa [ ai1 ];
+ Anchor ena = aa [ ai2 ];
+ if ( ( exa != null ) && ( ena != null ) ) {
+ return new Anchor[] { exa, ena };
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof Anchor[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first (and only) entry must be a Anchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else if ( ( ( (Anchor[]) o ) . length % 2 ) != 0 ) {
+ throw new IllegalArgumentException ( "illegal entries, Anchor[] array must have an even number of entries, but has: " + ( (Anchor[]) o ) . length );
+ } else {
+ aa = (Anchor[]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToBaseSubtable extends GlyphPositioningSubtable {
+ MarkToBaseSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_BASE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToBaseSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int giMark = ps.getGlyph(), ciMark;
+ if ( ( ciMark = getCoverageIndex ( giMark ) ) < 0 ) {
+ return false;
+ } else {
+ MarkAnchor ma = getMarkAnchor ( ciMark, giMark );
+ if ( ma != null ) {
+ for ( int i = 0, n = ps.getPosition(); i < n; i++ ) {
+ int gi = ps.getGlyph ( - ( i + 1 ) );
+ if ( ps.isMark ( gi ) ) {
+ continue;
+ } else {
+ Anchor a = getBaseAnchor ( gi, ma.getMarkClass() );
+ if ( a != null ) {
+ Value v = a.getAlignmentAdjustment ( ma );
+ // start experimental fix for END OF AYAH in Lateef/Scheherazade
+ int[] aa = ps.getAdjustment();
+ if ( aa[2] == 0 ) {
+ v.adjust ( 0, 0, - ps.getWidth ( giMark ), 0 );
+ }
+ // end experimental fix for END OF AYAH in Lateef/Scheherazade
+ if ( ps.adjust ( v ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ break;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor ( int ciMark, int giMark );
+ /**
+ * Obtain anchor associated with base glyph index and mark class.
+ * @param giBase input glyph index of base glyph
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getBaseAnchor ( int giBase, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToBaseSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToBaseSubtableFormat1 extends MarkToBaseSubtable {
+ private GlyphCoverageTable bct; // base coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][] bam; // base anchor matrix, ordered by base coverage index, then by mark class
+ MarkToBaseSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( ( bct != null ) && ( maa != null ) && ( nmc > 0 ) && ( bam != null ) ) {
+ List entries = new ArrayList ( 4 );
+ entries.add ( bct );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( maa );
+ entries.add ( bam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor ( int ciMark, int giMark ) {
+ if ( ( maa != null ) && ( ciMark < maa.length ) ) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getBaseAnchor ( int giBase, int markClass ) {
+ int ciBase;
+ if ( ( bct != null ) && ( ( ciBase = bct.getCoverageIndex ( giBase ) ) >= 0 ) ) {
+ if ( ( bam != null ) && ( ciBase < bam.length ) ) {
+ Anchor[] ba = bam [ ciBase ];
+ if ( ( ba != null ) && ( markClass < ba.length ) ) {
+ return ba [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 4 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 4 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ bct = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, third entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Anchor[][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fourth entry must be a Anchor[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ bam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToLigatureSubtable extends GlyphPositioningSubtable {
+ MarkToLigatureSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_LIGATURE;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToLigatureSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int giMark = ps.getGlyph(), ciMark;
+ if ( ( ciMark = getCoverageIndex ( giMark ) ) < 0 ) {
+ return false;
+ } else {
+ MarkAnchor ma = getMarkAnchor ( ciMark, giMark );
+ int mxc = getMaxComponentCount();
+ if ( ma != null ) {
+ for ( int i = 0, n = ps.getPosition(); i < n; i++ ) {
+ int gi = ps.getGlyph ( - ( i + 1 ) );
+ if ( ps.isMark ( gi ) ) {
+ continue;
+ } else {
+ Anchor a = getLigatureAnchor ( gi, mxc, i, ma.getMarkClass() );
+ if ( a != null ) {
+ if ( ps.adjust ( a.getAlignmentAdjustment ( ma ) ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ break;
+ }
+ }
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain mark anchor associated with mark coverage index.
+ * @param ciMark coverage index
+ * @param giMark input glyph index of mark glyph
+ * @return mark anchor or null if none applies
+ */
+ public abstract MarkAnchor getMarkAnchor ( int ciMark, int giMark );
+ /**
+ * Obtain maximum component count.
+ * @return maximum component count (>=0)
+ */
+ public abstract int getMaxComponentCount();
+ /**
+ * Obtain anchor associated with ligature glyph index and mark class.
+ * @param giLig input glyph index of ligature glyph
+ * @param maxComponents maximum component count
+ * @param component component number (0...maxComponents-1)
+ * @param markClass class number of mark glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getLigatureAnchor ( int giLig, int maxComponents, int component, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToLigatureSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToLigatureSubtableFormat1 extends MarkToLigatureSubtable {
+ private GlyphCoverageTable lct; // ligature coverage table
+ private int nmc; // mark class count
+ private int mxc; // maximum ligature component count
+ private MarkAnchor[] maa; // mark anchor array, ordered by mark coverage index
+ private Anchor[][][] lam; // ligature anchor matrix, ordered by ligature coverage index, then ligature component, then mark class
+ MarkToLigatureSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( lam != null ) {
+ List entries = new ArrayList ( 5 );
+ entries.add ( lct );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( Integer.valueOf ( mxc ) );
+ entries.add ( maa );
+ entries.add ( lam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMarkAnchor ( int ciMark, int giMark ) {
+ if ( ( maa != null ) && ( ciMark < maa.length ) ) {
+ return maa [ ciMark ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public int getMaxComponentCount() {
+ return mxc;
+ }
+ /** {@inheritDoc} */
+ public Anchor getLigatureAnchor ( int giLig, int maxComponents, int component, int markClass ) {
+ int ciLig;
+ if ( ( lct != null ) && ( ( ciLig = lct.getCoverageIndex ( giLig ) ) >= 0 ) ) {
+ if ( ( lam != null ) && ( ciLig < lam.length ) ) {
+ Anchor[][] lcm = lam [ ciLig ];
+ if ( component < maxComponents ) {
+ Anchor[] la = lcm [ component ];
+ if ( ( la != null ) && ( markClass < la.length ) ) {
+ return la [ markClass ];
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ lct = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, third entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mxc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fourth entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof Anchor[][][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fifth entry must be a Anchor[][][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ lam = (Anchor[][][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class MarkToMarkSubtable extends GlyphPositioningSubtable {
+ MarkToMarkSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_MARK_TO_MARK;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof MarkToMarkSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int giMark1 = ps.getGlyph(), ciMark1;
+ if ( ( ciMark1 = getCoverageIndex ( giMark1 ) ) < 0 ) {
+ return false;
+ } else {
+ MarkAnchor ma = getMark1Anchor ( ciMark1, giMark1 );
+ if ( ma != null ) {
+ if ( ps.hasPrev() ) {
+ Anchor a = getMark2Anchor ( ps.getGlyph(-1), ma.getMarkClass() );
+ if ( a != null ) {
+ if ( ps.adjust ( a.getAlignmentAdjustment ( ma ) ) ) {
+ ps.setAdjusted ( true );
+ }
+ }
+ ps.consume(1);
+ }
+ }
+ return true;
+ }
+ }
+ /**
+ * Obtain mark 1 anchor associated with mark 1 coverage index.
+ * @param ciMark1 mark 1 coverage index
+ * @param giMark1 input glyph index of mark 1 glyph
+ * @return mark 1 anchor or null if none applies
+ */
+ public abstract MarkAnchor getMark1Anchor ( int ciMark1, int giMark1 );
+ /**
+ * Obtain anchor associated with mark 2 glyph index and mark 1 class.
+ * @param giMark2 input glyph index of mark 2 glyph
+ * @param markClass class number of mark 1 glyph
+ * @return anchor or null if none applies
+ */
+ public abstract Anchor getMark2Anchor ( int giBase, int markClass );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new MarkToMarkSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class MarkToMarkSubtableFormat1 extends MarkToMarkSubtable {
+ private GlyphCoverageTable mct2; // mark 2 coverage table
+ private int nmc; // mark class count
+ private MarkAnchor[] maa; // mark1 anchor array, ordered by mark1 coverage index
+ private Anchor[][] mam; // mark2 anchor matrix, ordered by mark2 coverage index, then by mark1 class
+ MarkToMarkSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( ( mct2 != null ) && ( maa != null ) && ( nmc > 0 ) && ( mam != null ) ) {
+ List entries = new ArrayList ( 4 );
+ entries.add ( mct2 );
+ entries.add ( Integer.valueOf ( nmc ) );
+ entries.add ( maa );
+ entries.add ( mam );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public MarkAnchor getMark1Anchor ( int ciMark1, int giMark1 ) {
+ if ( ( maa != null ) && ( ciMark1 < maa.length ) ) {
+ return maa [ ciMark1 ];
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public Anchor getMark2Anchor ( int giMark2, int markClass ) {
+ int ciMark2;
+ if ( ( mct2 != null ) && ( ( ciMark2 = mct2.getCoverageIndex ( giMark2 ) ) >= 0 ) ) {
+ if ( ( mam != null ) && ( ciMark2 < mam.length ) ) {
+ Anchor[] ma = mam [ ciMark2 ];
+ if ( ( ma != null ) && ( markClass < ma.length ) ) {
+ return ma [ markClass ];
+ }
+ }
+ }
+ return null;
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 4 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 4 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphCoverageTable ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an GlyphCoverageTable, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mct2 = (GlyphCoverageTable) o;
+ }
+ if ( ( ( o = entries.get(1) ) == null ) || ! ( o instanceof Integer ) ) {
+ throw new IllegalArgumentException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ nmc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof MarkAnchor[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, third entry must be a MarkAnchor[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ maa = (MarkAnchor[]) o;
+ }
+ if ( ( ( o = entries.get(3) ) == null ) || ! ( o instanceof Anchor[][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, fourth entry must be a Anchor[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ mam = (Anchor[][]) o;
+ }
+ }
+ }
+ }
+
+ private abstract static class ContextualSubtable extends GlyphPositioningSubtable {
+ ContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] rv = new int[1];
+ RuleLookup[] la = getLookups ( ci, gi, ps, rv );
+ if ( la != null ) {
+ ps.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 ps glyph positioning state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new ContextualSubtableFormat1 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 2 ) {
+ return new ContextualSubtableFormat2 ( id, sequence, flags, format, coverage, entries );
+ } else if ( format == 3 ) {
+ return new ContextualSubtableFormat3 ( id, sequence, flags, format, coverage, entries );
+ } else {
+ throw new UnsupportedOperationException();
+ }
+ }
+ }
+
+ private static class ContextualSubtableFormat1 extends ContextualSubtable {
+ private RuleSet[] rsa; // rule set array, ordered by glyph coverage index
+ ContextualSubtableFormat1 ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage, entries );
+ populate ( entries );
+ }
+ /** {@inheritDoc} */
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ps, iga, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, 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 = ps.getIgnoreDefault();
+ int[] counts = ps.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 = ps.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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ca = cr.getClasses ( cdt.getClassIndex ( gi, ps.getClassMatchSet ( gi ) ) );
+ if ( matches ( ps, cdt, ca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, 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 = ps.getIgnoreDefault();
+ int[] counts = ps.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 = ps.getGlyphs ( offset, ngm, reverse, ignores, null, counts );
+ for ( int k = 0; k < ngm; k++ ) {
+ int gi = ga [ k ];
+ int ms = ps.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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 3 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 3 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] gca = cr.getCoverages();
+ if ( matches ( ps, gca, 0, rv ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ static boolean matches ( GlyphPositioningState ps, 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 = ps.getIgnoreDefault();
+ int[] counts = ps.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 = ps.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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 GlyphPositioningSubtable {
+ ChainedContextualSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ super ( id, sequence, flags, format, coverage );
+ }
+ /** {@inheritDoc} */
+ public int getType() {
+ return GPOS_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
+ }
+ /** {@inheritDoc} */
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof ChainedContextualSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean position ( GlyphPositioningState ps ) {
+ int gi = ps.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] rv = new int[1];
+ RuleLookup[] la = getLookups ( ci, gi, ps, rv );
+ if ( la != null ) {
+ ps.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 ps glyph positioning state
+ * @param rv array of ints used to receive multiple return values, must be of length 1 or greater,
+ * where the first entry is used to return the input sequence length of the matched rule
+ * @return array of rule lookups or null if none applies
+ */
+ public abstract RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv );
+ static GlyphPositioningSubtable create ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage, List entries ) {
+ if ( format == 1 ) {
+ return new 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();
+ }
+ }
}
- /** {@inheritDoc} */
- public int[] position ( GlyphSequence gs, String script, String language ) {
- return null;
+ 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/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedGlyphSequenceRule ) ) {
+ ChainedGlyphSequenceRule cr = (ChainedGlyphSequenceRule) r;
+ int[] iga = cr.getGlyphs ( gi );
+ if ( matches ( ps, iga, 0, rv ) ) {
+ int[] bga = cr.getBacktrackGlyphs();
+ if ( matches ( ps, bga, -1, null ) ) {
+ int[] lga = cr.getLookaheadGlyphs();
+ if ( matches ( ps, lga, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, int[] glyphs, int offset, int[] rv ) {
+ return ContextualSubtableFormat1.matches ( ps, glyphs, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedClassSequenceRule ) ) {
+ ChainedClassSequenceRule cr = (ChainedClassSequenceRule) r;
+ int[] ica = cr.getClasses ( icdt.getClassIndex ( gi, ps.getClassMatchSet ( gi ) ) );
+ if ( matches ( ps, icdt, ica, 0, rv ) ) {
+ int[] bca = cr.getBacktrackClasses();
+ if ( matches ( ps, bcdt, bca, -1, null ) ) {
+ int[] lca = cr.getLookaheadClasses();
+ if ( matches ( ps, lcdt, lca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, GlyphClassTable cdt, int[] classes, int offset, int[] rv ) {
+ return ContextualSubtableFormat2.matches ( ps, cdt, classes, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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 IllegalArgumentException ( "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 IllegalArgumentException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ /** {@inheritDoc} */
+ public RuleLookup[] getLookups ( int ci, int gi, GlyphPositioningState ps, int[] rv ) {
+ assert ps != 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ if ( ( r != null ) && ( r instanceof ChainedCoverageSequenceRule ) ) {
+ ChainedCoverageSequenceRule cr = (ChainedCoverageSequenceRule) r;
+ GlyphCoverageTable[] igca = cr.getCoverages();
+ if ( matches ( ps, igca, 0, rv ) ) {
+ GlyphCoverageTable[] bgca = cr.getBacktrackCoverages();
+ if ( matches ( ps, bgca, -1, null ) ) {
+ GlyphCoverageTable[] lgca = cr.getLookaheadCoverages();
+ if ( matches ( ps, lgca, rv[0], null ) ) {
+ return r.getLookups();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+ private boolean matches ( GlyphPositioningState ps, GlyphCoverageTable[] gca, int offset, int[] rv ) {
+ return ContextualSubtableFormat3.matches ( ps, gca, offset, rv );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
+ }
+ }
+
+ /**
+ * The <code>DeviceTable</code> class implements a positioning device table record, comprising
+ * adjustments to be made to scaled design units according to the scaled size.
+ */
+ public static class DeviceTable {
+
+ private final int startSize;
+ private final int endSize;
+ private final int[] deltas;
+
+ /**
+ * Instantiate a DeviceTable.
+ * @param startSize the
+ * @param endSize the ending (scaled) size
+ * @param deltas adjustments for each scaled size
+ */
+ public DeviceTable ( int startSize, int endSize, int[] deltas ) {
+ assert startSize >= 0;
+ assert startSize <= endSize;
+ assert deltas != null;
+ assert deltas.length == ( endSize - startSize ) + 1;
+ this.startSize = startSize;
+ this.endSize = endSize;
+ this.deltas = deltas;
+ }
+
+ /** @return the start size */
+ public int getStartSize() {
+ return startSize;
+ }
+
+ /** @return the end size */
+ public int getEndSize() {
+ return endSize;
+ }
+
+ /** @return the deltas */
+ public int[] getDeltas() {
+ return deltas;
+ }
+
+ /**
+ * Find device adjustment.
+ * @param fontSize the font size to search for
+ * @return an adjustment if font size matches an entry
+ * @asf.todo at present, assumes that 1 device unit equals one point
+ */
+ public int findAdjustment ( int fontSize ) {
+ int fs = fontSize / 1000;
+ if ( fs < startSize ) {
+ return 0;
+ } else if ( fs <= endSize ) {
+ return deltas [ fs - startSize ] * 1000;
+ } else {
+ return 0;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ start = " + startSize + ", end = " + endSize + ", deltas = " + Arrays.toString ( deltas ) + "}";
+ }
+
+ }
+
+ /**
+ * The <code>Value</code> class implements a positioning value record, comprising placement
+ * and advancement information in X and Y axes, and optionally including device data used to
+ * perform device (grid-fitted) specific fine grain adjustments.
+ */
+ public static class Value {
+
+ /** X_PLACEMENT value format flag */
+ public static final int X_PLACEMENT = 0x0001;
+ /** Y_PLACEMENT value format flag */
+ public static final int Y_PLACEMENT = 0x0002;
+ /** X_ADVANCE value format flag */
+ public static final int X_ADVANCE = 0x0004;
+ /** Y_ADVANCE value format flag */
+ public static final int Y_ADVANCE = 0x0008;
+ /** X_PLACEMENT_DEVICE value format flag */
+ public static final int X_PLACEMENT_DEVICE = 0x0010;
+ /** Y_PLACEMENT_DEVICE value format flag */
+ public static final int Y_PLACEMENT_DEVICE = 0x0020;
+ /** X_ADVANCE_DEVICE value format flag */
+ public static final int X_ADVANCE_DEVICE = 0x0040;
+ /** Y_ADVANCE_DEVICE value format flag */
+ public static final int Y_ADVANCE_DEVICE = 0x0080;
+
+ /** X_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_X_PLACEMENT = 0;
+ /** Y_PLACEMENT value index (within adjustments arrays) */
+ public static final int IDX_Y_PLACEMENT = 1;
+ /** X_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_X_ADVANCE = 2;
+ /** Y_ADVANCE value index (within adjustments arrays) */
+ public static final int IDX_Y_ADVANCE = 3;
+
+ private int xPlacement; // x placement
+ private int yPlacement; // y placement
+ private int xAdvance; // x advance
+ private int yAdvance; // y advance
+ private final DeviceTable xPlaDevice; // x placement device table
+ private final DeviceTable yPlaDevice; // y placement device table
+ private final DeviceTable xAdvDevice; // x advance device table
+ private final DeviceTable yAdvDevice; // x advance device table
+
+ /**
+ * Instantiate a Value.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ * @param xPlaDevice the x placement device table or null
+ * @param yPlaDevice the y placement device table or null
+ * @param xAdvDevice the x advance device table or null
+ * @param yAdvDevice the y advance device table or null
+ */
+ public Value ( int xPlacement, int yPlacement, int xAdvance, int yAdvance, DeviceTable xPlaDevice, DeviceTable yPlaDevice, DeviceTable xAdvDevice, DeviceTable yAdvDevice ) {
+ this.xPlacement = xPlacement;
+ this.yPlacement = yPlacement;
+ this.xAdvance = xAdvance;
+ this.yAdvance = yAdvance;
+ this.xPlaDevice = xPlaDevice;
+ this.yPlaDevice = yPlaDevice;
+ this.xAdvDevice = xAdvDevice;
+ this.yAdvDevice = yAdvDevice;
+ }
+
+ /** @return the x placement */
+ public int getXPlacement() {
+ return xPlacement;
+ }
+
+ /** @return the y placement */
+ public int getYPlacement() {
+ return yPlacement;
+ }
+
+ /** @return the x advance */
+ public int getXAdvance() {
+ return xAdvance;
+ }
+
+ /** @return the y advance */
+ public int getYAdvance() {
+ return yAdvance;
+ }
+
+ /** @return the x placement device table */
+ public DeviceTable getXPlaDevice() {
+ return xPlaDevice;
+ }
+
+ /** @return the y placement device table */
+ public DeviceTable getYPlaDevice() {
+ return yPlaDevice;
+ }
+
+ /** @return the x advance device table */
+ public DeviceTable getXAdvDevice() {
+ return xAdvDevice;
+ }
+
+ /** @return the y advance device table */
+ public DeviceTable getYAdvDevice() {
+ return yAdvDevice;
+ }
+
+ /**
+ * Apply value to specific adjustments to without use of device table adjustments.
+ * @param xPlacement the x placement or zero
+ * @param yPlacement the y placement or zero
+ * @param xAdvance the x advance or zero
+ * @param yAdvance the y advance or zero
+ */
+ public void adjust ( int xPlacement, int yPlacement, int xAdvance, int yAdvance ) {
+ this.xPlacement += xPlacement;
+ this.yPlacement += yPlacement;
+ this.xAdvance += xAdvance;
+ this.yAdvance += yAdvance;
+ }
+
+ /**
+ * Apply value to adjustments using font size for device table adjustments.
+ * @param adjustments array of four integers containing X,Y placement and X,Y advance adjustments
+ * @param fontSize font size for device table adjustments
+ * @return true if some adjustment was made
+ */
+ public boolean adjust ( int[] adjustments, int fontSize ) {
+ boolean adjust = false;
+ int dv;
+ if ( ( dv = xPlacement ) != 0 ) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = yPlacement ) != 0 ) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = xAdvance ) != 0 ) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if ( ( dv = yAdvance ) != 0 ) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ if ( fontSize != 0 ) {
+ DeviceTable dt;
+ if ( ( dt = xPlaDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_X_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = yPlaDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_Y_PLACEMENT ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = xAdvDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_X_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ if ( ( dt = yAdvDevice ) != null ) {
+ if ( ( dv = dt.findAdjustment ( fontSize ) ) != 0 ) {
+ adjustments [ IDX_Y_ADVANCE ] += dv;
+ adjust = true;
+ }
+ }
+ }
+ return adjust;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append ( "{ " );
+ if ( xPlacement != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlacement = " + xPlacement );
+ }
+ if ( yPlacement != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "yPlacement = " + yPlacement );
+ }
+ if ( xAdvance != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvance = " + xAdvance );
+ }
+ if ( yAdvance != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "yAdvance = " + yAdvance );
+ }
+ if ( xPlaDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlaDevice = " + xPlaDevice );
+ }
+ if ( yPlaDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xPlaDevice = " + yPlaDevice );
+ }
+ if ( xAdvDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvDevice = " + xAdvDevice );
+ }
+ if ( yAdvDevice != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "xAdvDevice = " + yAdvDevice );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>PairValues</code> class implements a pair value record, comprising a glyph id (or zero)
+ * and two optional positioning values.
+ */
+ public static class PairValues {
+
+ private final int glyph; // glyph id (or 0)
+ private final Value value1; // value for first glyph in pair (or null)
+ private final Value value2; // value for second glyph in pair (or null)
+
+ /**
+ * Instantiate a PairValues.
+ * @param glyph the glyph id (or zero)
+ * @param value1 the value of the first glyph in pair (or null)
+ * @param value2 the value of the second glyph in pair (or null)
+ */
+ public PairValues ( int glyph, Value value1, Value value2 ) {
+ assert glyph >= 0;
+ this.glyph = glyph;
+ this.value1 = value1;
+ this.value2 = value2;
+ }
+
+ /** @return the glyph id */
+ public int getGlyph() {
+ return glyph;
+ }
+
+ /** @return the first value */
+ public Value getValue1() {
+ return value1;
+ }
+
+ /** @return the second value */
+ public Value getValue2() {
+ return value2;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ boolean first = true;
+ sb.append ( "{ " );
+ if ( glyph != 0 ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "glyph = " + glyph );
+ }
+ if ( value1 != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "value1 = " + value1 );
+ }
+ if ( value2 != null ) {
+ if ( ! first ) {
+ sb.append ( ", " );
+ } else {
+ first = false;
+ }
+ sb.append ( "value2 = " + value2 );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>Anchor</code> class implements a anchor record, comprising an X,Y coordinate pair,
+ * an optional anchor point index (or -1), and optional X or Y device tables (or null if absent).
+ */
+ public static class Anchor {
+
+ private final int x; // xCoordinate (in design units)
+ private final int y; // yCoordinate (in design units)
+ private final int anchorPoint; // anchor point index (or -1)
+ private final DeviceTable xDevice; // x device table
+ private final DeviceTable yDevice; // y device table
+
+ /**
+ * Instantiate an Anchor (format 1).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ */
+ public Anchor ( int x, int y ) {
+ this ( x, y, -1, null, null );
+ }
+
+ /**
+ * Instantiate an Anchor (format 2).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param anchorPoint anchor index (or -1)
+ */
+ public Anchor ( int x, int y, int anchorPoint ) {
+ this ( x, y, anchorPoint, null, null );
+ }
+
+ /**
+ * Instantiate an Anchor (format 3).
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @param xDevice the x device table (or null if not present)
+ * @param yDevice the y device table (or null if not present)
+ */
+ public Anchor ( int x, int y, DeviceTable xDevice, DeviceTable yDevice ) {
+ this ( x, y, -1, xDevice, yDevice );
+ }
+
+ /**
+ * Instantiate an Anchor based on an existing anchor.
+ * @param a the existing anchor
+ */
+ protected Anchor ( Anchor a ) {
+ this ( a.x, a.y, a.anchorPoint, a.xDevice, a.yDevice );
+ }
+
+ private Anchor ( int x, int y, int anchorPoint, DeviceTable xDevice, DeviceTable yDevice ) {
+ assert ( anchorPoint >= 0 ) || ( anchorPoint == -1 );
+ this.x = x;
+ this.y = y;
+ this.anchorPoint = anchorPoint;
+ this.xDevice = xDevice;
+ this.yDevice = yDevice;
+ }
+
+ /** @return the x coordinate */
+ public int getX() {
+ return x;
+ }
+
+ /** @return the y coordinate */
+ public int getY() {
+ return y;
+ }
+
+ /** @return the anchor point index (or -1 if not specified) */
+ public int getAnchorPoint() {
+ return anchorPoint;
+ }
+
+ /** @return the x device table (or null if not specified) */
+ public DeviceTable getXDevice() {
+ return xDevice;
+ }
+
+ /** @return the y device table (or null if not specified) */
+ public DeviceTable getYDevice() {
+ return yDevice;
+ }
+
+ /**
+ * Obtain adjustment value required to align the specified anchor
+ * with this anchor.
+ * @param a the anchor to align
+ * @return the adjustment value needed to effect alignment
+ */
+ public Value getAlignmentAdjustment ( Anchor a ) {
+ assert a != null;
+ // TODO - handle anchor point
+ // TODO - handle device tables
+ return new Value ( x - a.x, y - a.y, 0, 0, null, null, null, null );
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ [" + x + "," + y + "]" );
+ if ( anchorPoint != -1 ) {
+ sb.append ( ", anchorPoint = " + anchorPoint );
+ }
+ if ( xDevice != null ) {
+ sb.append ( ", xDevice = " + xDevice );
+ }
+ if ( yDevice != null ) {
+ sb.append ( ", yDevice = " + yDevice );
+ }
+ sb.append(" }");
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>MarkAnchor</code> class is a subclass of the <code>Anchor</code> class, adding a mark
+ * class designation.
+ */
+ public static class MarkAnchor extends Anchor {
+
+ private final int markClass; // mark class
+
+ /**
+ * Instantiate a MarkAnchor
+ * @param markClass the mark class
+ * @param a the underlying anchor (whose fields are copied)
+ */
+ public MarkAnchor ( int markClass, Anchor a ) {
+ super ( a );
+ this.markClass = markClass;
+ }
+
+ /** @return the mark class */
+ public int getMarkClass() {
+ return markClass;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ markClass = " + markClass + ", anchor = " + super.toString() + " }";
+ }
+
}
}
--- /dev/null
+/*
+ * 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.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphProcessingState</code> implements a common, base state object used during glyph substitution
+ * and positioning processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphProcessingState {
+
+ /** governing glyph definition table */
+ protected GlyphDefinitionTable gdef;
+ /** governing script */
+ protected String script;
+ /** governing language */
+ protected String language;
+ /** governing feature */
+ protected String feature;
+ /** current input glyph sequence */
+ protected GlyphSequence igs;
+ /** current index in input sequence */
+ protected int index;
+ /** last (maximum) index of input sequence (exclusive) */
+ protected int indexLast;
+ /** consumed, updated after each successful subtable application */
+ protected int consumed;
+ /** lookup flags */
+ protected int flags;
+ /** class match set */
+ protected int classMatchSet;
+ /** script specific context tester or null */
+ protected ScriptContextTester sct;
+ /** glyph context tester or null */
+ protected GlyphContextTester gct;
+ /** ignore base glyph tester */
+ protected GlyphTester ignoreBase;
+ /** ignore ligature glyph tester */
+ protected GlyphTester ignoreLigature;
+ /** ignore mark glyph tester */
+ protected GlyphTester ignoreMark;
+ /** default ignore glyph tester */
+ protected GlyphTester ignoreDefault;
+
+ /**
+ * Construct glyph processing state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param sct script context tester (or null)
+ */
+ protected GlyphProcessingState ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ this.script = script;
+ this.language = language;
+ this.feature = feature;
+ this.igs = gs;
+ this.indexLast = gs.getGlyphCount();
+ this.sct = sct;
+ this.gct = ( sct != null ) ? sct.getTester ( feature ) : null;
+ this.ignoreBase = new GlyphTester() { public boolean test(int gi) { return isBase(gi); } };
+ this.ignoreLigature = new GlyphTester() { public boolean test(int gi) { return isLigature(gi); } };
+ this.ignoreMark = new GlyphTester() { public boolean test(int gi) { return isMark(gi); } };
+ }
+
+ /**
+ * Construct glyph processing state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param s existing processing state to copy from
+ */
+ protected GlyphProcessingState ( GlyphProcessingState s ) {
+ this ( new GlyphSequence ( s.igs ), s.script, s.language, s.feature, s.sct );
+ setPosition ( s.index );
+ }
+
+ /**
+ * Set governing glyph definition table.
+ * @param gdef glyph definition table (or null, to unset)
+ */
+ public void setGDEF ( GlyphDefinitionTable gdef ) {
+ if ( this.gdef == null ) {
+ this.gdef = gdef;
+ } else if ( gdef == null ) {
+ this.gdef = null;
+ }
+ }
+
+ /**
+ * Obtain governing glyph definition table.
+ * @return glyph definition table (or null, to not set)
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
+ /**
+ * Set governing lookup flags
+ * @param flags lookup flags (or zero, to unset)
+ */
+ public void setFlags ( int flags ) {
+ if ( this.flags == 0 ) {
+ this.flags = flags;
+ } else if ( flags == 0 ) {
+ this.flags = 0;
+ }
+ }
+
+ /**
+ * Obtain governing lookup flags.
+ * @return lookup flags (zero may indicate unset or no flags)
+ */
+ public int getFlags() {
+ return flags;
+ }
+
+ /**
+ * Obtain governing class match set.
+ * @param gi glyph index that may be used to determine which match set applies
+ * @return class match set (zero may indicate unset or no set)
+ */
+ public int getClassMatchSet ( int gi ) {
+ return 0;
+ }
+
+ /**
+ * Set default ignore tester.
+ * @param ignoreDefault glyph tester (or null, to unset)
+ */
+ public void setIgnoreDefault ( GlyphTester ignoreDefault ) {
+ if ( this.ignoreDefault == null ) {
+ this.ignoreDefault = ignoreDefault;
+ } else if ( ignoreDefault == null ) {
+ this.ignoreDefault = null;
+ }
+ }
+
+ /**
+ * Obtain governing default ignores tester.
+ * @return default ignores tester
+ */
+ public GlyphTester getIgnoreDefault() {
+ return ignoreDefault;
+ }
+
+ /**
+ * Update glyph subtable specific state. Each time a
+ * different glyph subtable is to be applied, it is used
+ * to update this state prior to application, after which
+ * this state is to be reset.
+ * @param st glyph subtable to use for update
+ */
+ public void updateSubtableState ( GlyphSubtable st ) {
+ setGDEF ( st.getGDEF() );
+ setFlags ( st.getFlags() );
+ setIgnoreDefault ( getIgnoreTester ( flags ) );
+ }
+
+ /**
+ * Reset glyph subtable specific state.
+ */
+ public void resetSubtableState() {
+ setGDEF ( null );
+ setFlags ( 0 );
+ setIgnoreDefault ( null );
+ }
+
+ /**
+ * Obtain current position index in input glyph sequence.
+ * @return current index
+ */
+ public int getPosition() {
+ return index;
+ }
+
+ /**
+ * Set (seek to) position index in input glyph sequence.
+ * @param index to seek to
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public void setPosition ( int index ) throws IndexOutOfBoundsException {
+ if ( ( index >= 0 ) && ( index <= indexLast ) ) {
+ this.index = index;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Obtain last valid position index in input glyph sequence.
+ * @return current last index
+ */
+ public int getLastPosition() {
+ return indexLast;
+ }
+
+ /**
+ * Determine if at least one glyph remains in
+ * input sequence.
+ * @return true if one or more glyph remains
+ */
+ public boolean hasNext() {
+ return hasNext ( 1 );
+ }
+
+ /**
+ * Determine if at least <code>count</code> glyphs remain in
+ * input sequence.
+ * @param count of glyphs to test
+ * @return true if at least <code>count</code> glyphs are available
+ */
+ public boolean hasNext ( int count ) {
+ return ( index + count ) <= indexLast;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., add the consuemd count to the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed.
+ * @return the new (updated) position index
+ */
+ public int next() {
+ if ( index < indexLast ) {
+ // force consumption of at least one input glyph
+ if ( consumed == 0 ) {
+ consumed = 1;
+ }
+ index += consumed; consumed = 0;
+ if ( index > indexLast ) {
+ index = indexLast;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Determine if at least one backtrack (previous) glyph is present
+ * in input sequence.
+ * @return true if one or more glyph remains
+ */
+ public boolean hasPrev() {
+ return hasPrev ( 1 );
+ }
+
+ /**
+ * Determine if at least <code>count</code> backtrack (previous) glyphs
+ * are present in input sequence.
+ * @param count of glyphs to test
+ * @return true if at least <code>count</code> glyphs are available
+ */
+ public boolean hasPrev ( int count ) {
+ return ( index - count ) >= 0;
+ }
+
+ /**
+ * Update the current position index based upon previously consumed
+ * glyphs, i.e., subtract the consuemd count from the current position index.
+ * If no glyphs were previously consumed, then forces exactly one
+ * glyph to be consumed. This method is used to traverse an input
+ * glyph sequence in reverse order.
+ * @return the new (updated) position index
+ */
+ public int prev() {
+ if ( index > 0 ) {
+ // force consumption of at least one input glyph
+ if ( consumed == 0 ) {
+ consumed = 1;
+ }
+ index -= consumed; consumed = 0;
+ if ( index < 0 ) {
+ index = 0;
+ }
+ }
+ return index;
+ }
+
+ /**
+ * Record the consumption of <code>count</code> glyphs such that
+ * this consumption never exceeds the number of glyphs in the input glyph
+ * sequence.
+ * @param count of glyphs to consume
+ * @return newly adjusted consumption count
+ * @throws IndexOutOfBoundsException if count would cause consumption
+ * to exceed count of glyphs in input glyph sequence
+ */
+ public int consume ( int count ) throws IndexOutOfBoundsException {
+ if ( ( consumed + count ) <= indexLast ) {
+ consumed += count;
+ return consumed;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ /**
+ * Determine if any consumption has occurred.
+ * @return true if consumption count is greater than zero
+ */
+ public boolean didConsume() {
+ return consumed > 0;
+ }
+
+ /**
+ * Obtain reference to input glyph sequence, which must not be modified.
+ * @return input glyph sequence
+ */
+ public GlyphSequence getInput() {
+ return igs;
+ }
+
+ /**
+ * Obtain glyph at specified offset from current position.
+ * @param offset from current position
+ * @return glyph at specified offset from current position
+ * @throws IndexOutOfBoundsException if no glyph available at offset
+ */
+ public int getGlyph ( int offset ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ return igs.getGlyph ( i );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain glyph at current position.
+ * @return glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int getGlyph() throws IndexOutOfBoundsException {
+ return getGlyph ( 0 );
+ }
+
+ /**
+ * Set (replace) glyph at specified offset from current position.
+ * @param offset from current position
+ * @param glyph to set at specified offset from current position
+ * @throws IndexOutOfBoundsException if specified offset is not valid position
+ */
+ public void setGlyph ( int offset, int glyph ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ igs.setGlyph ( i, glyph );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at specified offset from current position.
+ * @param offset from current position
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if offset results in an invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation getAssociation ( int offset ) throws IndexOutOfBoundsException {
+ int i = index + offset;
+ if ( ( i >= 0 ) && ( i < indexLast ) ) {
+ return igs.getAssociation ( i );
+ } else {
+ throw new IndexOutOfBoundsException ( "attempting index at " + i );
+ }
+ }
+
+ /**
+ * Obtain character association of glyph at current position.
+ * @return character association of glyph at current position
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public GlyphSequence.CharAssociation getAssociation() throws IndexOutOfBoundsException {
+ return getAssociation ( 0 );
+ }
+
+ /**
+ * Obtain <code>count</code> glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ if ( count < 0 ) {
+ count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
+ }
+ int start = index + offset;
+ if ( start < 0 ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
+ } else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
+ }
+ if ( glyphs == null ) {
+ glyphs = new int [ count ];
+ } else if ( glyphs.length != count ) {
+ throw new IllegalArgumentException ( "glyphs array is non-null, but its length (" + glyphs.length + "), is not equal to count (" + count + ")" );
+ }
+ if ( ! reverseOrder ) {
+ return getGlyphsForward ( start, count, ignoreTester, glyphs, counts );
+ } else {
+ return getGlyphsReverse ( start, count, ignoreTester, glyphs, counts );
+ }
+ }
+
+ private int[] getGlyphsForward ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
+ if ( k < count ) {
+ glyphs [ k++ ] = gi; counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ private int[] getGlyphsReverse ( int start, int count, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, k = 0; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
+ if ( k < count ) {
+ glyphs [ k++ ] = gi; counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return glyphs;
+ }
+
+ /**
+ * Obtain <code>count</code> glyphs starting at specified offset from current position. If
+ * offset is negative, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphs ( int offset, int count, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ return getGlyphs ( offset, count, offset < 0, ignoreDefault, glyphs, counts );
+ }
+
+ /**
+ * Obtain all glyphs starting from current position to end of input glyph sequence.
+ * @return array of available glyphs
+ * @throws IndexOutOfBoundsException if no glyph available
+ */
+ public int[] getGlyphs() throws IndexOutOfBoundsException {
+ return getGlyphs ( 0, indexLast - index, false, null, null, null );
+ }
+
+ /**
+ * Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param glyphs array to use to fetch glyphs
+ * @param counts int[2] array to receive fetched glyph counts, where counts[0] will
+ * receive the number of glyphs obtained, and counts[1] will receive the number of glyphs
+ * ignored
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, int[] glyphs, int[] counts ) throws IndexOutOfBoundsException {
+ return getGlyphs ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), glyphs, counts );
+ }
+
+ /**
+ * Obtain <code>count</code> ignored glyphs starting at specified offset from current position. If <code>offset</code> is
+ * negative, then fetch in reverse order.
+ * @param offset from current position
+ * @param count number of glyphs to obtain
+ * @return array of glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getIgnoredGlyphs ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getIgnoredGlyphs ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then search backwards in input glyph sequence.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs to count (or null, in which case none are ignored)
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset, boolean reverseOrder, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ( ( start < 0 ) || ( start > indexLast ) ) {
+ return new int[] { 0, 0 };
+ } else if ( ! reverseOrder ) {
+ return getGlyphsAvailableForward ( start, ignoreTester );
+ } else {
+ return getGlyphsAvailableReverse ( start, ignoreTester );
+ }
+ }
+
+ private int[] getGlyphsAvailableForward ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if ( ignoreTester == null ) {
+ counted = indexLast - start;
+ } else {
+ for ( int i = start, n = indexLast; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ignoreTester.test ( gi ) ) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ private int[] getGlyphsAvailableReverse ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ if ( ignoreTester == null ) {
+ counted = start + 1;
+ } else {
+ for ( int i = start; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ignoreTester.test ( gi ) ) {
+ ignored++;
+ } else {
+ counted++;
+ }
+ }
+ }
+ }
+ return new int[] { counted, ignored };
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param reverseOrder true if to obtain in reverse order
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset, boolean reverseOrder ) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable ( offset, reverseOrder, ignoreDefault );
+ }
+
+ /**
+ * Determine number of glyphs available starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @return an int[2] array where counts[0] is the number of glyphs available, and counts[1] is the number of glyphs ignored
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int[] getGlyphsAvailable ( int offset ) throws IndexOutOfBoundsException {
+ return getGlyphsAvailable ( offset, offset < 0 );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then associations are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ if ( count < 0 ) {
+ count = getGlyphsAvailable ( offset, reverseOrder, ignoreTester ) [ 0 ];
+ }
+ int start = index + offset;
+ if ( start < 0 ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else if ( ! reverseOrder && ( ( start + count ) > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start + count ) );
+ } else if ( reverseOrder && ( ( start + 1 ) < count ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + ( start - count ) );
+ }
+ if ( associations == null ) {
+ associations = new GlyphSequence.CharAssociation [ count ];
+ } else if ( associations.length != count ) {
+ throw new IllegalArgumentException ( "associations array is non-null, but its length (" + associations.length + "), is not equal to count (" + count + ")" );
+ }
+ if ( ! reverseOrder ) {
+ return getAssociationsForward ( start, count, ignoreTester, associations, counts );
+ } else {
+ return getAssociationsReverse ( start, count, ignoreTester, associations, counts );
+ }
+ }
+
+ private GlyphSequence.CharAssociation[] getAssociationsForward ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, n = indexLast, k = 0; i < n; i++ ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
+ if ( k < count ) {
+ associations [ k++ ] = getAssociation ( i - index ); counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ private GlyphSequence.CharAssociation[] getAssociationsReverse ( int start, int count, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ int counted = 0;
+ int ignored = 0;
+ for ( int i = start, k = 0; i >= 0; i-- ) {
+ int gi = getGlyph ( i - index );
+ if ( gi == 65535 ) {
+ ignored++;
+ } else {
+ if ( ( ignoreTester == null ) || ! ignoreTester.test ( gi ) ) {
+ if ( k < count ) {
+ associations [ k++ ] = getAssociation ( i - index ); counted++;
+ } else {
+ break;
+ }
+ } else {
+ ignored++;
+ }
+ }
+ }
+ if ( ( counts != null ) && ( counts.length > 1 ) ) {
+ counts[0] = counted;
+ counts[1] = ignored;
+ }
+ return associations;
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
+ * <code>reverseOrder</code> is true, then glyphs are returned in reverse order starting at specified offset
+ * and going in reverse towards beginning of input glyph sequence.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @param reverseOrder true if to obtain in reverse order
+ * @param ignoreTester glyph tester to use to determine which glyphs are ignored (or null, in which case none are ignored)
+ * @param associations array to use to fetch associations
+ * @param counts int[2] array to receive fetched association counts, where counts[0] will
+ * receive the number of associations obtained, and counts[1] will receive the number of glyphs whose
+ * associations were ignored
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count, boolean reverseOrder, GlyphTester ignoreTester, GlyphSequence.CharAssociation[] associations, int[] counts )
+ throws IndexOutOfBoundsException {
+ return getAssociations ( offset, count, reverseOrder, new NotGlyphTester ( ignoreTester ), associations, counts );
+ }
+
+ /**
+ * Obtain <code>count</code> character associations of ignored glyphs starting at specified offset from current position. If
+ * offset is negative, then search backwards in input glyph sequence. Uses the
+ * default ignores tester.
+ * @param offset from current position
+ * @param count number of character associations to obtain
+ * @return array of associations
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public GlyphSequence.CharAssociation[] getIgnoredAssociations ( int offset, int count ) throws IndexOutOfBoundsException {
+ return getIgnoredAssociations ( offset, count, offset < 0, ignoreDefault, null, null );
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length <code>count</code> glyphs with a subsequence of the sequence <code>gs</code> starting from the specified
+ * offset <code>gsOffset</code> of length <code>gsCount</code> glyphs.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @param gsOffset offset of first glyph in replacement sequence
+ * @param gsCount count of glyphs in replacement sequence starting at <code>gsOffset</code>
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput ( int offset, int count, GlyphSequence gs, int gsOffset, int gsCount ) throws IndexOutOfBoundsException {
+ int nig = ( igs != null ) ? igs.getGlyphCount() : 0;
+ int position = getPosition() + offset;
+ if ( position < 0 ) {
+ position = 0;
+ } else if ( position > nig ) {
+ position = nig;
+ }
+ if ( ( count < 0 ) || ( ( position + count ) > nig ) ) {
+ count = nig - position;
+ }
+ int nrg = ( gs != null ) ? gs.getGlyphCount() : 0;
+ if ( gsOffset < 0 ) {
+ gsOffset = 0;
+ } else if ( gsOffset > nrg ) {
+ gsOffset = nrg;
+ }
+ if ( ( gsCount < 0 ) || ( ( gsOffset + gsCount ) > nrg ) ) {
+ gsCount = nrg - gsOffset;
+ }
+ int ng = nig + gsCount - count;
+ IntBuffer gb = IntBuffer.allocate ( ng );
+ List al = new ArrayList ( ng );
+ for ( int i = 0, n = position; i < n; i++ ) {
+ gb.put ( igs.getGlyph ( i ) );
+ al.add ( igs.getAssociation ( i ) );
+ }
+ for ( int i = gsOffset, n = gsOffset + gsCount; i < n; i++ ) {
+ gb.put ( gs.getGlyph ( i ) );
+ al.add ( gs.getAssociation ( i ) );
+ }
+ for ( int i = position + count, n = nig; i < n; i++ ) {
+ gb.put ( igs.getGlyph ( i ) );
+ al.add ( igs.getAssociation ( i ) );
+ }
+ gb.flip();
+ if ( igs.compareGlyphs ( gb ) != 0 ) {
+ this.igs = new GlyphSequence ( igs.getCharacters(), gb, al );
+ this.indexLast = gb.limit();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Replace subsequence of input glyph sequence starting at specified offset from current position and of
+ * length <code>count</code> glyphs with all glyphs in the replacement sequence <code>gs</code>.
+ * @param offset from current position
+ * @param count number of glyphs to replace, which, if negative means all glyphs from offset to end of input sequence
+ * @param gs glyph sequence from which to obtain replacement glyphs
+ * @return true if replacement occurred, or false if replacement would result in no change to input glyph sequence
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public boolean replaceInput ( int offset, int count, GlyphSequence gs ) throws IndexOutOfBoundsException {
+ return replaceInput ( offset, count, gs, 0, gs.getGlyphCount() );
+ }
+
+ /**
+ * Erase glyphs in input glyph sequence starting at specified offset from current position, where each glyph
+ * in the specified <code>glyphs</code> array is matched, one at a time, and when a (forward searching) match is found
+ * in the input glyph sequence, the matching glyph is replaced with the glyph index 65535.
+ * @param offset from current position
+ * @param glyphs array of glyphs to erase
+ * @return the number of glyphs erased, which may be less than the number of specified glyphs
+ * @throws IndexOutOfBoundsException if offset or count results in an
+ * invalid index into input glyph sequence
+ */
+ public int erase ( int offset, int[] glyphs ) throws IndexOutOfBoundsException {
+ int start = index + offset;
+ if ( ( start < 0 ) || ( start > indexLast ) ) {
+ throw new IndexOutOfBoundsException ( "will attempt index at " + start );
+ } else {
+ int erased = 0;
+ for ( int i = start - index, n = indexLast - start; i < n; i++ ) {
+ int gi = getGlyph ( i );
+ if ( gi == glyphs [ erased ] ) {
+ setGlyph ( i, 65535 );
+ erased++;
+ }
+ }
+ return erased;
+ }
+ }
+
+ /**
+ * Determine if is possible that the current input sequence satisfies a script specific
+ * context testing predicate. If no predicate applies, then application is always possible.
+ * @return true if no script specific context tester applies or if a specified tester returns
+ * true for the current input sequence context
+ */
+ public boolean maybeApplicable() {
+ if ( gct == null ) {
+ return true;
+ } else {
+ return gct.test ( igs, index );
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one glyph.
+ */
+ public void applyDefault() {
+ consumed += 1;
+ }
+
+ /**
+ * Determine if specified glyph is a base glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+ */
+ public boolean isBase ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_BASE );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is a ligature glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+ */
+ public boolean isLigature ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_LIGATURE );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Determine if specified glyph is a mark glyph according to the governing
+ * glyph definition table.
+ * @param gi glyph index to test
+ * @return true if glyph definition table records glyph as a mark glyph; otherwise, false
+ */
+ public boolean isMark ( int gi ) {
+ if ( gdef != null ) {
+ return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_MARK );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getIgnoreTester ( int flags ) {
+ if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_LIGATURE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+ return ignoreBase;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+ return ignoreLigature;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+ if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_LIGATURE) ) == 0 ) {
+ return ignoreMark;
+ } else {
+ return getCombinedIgnoreTester ( flags );
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Obtain an ignored glyph tester that corresponds to the specified multiple (combined) lookup flags.
+ * @param flags lookup flags
+ * @return a glyph tester
+ */
+ public GlyphTester getCombinedIgnoreTester ( int flags ) {
+ GlyphTester[] gta = new GlyphTester [ 3 ];
+ int ngt = 0;
+ if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+ gta [ ngt++ ] = ignoreBase;
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+ gta [ ngt++ ] = ignoreLigature;
+ }
+ if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+ gta [ ngt++ ] = ignoreMark;
+ }
+ return getCombinedOrTester ( gta, ngt );
+ }
+
+ /**
+ * Obtain an combined OR glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined OR glyph tester
+ */
+ public GlyphTester getCombinedOrTester ( GlyphTester[] gta, int ngt ) {
+ if ( ngt > 0 ) {
+ return new CombinedOrGlyphTester ( gta, ngt );
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Obtain an combined AND glyph tester.
+ * @param gta an array of glyph testers
+ * @param ngt number of glyph testers present in specified array
+ * @return a combined AND glyph tester
+ */
+ public GlyphTester getCombinedAndTester ( GlyphTester[] gta, int ngt ) {
+ if ( ngt > 0 ) {
+ return new CombinedAndGlyphTester ( gta, ngt );
+ } else {
+ return null;
+ }
+ }
+
+ /** combined OR glyph tester */
+ private static class CombinedOrGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedOrGlyphTester ( GlyphTester[] gta, int ngt ) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi ) {
+ for ( int i = 0, n = ngt; i < n; i++ ) {
+ GlyphTester gt = gta [ i ];
+ if ( gt != null ) {
+ if ( gt.test ( gi ) ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ /** combined AND glyph tester */
+ private static class CombinedAndGlyphTester implements GlyphTester {
+ private GlyphTester[] gta;
+ private int ngt;
+ CombinedAndGlyphTester ( GlyphTester[] gta, int ngt ) {
+ this.gta = gta;
+ this.ngt = ngt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi ) {
+ for ( int i = 0, n = ngt; i < n; i++ ) {
+ GlyphTester gt = gta [ i ];
+ if ( gt != null ) {
+ if ( ! gt.test ( gi ) ) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ /** NOT glyph tester */
+ private static class NotGlyphTester implements GlyphTester {
+ private GlyphTester gt;
+ NotGlyphTester ( GlyphTester gt ) {
+ this.gt = gt;
+ }
+ /** {@inheritDoc} */
+ public boolean test ( int gi ) {
+ if ( gt != null ) {
+ if ( gt.test ( gi ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+}
package org.apache.fop.fonts;
-import java.nio.CharBuffer;
+import java.nio.IntBuffer;
-import java.util.Arrays;
-import java.util.Iterator;
+import java.util.ArrayList;
import java.util.List;
-import java.util.ListIterator;
-// CSOFF: NoWhitespaceAfterCheck
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
/**
* A GlyphSequence encapsulates a sequence of character codes, a sequence of glyph codes,
* character code sequence with which the glyph codes are associated.
* @author Glenn Adams
*/
-public class GlyphSequence implements CharSequence {
+public class GlyphSequence implements Cloneable {
+
+ /** default character buffer capacity in case new character buffer is created */
+ private static final int DEFAULT_CHARS_CAPACITY = 8;
+
+ /** character buffer */
+ private IntBuffer characters;
+ /** glyph buffer */
+ private IntBuffer glyphs;
+ /** association list */
+ private List associations;
+
+ /**
+ * Instantiate a glyph sequence, reusing (i.e., not copying) the referenced
+ * character and glyph buffers and associations. If characters is null, then
+ * an empty character buffer is created. If glyphs is null, then a glyph buffer
+ * is created whose capacity is that of the character buffer. If associations is
+ * null, then identity associations are created.
+ * @param characters a (possibly null) buffer of associated (originating) characters
+ * @param glyphs a (possibly null) buffer of glyphs
+ * @param associations a (possibly null) array of glyph to character associations
+ */
+ public GlyphSequence ( IntBuffer characters, IntBuffer glyphs, List associations ) {
+ if ( characters == null ) {
+ characters = IntBuffer.allocate ( DEFAULT_CHARS_CAPACITY );
+ }
+ if ( glyphs == null ) {
+ glyphs = IntBuffer.allocate ( characters.capacity() );
+ }
+ if ( associations == null ) {
+ associations = makeIdentityAssociations ( characters.limit(), glyphs.limit() );
+ }
+ this.characters = characters;
+ this.glyphs = glyphs;
+ this.associations = associations;
+ }
- private CharSequence characters;
- private CharSequence glyphs;
- private CharAssociation[] associations;
+ /**
+ * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+ * the character array of the existing sequence (but not the buffer object), and creates new copies
+ * of glyphs buffer and association list.
+ * @param gs an existing glyph sequence
+ */
+ public GlyphSequence ( GlyphSequence gs ) {
+ this ( gs.characters.duplicate(), copyBuffer ( gs.glyphs ), copyAssociations ( gs.associations ) );
+ }
/**
- * Instantiate a glyph sequence.
- * @param characters a (possibly empty) sequence of associated (originating) characters
- * @param sequences a (possibly empty) list of glyph sequences
- * @param associations a (possibly empty) list of glyph to character associations, one for each glyph in the concatenated glyph sequences
- * @param reverse a boolean indicating if the glyphs are in reverse order with respect to the nominal inline progression direction
+ * Instantiate a glyph sequence using an existing glyph sequence, where the new glyph sequence shares
+ * the character array of the existing sequence (but not the buffer object), but uses the specified
+ * backtrack, input, and lookahead glyph arrays to populate the glyphs, and uses the specified
+ * of glyphs buffer and association list.
+ * backtrack, input, and lookahead association arrays to populate the associations.
+ * @param gs an existing glyph sequence
+ * @param bga backtrack glyph array
+ * @param iga input glyph array
+ * @param lga lookahead glyph array
+ * @param bal backtrack association list
+ * @param ial input association list
+ * @param lal lookahead association list
*/
- public GlyphSequence ( CharSequence characters, List/*<GlyphSequence>*/ sequences, List/*<CharAssociation>*/ associations, boolean reverse ) {
- this ( characters, concatenateSequences ( sequences, reverse ), concatenateAssociations ( associations, reverse ) );
+ public GlyphSequence ( GlyphSequence gs, int[] bga, int[] iga, int[] lga, CharAssociation[] bal, CharAssociation[] ial, CharAssociation[] lal ) {
+ this ( gs.characters.duplicate(), concatGlyphs ( bga, iga, lga ), concatAssociations ( bal, ial, lal ) );
}
/**
- * Instantiate a glyph sequence.
- * @param characters a (possibly empty) sequence of associated (originating) characters
- * @param glyphs a (possibly empty) list of glyphs
- * @param associations a (possibly empty) list of glyph to character associations, one for each glyph in the concatenated glyph sequences
+ * Obtain reference to underlying character buffer.
+ * @return character buffer reference
*/
- public GlyphSequence ( CharSequence characters, CharSequence glyphs, CharAssociation[] associations ) {
- if ( ( characters == null ) || ( glyphs == null ) ) {
- throw new IllegalArgumentException ( "characters and glyphs must be non-null" );
- } else if ( ( associations != null ) && ( associations.length != glyphs.length() ) ) {
- throw new IllegalArgumentException ( "number of associations must match number of glyphs" );
+ public IntBuffer getCharacters() {
+ return characters;
+ }
+
+ /**
+ * Obtain array of characters. If <code>copy</code> is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of characters in array.
+ * To determine the number of characters, use {@link #getCharacterCount}.
+ * @param copy true if to return a newly instantiated array of characters
+ * @return array of characters
+ */
+ public int[] getCharacterArray ( boolean copy ) {
+ if ( copy ) {
+ return toArray ( characters );
} else {
- this.characters = characters;
- this.glyphs = glyphs;
- if ( associations == null ) {
- associations = makeIdentityAssociations ( characters, glyphs );
- }
- this.associations = associations;
+ return characters.array();
}
}
- /** @return sequence of corresponding (originating) characters */
- public CharSequence getCharacters() {
- return characters;
+ /**
+ * Obtain the number of characters in character array, where
+ * each character constitutes a unicode scalar value.
+ * @return number of characters available in character array
+ */
+ public int getCharacterCount() {
+ return characters.limit();
}
- /** @return sequence of glyphs in glyph sequence */
- public CharSequence getGlyphs() {
- return glyphs;
+ /**
+ * Obtain glyph id at specified index.
+ * @param index to obtain glyph
+ * @return the glyph identifier of glyph at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
+ */
+ public int getGlyph ( int index ) throws IndexOutOfBoundsException {
+ return glyphs.get ( index );
}
- /** @return glyph to character associations, one for each glyph */
- public CharAssociation[] getAssociations() {
- return associations;
+ /**
+ * Set glyph id at specified index.
+ * @param index to set glyph
+ * @param gi glyph index
+ * @throws IndexOutOfBoundsException if index is greater or equal to
+ * the limit of the underlying glyph buffer
+ */
+ public void setGlyph ( int index, int gi ) throws IndexOutOfBoundsException {
+ if ( gi > 65535 ) {
+ gi = 65535;
+ }
+ glyphs.put ( index, gi );
}
/**
- * Obtain the sequence of characters that corresponds to the glyph sequence at interval
- * [offset,offset+count).
- * @param offset to first glyph
- * @param count of glyphs
- * @return corresponding character sequence
+ * Obtain reference to underlying glyph buffer.
+ * @return glyph buffer reference
*/
- public CharSequence getCharsForGlyphs ( int offset, int count ) throws DiscontinuousAssociationException { // CSOK: JavadocMethodCheck
- int sFirst = -1, eLast = -1;
- for ( int i = 0, n = count; i < n; i++ ) {
- CharAssociation ca = associations [ offset + i ];
- int s = ca.getStart();
- int e = ca.getEnd();
- if ( sFirst < 0 ) {
- sFirst = s;
- }
- if ( eLast < 0 ) {
- eLast = e;
- } else if ( s == eLast ) {
- eLast = e;
- } else {
- throw new DiscontinuousAssociationException();
+ public IntBuffer getGlyphs() {
+ return glyphs;
+ }
+
+ /**
+ * Obtain count glyphs starting at offset. If <code>count</code> is
+ * negative, then it is treated as if the number of available glyphs
+ * were specified.
+ * @param offset into glyph sequence
+ * @param count of glyphs to obtain starting at offset, or negative,
+ * indicating all avaialble glyphs starting at offset
+ * @return glyph array
+ */
+ public int[] getGlyphs ( int offset, int count ) {
+ int ng = getGlyphCount();
+ if ( offset < 0 ) {
+ offset = 0;
+ } else if ( offset > ng ) {
+ offset = ng;
+ }
+ if ( count < 0 ) {
+ count = ng - offset;
+ }
+ int[] ga = new int [ count ];
+ for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+ if ( k < ga.length ) {
+ ga [ k++ ] = glyphs.get ( i );
}
}
- return characters.subSequence ( sFirst, eLast );
+ return ga;
}
/**
- * Obtain the glyph subsequence corresponding to the half-open interval [start,end).
- * @param start of subsequence
- * @param end of subsequence
- * @return a subsequence of this sequence
+ * Obtain array of glyphs. If <code>copy</code> is true, then
+ * a newly instantiated array is returned, otherwise a reference to
+ * the underlying buffer's array is returned. N.B. in case a reference
+ * to the undelying buffer's array is returned, the length
+ * of the array is not necessarily the number of glyphs in array.
+ * To determine the number of glyphs, use {@link #getGlyphCount}.
+ * @param copy true if to return a newly instantiated array of glyphs
+ * @return array of glyphs
*/
- public GlyphSequence getGlyphSubsequence ( int start, int end ) {
- CharAssociation[] subset = new CharAssociation[end - start];
- System.arraycopy(associations, start, subset, 0, end - start);
- return new GlyphSequence ( characters, glyphs.subSequence ( start, end ), subset );
+ public int[] getGlyphArray ( boolean copy ) {
+ if ( copy ) {
+ return toArray ( glyphs );
+ } else {
+ return glyphs.array();
+ }
}
- /** @return the number of glyphs in this glyph sequence */
- public int length() {
- return glyphs.length();
+ /**
+ * Obtain the number of glyphs in glyphs array, where
+ * each glyph constitutes a font specific glyph index.
+ * @return number of glyphs available in character array
+ */
+ public int getGlyphCount() {
+ return glyphs.limit();
}
/**
- * Obtain glyph id at specified index.
- * @param index to obtain glyph
- * @return the glyph identifier of glyph at specified index
+ * Obtain association at specified index.
+ * @param index into associations array
+ * @return glyph to character associations at specified index
+ * @throws IndexOutOfBoundsException if index is less than zero
+ * or exceeds last valid position
*/
- public char charAt ( int index ) {
- return glyphs.charAt ( index );
+ public CharAssociation getAssociation ( int index ) throws IndexOutOfBoundsException {
+ return (CharAssociation) associations.get ( index );
}
/**
- * Obtain glyph code subsequence over interval [start,end).
- * @param start of subsequence
- * @param end of subsequence
- * @return the glyph code subsequence
+ * Obtain reference to underlying associations list.
+ * @return associations list
*/
- public CharSequence subSequence ( int start, int end ) {
- return glyphs.subSequence ( start, end );
+ public List getAssociations() {
+ return associations;
+ }
+
+ /**
+ * Obtain count associations starting at offset.
+ * @param offset into glyph sequence
+ * @param count of associations to obtain starting at offset, or negative,
+ * indicating all avaialble associations starting at offset
+ * @return associations
+ */
+ public CharAssociation[] getAssociations ( int offset, int count ) {
+ int ng = getGlyphCount();
+ if ( offset < 0 ) {
+ offset = 0;
+ } else if ( offset > ng ) {
+ offset = ng;
+ }
+ if ( count < 0 ) {
+ count = ng - offset;
+ }
+ CharAssociation[] aa = new CharAssociation [ count ];
+ for ( int i = offset, n = offset + count, k = 0; i < n; i++ ) {
+ if ( k < aa.length ) {
+ aa [ k++ ] = (CharAssociation) associations.get ( i );
+ }
+ }
+ return aa;
+ }
+
+ /**
+ * Compare glyphs.
+ * @param gb buffer containing glyph indices with which this glyph sequence's glyphs are to be compared
+ * @return zero if glyphs are the same, otherwise returns 1 or -1 according to whether this glyph sequence's
+ * glyphs are lexicographically greater or lesser than the glyphs in the specified string buffer
+ */
+ public int compareGlyphs ( IntBuffer gb ) {
+ int ng = getGlyphCount();
+ for ( int i = 0, n = gb.limit(); i < n; i++ ) {
+ if ( i < ng ) {
+ int g1 = glyphs.get ( i );
+ int g2 = gb.get ( i );
+ if ( g1 > g2 ) {
+ return 1;
+ } else if ( g1 < g2 ) {
+ return -1;
+ }
+ } else {
+ return -1; // this gb is a proper prefix of specified gb
+ }
+ }
+ return 0; // same lengths with no difference
+ }
+
+ /** {@inheritDoc} */
+ public Object clone() {
+ try {
+ GlyphSequence gs = (GlyphSequence) super.clone();
+ gs.characters = copyBuffer ( characters );
+ gs.glyphs = copyBuffer ( glyphs );
+ gs.associations = copyAssociations ( associations );
+ return gs;
+ } catch ( CloneNotSupportedException e ) {
+ return null;
+ }
}
/** {@inheritDoc} */
public String toString() {
- return glyphs.toString();
+ StringBuffer sb = new StringBuffer();
+ sb.append ( '{' );
+ sb.append ( "chars = [" );
+ sb.append ( characters );
+ sb.append ( "], glyphs = [" );
+ sb.append ( glyphs );
+ sb.append ( "], associations = [" );
+ sb.append ( associations );
+ sb.append ( "]" );
+ sb.append ( '}' );
+ return sb.toString();
}
- private CharAssociation[] makeIdentityAssociations ( CharSequence characters, CharSequence glyphs ) {
- int nc = characters.length();
- int ng = glyphs.length();
- CharAssociation[] ca = new CharAssociation [ ng ];
- for ( int i = 0, n = ng; i < n; i++ ) {
- int k = ( i > nc ) ? nc : i;
- ca [ i ] = new CharAssociation ( i, ( k == nc ) ? 0 : 1 );
+ /**
+ * Determine if two arrays of glyphs are identical.
+ * @param ga1 first glyph array
+ * @param ga2 second glyph array
+ * @return true if arrays are botth null or both non-null and have identical elements
+ */
+ public static boolean sameGlyphs ( int[] ga1, int[] ga2 ) {
+ if ( ga1 == ga2 ) {
+ return true;
+ } else if ( ( ga1 == null ) || ( ga2 == null ) ) {
+ return false;
+ } else if ( ga1.length != ga2.length ) {
+ return false;
+ } else {
+ for ( int i = 0, n = ga1.length; i < n; i++ ) {
+ if ( ga1[i] != ga2[i] ) {
+ return false;
+ }
+ }
+ return true;
}
- return ca;
}
- private static CharSequence concatenateSequences ( List/*<GlyphSequence>*/ sequences, boolean reverse ) {
+ /**
+ * Concatenante glyph arrays.
+ * @param bga backtrack glyph array
+ * @param iga input glyph array
+ * @param lga lookahead glyph array
+ * @return new integer buffer containing concatenated glyphs
+ */
+ public static IntBuffer concatGlyphs ( int[] bga, int[] iga, int[] lga ) {
int ng = 0;
- for ( Iterator it = sequences.iterator(); it.hasNext();) {
- GlyphSequence gs = (GlyphSequence) it.next();
- ng += gs.length();
+ if ( bga != null ) {
+ ng += bga.length;
}
- CharBuffer cb = CharBuffer.allocate ( ng );
- if ( ! reverse ) {
- for ( ListIterator it = sequences.listIterator(); it.hasNext();) {
- GlyphSequence gs = (GlyphSequence) it.next();
- cb.append ( (CharSequence) gs );
- }
- } else {
- for ( ListIterator it = sequences.listIterator ( sequences.size() ); it.hasPrevious();) {
- GlyphSequence gs = (GlyphSequence) it.previous();
- cb.append ( (CharSequence) gs );
- }
+ if ( iga != null ) {
+ ng += iga.length;
+ }
+ if ( lga != null ) {
+ ng += lga.length;
+ }
+ IntBuffer gb = IntBuffer.allocate ( ng );
+ if ( bga != null ) {
+ gb.put ( bga );
+ }
+ if ( iga != null ) {
+ gb.put ( iga );
+ }
+ if ( lga != null ) {
+ gb.put ( lga );
}
- cb.rewind();
- return cb;
+ gb.flip();
+ return gb;
}
- private static CharAssociation[] concatenateAssociations ( List/*<CharAssociation>*/ associations, boolean reverse ) {
+ /**
+ * Concatenante association arrays.
+ * @param baa backtrack association array
+ * @param iaa input association array
+ * @param laa lookahead association array
+ * @return new list containing concatenated associations
+ */
+ public static List concatAssociations ( CharAssociation[] baa, CharAssociation[] iaa, CharAssociation[] laa ) {
int na = 0;
- CharAssociation[] ca = new CharAssociation [ associations.size() ];
- if ( ! reverse ) {
- for ( ListIterator it = associations.listIterator(); it.hasNext();) {
- CharAssociation a = (CharAssociation) it.next();
- ca [ na++ ] = a;
+ if ( baa != null ) {
+ na += baa.length;
+ }
+ if ( iaa != null ) {
+ na += iaa.length;
+ }
+ if ( laa != null ) {
+ na += laa.length;
+ }
+ if ( na > 0 ) {
+ List gl = new ArrayList ( na );
+ if ( baa != null ) {
+ for ( int i = 0; i < baa.length; i++ ) {
+ gl.add ( baa[i] );
+ }
}
- } else {
- for ( ListIterator it = associations.listIterator ( associations.size() ); it.hasPrevious();) {
- CharAssociation a = (CharAssociation) it.previous();
- ca [ na++ ] = a;
+ if ( iaa != null ) {
+ for ( int i = 0; i < iaa.length; i++ ) {
+ gl.add ( iaa[i] );
+ }
}
+ if ( laa != null ) {
+ for ( int i = 0; i < laa.length; i++ ) {
+ gl.add ( laa[i] );
+ }
+ }
+ return gl;
+ } else {
+ return null;
+ }
+ }
+
+ private static int[] toArray ( IntBuffer ib ) {
+ if ( ib != null ) {
+ int n = ib.limit();
+ int[] ia = new int[n];
+ ib.get ( ia, 0, n );
+ return ia;
+ } else {
+ return new int[0];
+ }
+ }
+
+ private static List makeIdentityAssociations ( int numChars, int numGlyphs ) {
+ int nc = numChars;
+ int ng = numGlyphs;
+ List av = new ArrayList ( ng );
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int k = ( i > nc ) ? nc : i;
+ av.add ( new CharAssociation ( i, ( k == nc ) ? 0 : 1 ) );
+ }
+ return av;
+ }
+
+ private static IntBuffer copyBuffer ( IntBuffer ib ) {
+ if ( ib != null ) {
+ int[] ia = new int [ ib.capacity() ];
+ int p = ib.position();
+ int l = ib.limit();
+ System.arraycopy ( ib.array(), 0, ia, 0, ia.length );
+ return IntBuffer.wrap ( ia, p, l - p );
+ } else {
+ return null;
+ }
+ }
+
+ private static List copyAssociations ( List ca ) {
+ if ( ca != null ) {
+ return new ArrayList ( ca );
+ } else {
+ return ca;
}
- return ca;
}
/**
* A structure class encapsulating an interval of character codes (in a CharSequence)
- * expressed as an offset and count (of code elements in a CharSequence, i.e., numbere of
+ * expressed as an offset and count (of code elements in a CharSequence, i.e., number of
* UTF-16 code elements. N.B. count does not necessarily designate the number of Unicode
* scalar values expressed by the CharSequence; in particular, it does not do so if there
* is one or more UTF-16 surrogate pairs present in the CharSequence.)
*/
- public static class CharAssociation {
+ public static class CharAssociation implements Cloneable {
private final int offset;
private final int count;
+ private final int[] subIntervals;
/**
* Instantiate a character association.
* @param offset into array of UTF-16 code elements (in associated CharSequence)
* @param count of UTF-16 character code elements (in associated CharSequence)
+ * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+ * members of array are sub-interval starts, and odd members are sub-interval
+ * ends (exclusive)
*/
- public CharAssociation ( int offset, int count ) {
+ public CharAssociation ( int offset, int count, int[] subIntervals ) {
this.offset = offset;
this.count = count;
+ this.subIntervals = ( ( subIntervals != null ) && ( subIntervals.length > 2 ) ) ? subIntervals : null;
+ }
+
+ /**
+ * Instantiate a non-disjoint character association.
+ * @param offset into array of UTF-16 code elements (in associated CharSequence)
+ * @param count of UTF-16 character code elements (in associated CharSequence)
+ */
+ public CharAssociation ( int offset, int count ) {
+ this ( offset, count, null );
+ }
+
+ /**
+ * Instantiate a non-disjoint character association.
+ * @param subIntervals if disjoint, then array of sub-intervals, otherwise null; even
+ * members of array are sub-interval starts, and odd members are sub-interval
+ * ends (exclusive)
+ */
+ public CharAssociation ( int[] subIntervals ) {
+ this ( getSubIntervalsStart ( subIntervals ), getSubIntervalsLength ( subIntervals ), subIntervals );
}
/** @return offset (start of association interval) */
return getOffset() + getCount();
}
+ /** @return true if association is disjoint */
+ public boolean isDisjoint() {
+ return subIntervals != null;
+ }
+
+ /** @return subintervals of disjoint association */
+ public int[] getSubIntervals() {
+ return subIntervals;
+ }
+
+ /** @return count of subintervals of disjoint association */
+ public int getSubIntervalCount() {
+ return ( subIntervals != null ) ? ( subIntervals.length / 2 ) : 0;
+ }
+
+ /** {@inheritDoc} */
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch ( CloneNotSupportedException e ) {
+ return null;
+ }
+ }
+
+ /**
+ * Replicate association to form <code>repeat</code> new associations.
+ * @param a association to replicate
+ * @param repeat count
+ * @return array of replicated associations
+ */
+ public static CharAssociation[] replicate ( CharAssociation a, int repeat ) {
+ CharAssociation[] aa = new CharAssociation [ repeat ];
+ for ( int i = 0, n = aa.length; i < n; i++ ) {
+ aa [ i ] = (CharAssociation) a.clone();
+ }
+ return aa;
+ }
+
+ /**
+ * Join (merge) multiple associations into a single, potentially disjoint
+ * association.
+ * @param aa array of associations to join
+ * @return (possibly disjoint) association containing joined associations
+ */
+ public static CharAssociation join ( CharAssociation[] aa ) {
+ // extract sorted intervals
+ int[] ia = extractIntervals ( aa );
+ if ( ( ia == null ) || ( ia.length == 0 ) ) {
+ return new CharAssociation ( 0, 0 );
+ } else if ( ia.length == 2 ) {
+ int s = ia[0];
+ int e = ia[1];
+ return new CharAssociation ( s, e - s );
+ } else {
+ return new CharAssociation ( mergeIntervals ( ia ) );
+ }
+ }
+
+ private static int getSubIntervalsStart ( int[] ia ) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if ( ia != null ) {
+ for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( s < us ) {
+ us = s;
+ }
+ if ( e > ue ) {
+ ue = e;
+ }
+ }
+ if ( ue < 0 ) {
+ ue = 0;
+ }
+ if ( us > ue ) {
+ us = ue;
+ }
+ }
+ return us;
+ }
+
+ private static int getSubIntervalsLength ( int[] ia ) {
+ int us = Integer.MAX_VALUE;
+ int ue = Integer.MIN_VALUE;
+ if ( ia != null ) {
+ for ( int i = 0, n = ia.length; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( s < us ) {
+ us = s;
+ }
+ if ( e > ue ) {
+ ue = e;
+ }
+ }
+ if ( ue < 0 ) {
+ ue = 0;
+ }
+ if ( us > ue ) {
+ us = ue;
+ }
+ }
+ return ue - us;
+ }
+
+ /**
+ * Extract sorted sub-intervals.
+ */
+ private static int[] extractIntervals ( CharAssociation[] aa ) {
+ int ni = 0;
+ for ( int i = 0, n = aa.length; i < n; i++ ) {
+ CharAssociation a = aa [ i ];
+ if ( a.isDisjoint() ) {
+ ni += a.getSubIntervalCount();
+ } else {
+ ni += 1;
+ }
+ }
+ int[] sa = new int [ ni ];
+ int[] ea = new int [ ni ];
+ for ( int i = 0, k = 0; i < aa.length; i++ ) {
+ CharAssociation a = aa [ i ];
+ if ( a.isDisjoint() ) {
+ int[] da = a.getSubIntervals();
+ for ( int j = 0; j < da.length; j += 2 ) {
+ sa [ k ] = da [ j + 0 ];
+ ea [ k ] = da [ j + 1 ];
+ k++;
+ }
+ } else {
+ sa [ k ] = a.getStart();
+ ea [ k ] = a.getEnd();
+ k++;
+ }
+ }
+ return sortIntervals ( sa, ea );
+ }
+
+ private static final int[] sortIncrements16 // CSOK: ConstantNameCheck
+ = { 1391376, 463792, 198768, 86961, 33936, 13776, 4592, 1968, 861, 336, 112, 48, 21, 7, 3, 1 };
+
+ private static final int[] sortIncrements03 // CSOK: ConstantNameCheck
+ = { 7, 3, 1 };
+
+ /**
+ * Sort sub-intervals using modified Shell Sort.
+ */
+ private static int[] sortIntervals ( int[] sa, int[] ea ) {
+ assert sa != null;
+ assert ea != null;
+ assert sa.length == ea.length;
+ int ni = sa.length;
+ int[] incr = ( ni < 21 ) ? sortIncrements03 : sortIncrements16;
+ for ( int k = 0; k < incr.length; k++ ) {
+ for ( int h = incr [ k ], i = h, n = ni, j; i < n; i++ ) {
+ int s1 = sa [ i ];
+ int e1 = ea [ i ];
+ for ( j = i; j >= h; j -= h) {
+ int s2 = sa [ j - h ];
+ int e2 = ea [ j - h ];
+ if ( s2 > s1 ) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else if ( ( s2 == s1 ) && ( e2 > e1 ) ) {
+ sa [ j ] = s2;
+ ea [ j ] = e2;
+ } else {
+ break;
+ }
+ }
+ sa [ j ] = s1;
+ ea [ j ] = e1;
+ }
+ }
+ int[] ia = new int [ ni * 2 ];
+ for ( int i = 0; i < ni; i++ ) {
+ ia [ ( i * 2 ) + 0 ] = sa [ i ];
+ ia [ ( i * 2 ) + 1 ] = ea [ i ];
+ }
+ return ia;
+ }
+
+ /**
+ * Merge overlapping and abutting sub-intervals.
+ */
+ private static int[] mergeIntervals ( int[] ia ) {
+ int ni = ia.length;
+ int i, n, nm, is, ie;
+ // count merged sub-intervals
+ for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ if ( ( ie < 0 ) || ( s > ie ) ) {
+ is = s;
+ ie = e;
+ nm++;
+ } else if ( s >= is ) {
+ if ( e > ie ) {
+ ie = e;
+ }
+ }
+ }
+ int[] mi = new int [ nm * 2 ];
+ // populate merged sub-intervals
+ for ( i = 0, n = ni, nm = 0, is = ie = -1; i < n; i += 2 ) {
+ int s = ia [ i + 0 ];
+ int e = ia [ i + 1 ];
+ int k = nm * 2;
+ if ( ( ie < 0 ) || ( s > ie ) ) {
+ is = s;
+ ie = e;
+ mi [ k + 0 ] = is;
+ mi [ k + 1 ] = ie;
+ nm++;
+ } else if ( s >= is ) {
+ if ( e > ie ) {
+ ie = e;
+ }
+ mi [ k - 1 ] = ie;
+ }
+ }
+ return mi;
+ }
+
}
+
}
// CSOFF: LineLengthCheck
/**
- * The <code>GlyphSubstitution</code> interface is implemented by a font related object
+ * The <code>GlyphSubstitution</code> interface is implemented by a glyph substitution subtable
* that supports the determination of glyph substitution information based on script and
* language of the corresponding character content.
* @author Glenn Adams
public interface GlyphSubstitution {
/**
- * Perform glyph substitutions. If no substitution applies, then returns the unmodified input sequence.
- * @param gs sequence to map to output glyph sequence
- * @param script the script associated with the characters corresponding to the glyph sequence
- * @param language the language associated with the characters corresponding to the glyph sequence
- * @return resulting glyph sequence, where each 'glyph' in the returned sequence has been mapped
- * (or not) by substitution
+ * Perform glyph substitution at the current index, mutating the substitution state object as required.
+ * Only the context associated with the current index is processed.
+ * @param ss glyph substitution state object
+ * @return true if the glyph subtable was applied, meaning that the current context matches the
+ * associated input context glyph coverage table
*/
- GlyphSequence substitute ( GlyphSequence gs, String script, String language );
+ boolean substitute ( GlyphSubstitutionState ss );
}
--- /dev/null
+/*
+ * 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.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphSubstitutionState</code> implements an state object used during glyph substitution
+ * processing.
+ * @author Glenn Adams
+ */
+
+public class GlyphSubstitutionState extends GlyphProcessingState {
+
+ /** alternates index */
+ private int[] alternatesIndex;
+ /** current output glyph sequence */
+ private IntBuffer ogb;
+ /** current output glyph to character associations */
+ private List oal;
+
+ /**
+ * Construct glyph substitution state.
+ * @param gs input glyph sequence
+ * @param script script identifier
+ * @param language language identifier
+ * @param feature feature identifier
+ * @param sct script context tester (or null)
+ */
+ public GlyphSubstitutionState ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ super ( gs, script, language, feature, sct );
+ this.ogb = IntBuffer.allocate ( gs.getGlyphCount() );
+ this.oal = new ArrayList ( gs.getGlyphCount() );
+ }
+
+ /**
+ * Construct glyph substitution state using an existing state object using shallow copy
+ * except as follows: input glyph sequence is copied deep except for its characters array.
+ * @param ss existing positioning state to copy from
+ */
+ public GlyphSubstitutionState ( GlyphSubstitutionState ss ) {
+ super ( ss );
+ this.ogb = IntBuffer.allocate ( indexLast );
+ this.oal = new ArrayList ( indexLast );
+ }
+
+ /**
+ * Set alternates indices.
+ * @param alternates array of alternates indices ordered by coverage index
+ */
+ public void setAlternates ( int[] alternates ) {
+ this.alternatesIndex = alternates;
+ }
+
+ /**
+ * Obtain alternates index associated with specified coverage index. An alternates
+ * index is used to select among stylistic alternates of a glyph at a particular
+ * coverage index. This information must be provided by the document itself (in the
+ * form of an extension attribute value), since a font has no way to determine which
+ * alternate the user desires.
+ * @param ci coverage index
+ * @return an alternates index
+ */
+ public int getAlternatesIndex ( int ci ) {
+ if ( alternatesIndex == null ) {
+ return 0;
+ } else if ( ( ci < 0 ) || ( ci > alternatesIndex.length ) ) {
+ return 0;
+ } else {
+ return alternatesIndex [ ci ];
+ }
+ }
+
+ /**
+ * Put (write) glyph into glyph output buffer.
+ * @param glyph to write
+ * @param a character association that applies to glyph
+ */
+ public void putGlyph ( int glyph, GlyphSequence.CharAssociation a ) {
+ if ( ! ogb.hasRemaining() ) {
+ ogb = growBuffer ( ogb );
+ }
+ ogb.put ( glyph );
+ oal.add ( a );
+ }
+
+ /**
+ * Put (write) array of glyphs into glyph output buffer.
+ * @param glyphs to write
+ * @param associations array of character associations that apply to glyphs
+ */
+ public void putGlyphs ( int[] glyphs, GlyphSequence.CharAssociation[] associations ) {
+ assert glyphs != null;
+ assert associations != null;
+ assert associations.length >= glyphs.length;
+ for ( int i = 0, n = glyphs.length; i < n; i++ ) {
+ putGlyph ( glyphs [ i ], associations [ i ] );
+ }
+ }
+
+ /**
+ * Obtain output glyph sequence.
+ * @return newly constructed glyph sequence comprised of original
+ * characters, output glyphs, and output associations
+ */
+ public GlyphSequence getOutput() {
+ int position = ogb.position();
+ if ( position > 0 ) {
+ ogb.limit ( position );
+ ogb.rewind();
+ return new GlyphSequence ( igs.getCharacters(), ogb, oal );
+ } else {
+ return igs;
+ }
+ }
+
+ /**
+ * Apply substitution subtable to current state at current position (only),
+ * resulting in the consumption of zero or more input glyphs, and possibly
+ * replacing the current input glyphs starting at the current position, in
+ * which case it is possible that indexLast is altered to be either less than
+ * or greater than its value prior to this application.
+ * @param st the glyph substitution subtable to apply
+ * @return true if subtable applied, or false if it did not (e.g., its
+ * input coverage table did not match current input context)
+ */
+ public boolean apply ( GlyphSubstitutionSubtable st ) {
+ assert st != null;
+ updateSubtableState ( st );
+ boolean applied = st.substitute ( this );
+ resetSubtableState();
+ return applied;
+ }
+
+ /**
+ * Apply a sequence of matched rule lookups to the <code>nig</code> input glyphs
+ * starting at the current position. If lookups are non-null and non-empty, then
+ * all input glyphs specified by <code>nig</code> are consumed irregardless of
+ * whether any specified lookup applied.
+ * @param lookups array of matched lookups (or null)
+ * @param nig number of glyphs in input sequence, starting at current position, to which
+ * the lookups are to apply, and to be consumed once the application has finished
+ * @return true if lookups are non-null and non-empty; otherwise, false
+ */
+ public boolean apply ( GlyphTable.RuleLookup[] lookups, int nig ) {
+ // int nbg = index;
+ int nlg = indexLast - ( index + nig );
+ int nog = 0;
+ if ( ( lookups != null ) && ( lookups.length > 0 ) ) {
+ // apply each rule lookup to extracted input glyph array
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ GlyphTable.RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ GlyphTable.LookupTable lt = l.getLookup();
+ if ( lt != null ) {
+ // perform substitution on a copy of previous state
+ GlyphSubstitutionState ss = new GlyphSubstitutionState ( this );
+ // apply lookup table substitutions
+ GlyphSequence gs = lt.substitute ( ss, l.getSequenceIndex() );
+ // replace current input sequence starting at current position with result
+ if ( replaceInput ( 0, -1, gs ) ) {
+ nog = gs.getGlyphCount() - nlg;
+ }
+ }
+ }
+ }
+ // output glyphs and associations
+ putGlyphs ( getGlyphs ( 0, nog, false, null, null, null ), getAssociations ( 0, nog, false, null, null, null ) );
+ // consume replaced input glyphs
+ consume ( nog );
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Apply default application semantices; namely, consume one input glyph,
+ * writing that glyph (and its association) to the output glyphs (and associations).
+ */
+ public void applyDefault() {
+ super.applyDefault();
+ int gi = getGlyph();
+ if ( gi != 65535 ) {
+ putGlyph ( gi, getAssociation() );
+ }
+ }
+
+ private static IntBuffer growBuffer ( IntBuffer ib ) {
+ int capacity = ib.capacity();
+ int capacityNew = capacity * 2;
+ IntBuffer ibNew = IntBuffer.allocate ( capacityNew );
+ ib.rewind();
+ return ibNew.put ( ib );
+ }
+
+}
package org.apache.fop.fonts;
// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
/**
* The <code>GlyphSubstitutionSubtable</code> implements an abstract base of a glyph substitution subtable,
}
/** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
- if ( gs == null ) {
- throw new IllegalArgumentException ( "invalid glyph sequence: must not be null" );
- } else {
- return gs;
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof GlyphSubstitutionSubtable;
+ }
+
+ /** {@inheritDoc} */
+ public boolean usesReverseScan() {
+ return false;
+ }
+
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ return false;
+ }
+
+ /**
+ * Apply substitutions using specified state and subtable array. For each position in input sequence,
+ * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+ * input was consumed for a given position, then apply default action (copy input glyph and advance).
+ * If <code>sequenceIndex</code> is non-negative, then apply subtables only when current position
+ * matches <code>sequenceIndex</code> in relation to the starting position. Furthermore, upon
+ * successful application at <code>sequenceIndex</code>, then apply default action for all remaining
+ * glyphs in input sequence.
+ * @param ss substitution state
+ * @param sta array of subtables to apply
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return output glyph sequence
+ */
+ public static final GlyphSequence substitute ( GlyphSubstitutionState ss, GlyphSubstitutionSubtable[] sta, int sequenceIndex ) {
+ int sequenceStart = ss.getPosition();
+ boolean appliedOneShot = false;
+ while ( ss.hasNext() ) {
+ boolean applied = false;
+ if ( ! appliedOneShot && ss.maybeApplicable() ) {
+ for ( int i = 0, n = sta.length; ! applied && ( i < n ); i++ ) {
+ if ( sequenceIndex < 0 ) {
+ applied = ss.apply ( sta [ i ] );
+ } else if ( ss.getPosition() == ( sequenceStart + sequenceIndex ) ) {
+ applied = ss.apply ( sta [ i ] );
+ if ( applied ) {
+ appliedOneShot = true;
+ }
+ }
+ }
+ }
+ if ( ! applied || ! ss.didConsume() ) {
+ ss.applyDefault();
+ }
+ ss.next();
}
+ return ss.getOutput();
+ }
+
+ static final GlyphSequence substitute ( GlyphSequence gs, String script, String language, String feature, GlyphSubstitutionSubtable[] sta, ScriptContextTester sct ) {
+ return substitute ( new GlyphSubstitutionState ( gs, script, language, feature, sct ), sta, -1 );
}
}
import java.util.List;
import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
/**
* The <code>GlyphSubstitutionTable</code> class is a glyph table that implements
* <code>GlyphSubstitution</code> functionality.
* @author Glenn Adams
*/
-public class GlyphSubstitutionTable extends GlyphTable implements GlyphSubstitution {
+public class GlyphSubstitutionTable extends GlyphTable {
+
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphSubstitutionTable.class); // CSOK: ConstantNameCheck
/** single substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_SINGLE = 1;
public static final int GSUB_LOOKUP_TYPE_ALTERNATE = 3;
/** ligature substitution subtable type */
public static final int GSUB_LOOKUP_TYPE_LIGATURE = 4;
- /** context substitution subtable type */
- public static final int GSUB_LOOKUP_TYPE_CONTEXT = 5;
- /** chaining context substitution subtable type */
- public static final int GSUB_LOOKUP_TYPE_CHAINING_CONTEXT = 6;
+ /** 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 chaining context single substitution subtable type */
- public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE = 8;
+ /** reverse chained contextual single substitution subtable type */
+ public static final int GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE = 8;
/**
* 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 ( Map lookups, List subtables ) {
- super ( lookups );
+ public GlyphSubstitutionTable ( GlyphDefinitionTable gdef, Map lookups, List subtables ) {
+ super ( gdef, lookups );
if ( ( subtables == null ) || ( subtables.size() == 0 ) ) {
throw new IllegalArgumentException ( "subtables must be non-empty" );
} else {
throw new IllegalArgumentException ( "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/*<LookupSpec,List<LookupTable>>*/ lookups = matchLookups ( script, language, "*" );
+ if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
+ ScriptProcessor sp = ScriptProcessor.getInstance ( script );
+ ogs = sp.substitute ( this, gs, script, language, lookups );
+ } else {
+ ogs = gs;
}
+ return ogs;
}
/**
t = GSUB_LOOKUP_TYPE_ALTERNATE;
} else if ( "ligature".equals ( s ) ) {
t = GSUB_LOOKUP_TYPE_LIGATURE;
- } else if ( "context".equals ( s ) ) {
- t = GSUB_LOOKUP_TYPE_CONTEXT;
- } else if ( "chainingcontext".equals ( s ) ) {
- t = GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+ } 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 ( "reversechainiingcontextsingle".equals ( s ) ) {
- t = GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE;
+ } else if ( "reversechainiingcontextualsingle".equals ( s ) ) {
+ t = GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
} else {
t = -1;
}
case GSUB_LOOKUP_TYPE_LIGATURE:
tn = "ligature";
break;
- case GSUB_LOOKUP_TYPE_CONTEXT:
- tn = "context";
+ case GSUB_LOOKUP_TYPE_CONTEXTUAL:
+ tn = "contextual";
break;
- case GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
- tn = "chainingcontext";
+ case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ tn = "chainedcontextual";
break;
case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
tn = "extensionsubstitution";
break;
- case GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
- tn = "reversechainiingcontextsingle";
+ case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
+ tn = "reversechainiingcontextualsingle";
break;
default:
tn = "unknown";
* @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 ) {
+ 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 = new SimpleSubtable ( id, sequence, flags, format, coverage, entries );
+ st = SingleSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
case GSUB_LOOKUP_TYPE_MULTIPLE:
- st = new MultipleSubtable ( id, sequence, flags, format, coverage, entries );
+ st = MultipleSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
case GSUB_LOOKUP_TYPE_ALTERNATE:
- st = new AlternateSubtable ( id, sequence, flags, format, coverage, entries );
+ st = AlternateSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
case GSUB_LOOKUP_TYPE_LIGATURE:
- st = new LigatureSubtable ( id, sequence, flags, format, coverage, entries );
+ st = LigatureSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
- case GSUB_LOOKUP_TYPE_CONTEXT:
- st = new ContextSubtable ( id, sequence, flags, format, coverage, entries );
+ case GSUB_LOOKUP_TYPE_CONTEXTUAL:
+ st = ContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
- case GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
- st = new ChainingContextSubtable ( id, sequence, flags, format, coverage, entries );
- break;
- case GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
- st = new ExtensionSubtable ( id, sequence, flags, format, coverage, entries );
+ case GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL:
+ st = ChainedContextualSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
- case GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
- st = new ReverseChainingSingleSubtable ( id, sequence, flags, format, coverage, entries );
+ case GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE:
+ st = ReverseChainedSingleSubtable.create ( id, sequence, flags, format, coverage, entries );
break;
default:
break;
return st;
}
- /** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
- GlyphSequence ogs;
- Map/*<LookupSpec,GlyphSubtable[]>*/ lookups = matchLookups ( script, language, "*" );
- if ( ( lookups != null ) && ( lookups.size() > 0 ) ) {
- ScriptProcessor sp = ScriptProcessor.getInstance ( script );
- ogs = sp.substitute ( gs, script, language, lookups );
- } else {
- ogs = gs;
- }
- return ogs;
+ /**
+ * 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 );
}
- static class SimpleSubtable extends GlyphSubstitutionSubtable {
- private int[] map;
- public SimpleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
- populate ( 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 List getEntries() {
- List entries = new ArrayList ( map.length );
- for ( int i = 0, n = map.length; i < n; i++ ) {
- entries.add ( Integer.valueOf ( map[i] ) );
- }
- return entries;
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof SingleSubtable;
}
/** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
- CharBuffer cb = CharBuffer.allocate ( gs.length() );
- int ng = 0;
- for ( int i = 0; i < gs.length(); i++ ) {
- int gi = gs.charAt ( i );
- int ci, go = gi;
- if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
- assert ci < map.length : "coverage index out of range";
- if ( ci < map.length ) {
- go = map [ ci ];
- }
- }
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int go = getGlyphForCoverageIndex ( ci, gi );
if ( ( go < 0 ) || ( go > 65535 ) ) {
go = 65535;
}
- cb.put ( (char) go );
- ng++;
+ ss.putGlyph ( go, ss.getAssociation() );
+ 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 ( Integer.valueOf ( 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 IllegalArgumentException ( "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 ) . intValue();
+ } else {
+ throw new IllegalArgumentException ( "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 i = 0, n = glyphs.length; i < n; i++ ) {
+ entries.add ( Integer.valueOf ( glyphs[i] ) );
+ }
+ 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 ];
}
- cb.limit(ng);
- cb.rewind();
- return new GlyphSequence ( gs.getCharacters(), (CharSequence) cb, null );
}
private void populate ( List entries ) {
int i = 0, n = entries.size();
- int[] map = new int [ n ];
+ int[] glyphs = new int [ n ];
for ( Iterator it = entries.iterator(); it.hasNext();) {
Object o = it.next();
if ( o instanceof Integer ) {
int gid = ( (Integer) o ) .intValue();
if ( ( gid >= 0 ) && ( gid < 65536 ) ) {
- map [ i++ ] = gid;
+ glyphs [ i++ ] = gid;
} else {
throw new IllegalArgumentException ( "illegal glyph index: " + gid );
}
}
}
assert i == n;
- assert this.map == null;
- this.map = map;
+ 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 String toString() {
- StringBuffer sb = new StringBuffer(super.toString());
- sb.append('{');
- sb.append("coverage=");
- sb.append(getCoverage().toString());
- sb.append(",entries={");
- for ( int i = 0, n = map.length; i < n; i++ ) {
- if ( i > 0 ) {
- sb.append(',');
+ 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(), ci;
+ if ( ( ci = getCoverageIndex ( gi ) ) < 0 ) {
+ return false;
+ } else {
+ int[] ga = getGlyphsForCoverageIndex ( ci, gi );
+ if ( ga != null ) {
+ ss.putGlyphs ( ga, GlyphSequence.CharAssociation.replicate ( ss.getAssociation(), ga.length ) );
+ ss.consume(1);
}
- sb.append(Integer.toString(map[i]));
+ 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();
}
- sb.append('}');
- sb.append('}');
- return sb.toString();
}
}
- static class MultipleSubtable extends GlyphSubstitutionSubtable {
- public MultipleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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 int getType() {
- return GSUB_LOOKUP_TYPE_MULTIPLE;
+ public List getEntries() {
+ if ( gsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( gsa );
+ return entries;
+ } else {
+ return null;
+ }
}
/** {@inheritDoc} */
- public List getEntries() {
- return null; // [TBD] - implement me
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof int[][] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an int[][], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ gsa = (int[][]) o;
+ }
+ }
}
}
- static class AlternateSubtable extends GlyphSubstitutionSubtable {
- public AlternateSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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 List getEntries() {
- return null; // [TBD] - implement me
+ public boolean isCompatible ( GlyphSubtable subtable ) {
+ return subtable instanceof AlternateSubtable;
+ }
+ /** {@inheritDoc} */
+ public boolean substitute ( GlyphSubstitutionState ss ) {
+ int gi = ss.getGlyph(), 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() );
+ 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();
+ }
}
}
- static class LigatureSubtable extends GlyphSubstitutionSubtable {
- private LigatureSet[] map;
- public LigatureSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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 int getType() {
- return GSUB_LOOKUP_TYPE_LIGATURE;
- }
- /** {@inheritDoc} */
public List getEntries() {
- List entries = new ArrayList ( map.length );
- for ( int i = 0, n = map.length; i < n; i++ ) {
- entries.add ( map[i] );
+ List entries = new ArrayList ( gaa.length );
+ for ( int i = 0, n = gaa.length; i < n; i++ ) {
+ entries.add ( gaa[i] );
}
return entries;
}
/** {@inheritDoc} */
- public GlyphSequence substitute ( GlyphSequence gs, String script, String language ) {
- CharBuffer cb = CharBuffer.allocate ( gs.length() );
- int ng = 0;
- for ( int i = 0, n = gs.length(); i < n; i++ ) {
- int gi = gs.charAt ( i );
- int ci, go = gi;
- LigatureSet ls = null;
- if ( ( ci = getCoverageIndex ( gi ) ) >= 0 ) {
- assert ci < map.length : "coverage index out of range";
- if ( ci < map.length ) {
- ls = map [ ci ];
- }
- }
- if ( ls != null ) {
- Ligature l;
- if ( ( l = findLigature ( ls, gs, i ) ) != null ) {
- go = l.getLigature();
- i += l.getNumComponents();
- }
- }
- if ( ( go < 0 ) || ( go > 65535 ) ) {
- go = 65535;
- }
- cb.put ( (char) go );
- ng++;
+ 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 ];
}
- cb.limit(ng);
- cb.rewind();
- return new GlyphSequence ( gs.getCharacters(), (CharSequence) cb, null );
}
private void populate ( List entries ) {
int i = 0, n = entries.size();
- LigatureSet[] map = new LigatureSet [ n ];
+ int[][] gaa = new int [ n ][];
for ( Iterator it = entries.iterator(); it.hasNext();) {
Object o = it.next();
- if ( o instanceof LigatureSet ) {
- map [ i++ ] = (LigatureSet) o;
+ if ( o instanceof int[] ) {
+ gaa [ i++ ] = (int[]) o;
} else {
- throw new IllegalArgumentException ( "illegal ligatures entry, must be LigatureSet: " + o );
+ throw new IllegalArgumentException ( "illegal entries entry, must be int[]: " + o );
}
}
assert i == n;
- assert this.map == null;
- this.map = map;
+ 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(), 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], 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
+ GlyphSequence.CharAssociation[] laa = ss.getAssociations ( 0, nga );
+ // output ligature glyph and its association
+ ss.putGlyph ( go, GlyphSequence.CharAssociation.join ( laa ) );
+ // fetch and output ignored glyphs (if necessary)
+ if ( ngi > 0 ) {
+ ss.putGlyphs ( ss.getIgnoredGlyphs ( 0, ngi ), ss.getIgnoredAssociations ( 0, ngi ) );
+ }
+ ss.consume ( nga + ngi );
+ }
+ }
+ }
+ return true;
+ }
}
- private Ligature findLigature ( LigatureSet ls, CharSequence cs, int offset ) {
+ 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 ( cs, offset + 1 ) ) {
+ if ( l.matchesComponents ( glyphs ) ) {
int nc = l.getNumComponents();
if ( nc > maxComponents ) {
maxComponents = nc;
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 String toString() {
- StringBuffer sb = new StringBuffer(super.toString());
- sb.append('{');
- sb.append("coverage=");
- sb.append(getCoverage().toString());
- sb.append(",entries={");
- for ( int i = 0, n = map.length; i < n; i++ ) {
- if ( i > 0 ) {
- sb.append(',');
+ public List getEntries() {
+ List entries = new ArrayList ( ligatureSets.length );
+ for ( int i = 0, n = ligatureSets.length; i < n; i++ ) {
+ entries.add ( ligatureSets[i] );
+ }
+ 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, n = entries.size();
+ LigatureSet[] ligatureSets = new LigatureSet [ n ];
+ for ( Iterator it = entries.iterator(); it.hasNext();) {
+ Object o = it.next();
+ if ( o instanceof LigatureSet ) {
+ ligatureSets [ i++ ] = (LigatureSet) o;
+ } else {
+ throw new IllegalArgumentException ( "illegal ligatures entry, must be LigatureSet: " + o );
}
- sb.append(map[i]);
}
- sb.append('}');
- sb.append('}');
- return sb.toString();
+ assert i == n;
+ assert this.ligatureSets == null;
+ this.ligatureSets = ligatureSets;
}
}
- static class ContextSubtable extends GlyphSubstitutionSubtable {
- public ContextSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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_CONTEXT;
+ 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(), 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() {
- return null; // [TBD] - implement me
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
}
}
- static class ChainingContextSubtable extends GlyphSubstitutionSubtable {
- public ChainingContextSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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 int getType() {
- return GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+ public List getEntries() {
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 3 );
+ entries.add ( cdt );
+ entries.add ( Integer.valueOf ( ngc ) );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 3 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 3 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "illegal entries, second entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(2) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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() {
- return null; // [TBD] - implement me
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
}
}
- static class ExtensionSubtable extends GlyphSubstitutionSubtable {
- public ExtensionSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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_EXTENSION_SUBSTITUTION;
+ 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(), 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/*<String,LookupTable>*/ 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 ( Integer.valueOf ( 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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/*<String,LookupTable>*/ lookupTables ) {
+ GlyphTable.resolveLookupReferences ( rsa, lookupTables );
+ }
+ private void populate ( List entries ) {
+ if ( entries == null ) {
+ throw new IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 5 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 5 entries" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof GlyphClassTable ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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 IllegalArgumentException ( "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 IllegalArgumentException ( "illegal entries, fourth entry must be an Integer, but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ ngc = ((Integer)(o)).intValue();
+ }
+ if ( ( ( o = entries.get(4) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "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 IllegalArgumentException ( "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() {
- return null; // [TBD] - implement me
+ if ( rsa != null ) {
+ List entries = new ArrayList ( 1 );
+ entries.add ( rsa );
+ return entries;
+ } else {
+ return null;
+ }
+ }
+ /** {@inheritDoc} */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ 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 ( int i = 0, n = ra.length; i < n; i++ ) {
+ Rule r = ra [ i ];
+ 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 IllegalArgumentException ( "illegal entries, must be non-null" );
+ } else if ( entries.size() != 1 ) {
+ throw new IllegalArgumentException ( "illegal entries, " + entries.size() + " entries present, but requires 1 entry" );
+ } else {
+ Object o;
+ if ( ( ( o = entries.get(0) ) == null ) || ! ( o instanceof RuleSet[] ) ) {
+ throw new IllegalArgumentException ( "illegal entries, first entry must be an RuleSet[], but is: " + ( ( o != null ) ? o.getClass() : null ) );
+ } else {
+ rsa = (RuleSet[]) o;
+ }
+ }
}
}
- static class ReverseChainingSingleSubtable extends GlyphSubstitutionSubtable {
- public ReverseChainingSingleSubtable ( String id, int sequence, int flags, int format, List coverage, List entries ) {
- super ( id, sequence, flags, format, GlyphCoverageTable.createCoverageTable ( coverage ) );
+ 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_CHAINING_CONTEXT_SINGLE;
+ 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; // [TBD] - implement me
+ return null;
+ }
+ private void populate ( List entries ) {
}
}
throw new IllegalArgumentException ( "invalid ligature components, must be non-empty array" );
} else {
for ( int i = 0, n = components.length; i < n; i++ ) {
- int c = components [ i ];
- if ( ( c < 0 ) || ( c > 65535 ) ) {
- throw new IllegalArgumentException ( "invalid component glyph index: " + c );
+ int gc = components [ i ];
+ if ( ( gc < 0 ) || ( gc > 65535 ) ) {
+ throw new IllegalArgumentException ( "invalid component glyph index: " + gc );
}
}
this.ligature = ligature;
}
/**
- * Determine of input sequence at offset matches ligature's components.
- * @param cs glyph (or character) sequence to match this ligature against
- * @param offset index at which to start matching the components of this ligature
+ * 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 ( CharSequence cs, int offset ) {
- if ( ( offset + components.length ) > cs.length() ) {
+ 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 ( (int) cs.charAt ( offset + i ) != components [ i ] ) {
+ if ( glyphs [ i + 1 ] != components [ i ] ) {
return false;
}
}
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.
throw new IllegalArgumentException ( "invalid ligatures, must be non-empty array" );
} else {
this.ligatures = ligatures;
+ int ncMax = -1;
+ for ( int i = 0, n = ligatures.length; i < n; i++ ) {
+ Ligature l = ligatures [ i ];
+ int nc = l.getNumComponents() + 1;
+ if ( nc > ncMax ) {
+ ncMax = nc;
+ }
+ }
+ maxComponents = ncMax;
}
}
return 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();
}
}
+
package org.apache.fop.fonts;
+import java.lang.ref.WeakReference;
+
import java.util.List;
+import java.util.Map;
+// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
/**
* encapsulates identification, type, format, and coverage information.
* @author Glenn Adams
*/
-public abstract class GlyphSubtable {
+public abstract class GlyphSubtable implements Comparable {
+
+ /** lookup flag - right to left */
+ public static final int LF_RIGHT_TO_LEFT = 0x0001;
+ /** lookup flag - ignore base glyphs */
+ public static final int LF_IGNORE_BASE = 0x0002;
+ /** lookup flag - ignore ligatures */
+ public static final int LF_IGNORE_LIGATURE = 0x0004;
+ /** lookup flag - ignore marks */
+ public static final int LF_IGNORE_MARK = 0x0008;
+ /** lookup flag - use mark filtering set */
+ public static final int LF_USE_MARK_FILTERING_SET = 0x0010;
+ /** lookup flag - reserved */
+ public static final int LF_RESERVED = 0x0E00;
+ /** lookup flag - mark attachment type */
+ public static final int LF_MARK_ATTACHMENT_TYPE = 0xFF00;
+ /** internal flag - use reverse scan */
+ public static final int LF_INTERNAL_USE_REVERSE_SCAN = 0x10000;
- private String id;
+ /** lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list; shared by multiple subtables in a single lookup */
+ private String lookupId;
+ /** subtable sequence (index) number in lookup, zero based */
private int sequence;
+ /** subtable flags */
private int flags;
+ /** subtable format */
private int format;
- private GlyphCoverageTable coverage;
+ /** subtable mapping table */
+ private GlyphMappingTable mapping;
+ /** weak reference to parent (gsub or gpos) table */
+ private WeakReference table;
/**
* Instantiate this glyph subtable.
- * @param id subtable identifier
- * @param sequence subtable sequence
+ * @param lookupId lookup identifier, having form of "lu%d" where %d is index of lookup in lookup list
+ * @param sequence subtable sequence (within lookup), starting with zero
* @param flags subtable flags
* @param format subtable format
- * @param coverage subtable coverage table
+ * @param mapping subtable mapping table
*/
- protected GlyphSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage )
+ protected GlyphSubtable ( String lookupId, int sequence, int flags, int format, GlyphMappingTable mapping )
{
- if ( ( id == null ) || ( id.length() == 0 ) ) {
+ if ( ( lookupId == null ) || ( lookupId.length() == 0 ) ) {
throw new IllegalArgumentException ( "invalid lookup identifier, must be non-empty string" );
- } else if ( coverage == null ) {
- throw new IllegalArgumentException ( "invalid coverage table, must not be null" );
+ } else if ( mapping == null ) {
+ throw new IllegalArgumentException ( "invalid mapping table, must not be null" );
} else {
- this.id = id;
+ this.lookupId = lookupId;
this.sequence = sequence;
this.flags = flags;
this.format = format;
- this.coverage = coverage;
+ this.mapping = mapping;
}
}
- /** @return this subtable's identifer */
- public String getID() {
- return id;
+ /** @return this subtable's lookup identifer */
+ public String getLookupId() {
+ return lookupId;
}
/** @return this subtable's table type */
/** @return this subtable's type name */
public abstract String getTypeName();
- /** @return this subtable's sequence */
+ /**
+ * Determine if a glyph subtable is compatible with this glyph subtable. Two glyph subtables are
+ * compatible if the both may appear in a single lookup table.
+ * @param subtable a glyph subtable to determine compatibility
+ * @return true if specified subtable is compatible with this glyph subtable, where by compatible
+ * is meant that they share the same lookup type
+ */
+ public abstract boolean isCompatible ( GlyphSubtable subtable );
+
+ /** @return true if subtable uses reverse scanning of glyph sequence, meaning from the last glyph
+ * in a glyph sequence to the first glyph
+ */
+ public abstract boolean usesReverseScan();
+
+ /** @return this subtable's sequence (index) within lookup */
public int getSequence() {
return sequence;
}
return format;
}
- /** @return this subtable's coverage table */
- public GlyphCoverageTable getCoverage() {
- return coverage;
+ /** @return this subtable's governing glyph definition table or null if none available */
+ public GlyphDefinitionTable getGDEF() {
+ GlyphTable gt = getTable();
+ if ( gt != null ) {
+ return gt.getGlyphDefinitions();
+ } else {
+ return null;
+ }
+ }
+
+ /** @return this subtable's coverage mapping or null if mapping is not a coverage mapping */
+ public GlyphCoverageMapping getCoverage() {
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return (GlyphCoverageMapping) mapping;
+ } else {
+ return null;
+ }
+ }
+
+ /** @return this subtable's class mapping or null if mapping is not a class mapping */
+ public GlyphClassMapping getClasses() {
+ if ( mapping instanceof GlyphClassMapping ) {
+ return (GlyphClassMapping) mapping;
+ } else {
+ return null;
+ }
}
/** @return this subtable's lookup entries */
public abstract List getEntries();
+ /** @return this subtable's parent table (or null if undefined) */
+ public synchronized GlyphTable getTable() {
+ WeakReference r = this.table;
+ return ( r != null ) ? (GlyphTable) r.get() : null;
+ }
+
+ /**
+ * Establish a weak reference from this subtable to its parent
+ * table. If table parameter is specified as <code>null</code>, then
+ * clear and remove weak reference.
+ * @param table the table or null
+ * @throws IllegalStateException if table is already set to non-null
+ */
+ public synchronized void setTable ( GlyphTable table ) throws IllegalStateException {
+ WeakReference r = this.table;
+ if ( table == null ) {
+ this.table = null;
+ if ( r != null ) {
+ r.clear();
+ }
+ } else if ( r == null ) {
+ this.table = new WeakReference ( table );
+ } else {
+ throw new IllegalStateException ( "table already set" );
+ }
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,GlyphTable.LookupTable>*/ lookupTables ) {
+ }
+
/**
* Map glyph id to coverage index.
* @param gid glyph id
* @return the corresponding coverage index of the specified glyph id
*/
public int getCoverageIndex ( int gid ) {
- return coverage.getCoverageIndex ( gid );
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return ( (GlyphCoverageMapping) mapping ) .getCoverageIndex ( gid );
+ } else {
+ return -1;
+ }
+ }
+
+ /**
+ * Map glyph id to coverage index.
+ * @return the corresponding coverage index of the specified glyph id
+ */
+ public int getCoverageSize() {
+ if ( mapping instanceof GlyphCoverageMapping ) {
+ return ( (GlyphCoverageMapping) mapping ) .getCoverageSize();
+ } else {
+ return 0;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = sequence;
+ hc = ( hc * 3 ) + ( lookupId.hashCode() ^ hc );
+ return hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return true if the lookup identifier and the sequence number of the specified subtable is the same
+ * as the lookup identifier and sequence number of this subtable
+ */
+ public boolean equals ( Object o ) {
+ if ( o instanceof GlyphSubtable ) {
+ GlyphSubtable st = (GlyphSubtable) o;
+ return lookupId.equals ( st.lookupId ) && ( sequence == st.sequence );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return the result of comparing the lookup identifier and the sequence number of the specified subtable with
+ * the lookup identifier and sequence number of this subtable
+ */
+ public int compareTo ( Object o ) {
+ int d;
+ if ( o instanceof GlyphSubtable ) {
+ GlyphSubtable st = (GlyphSubtable) o;
+ if ( ( d = lookupId.compareTo ( st.lookupId ) ) == 0 ) {
+ if ( sequence < st.sequence ) {
+ d = -1;
+ } else if ( sequence > st.sequence ) {
+ d = 1;
+ }
+ }
+ } else {
+ d = -1;
+ }
+ return d;
+ }
+
+ /**
+ * Determine if any of the specified subtables uses reverse scanning.
+ * @param subtables array of glyph subtables
+ * @return true if any of the specified subtables uses reverse scanning.
+ */
+ public static boolean usesReverseScan ( GlyphSubtable[] subtables ) {
+ if ( ( subtables == null ) || ( subtables.length == 0 ) ) {
+ return false;
+ } else {
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ if ( subtables[i].usesReverseScan() ) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Determine consistent flags for a set of subtables.
+ * @param subtables array of glyph subtables
+ * @return consistent flags
+ * @throws IllegalStateException if inconsistent flags
+ */
+ public static int getFlags ( GlyphSubtable[] subtables ) throws IllegalStateException {
+ if ( ( subtables == null ) || ( subtables.length == 0 ) ) {
+ return 0;
+ } else {
+ int flags = 0;
+ // obtain first non-zero value of flags in array of subtables
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ int f = subtables[i].getFlags();
+ if ( flags == 0 ) {
+ flags = f;
+ break;
+ }
+ }
+ // enforce flag consistency
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ int f = subtables[i].getFlags();
+ if ( f != flags ) {
+ throw new IllegalStateException ( "inconsistent lookup flags " + f + ", expected " + flags );
+ }
+ }
+ return flags | ( usesReverseScan ( subtables ) ? LF_INTERNAL_USE_REVERSE_SCAN : 0 );
+ }
}
}
package org.apache.fop.fonts;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
-// CSOFF: NoWhitespaceAfterCheck
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+// CSOFF: EmptyForIteratorPadCheck
// CSOFF: InnerAssignmentCheck
-// CSOFF: SimplifyBooleanReturnCheck
// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+// CSOFF: SimplifyBooleanReturnCheck
/**
* Base class for all advanced typographic glyph tables.
*/
public class GlyphTable {
+ /** logging instance */
+ private static final Log log = LogFactory.getLog(GlyphTable.class); // CSOK: ConstantNameCheck
+
/** substitution glyph table type */
public static final int GLYPH_TABLE_TYPE_SUBSTITUTION = 1;
/** positioning glyph table type */
/** definition glyph table type */
public static final int GLYPH_TABLE_TYPE_DEFINITION = 5;
- // map from lookup specs to lists of strings, each naming a subtable
- private Map /*<LookupSpec,List>*/ lookups;
+ // (optional) glyph definition table in table types other than glyph definition table
+ private GlyphTable gdef;
+
+ // map from lookup specs to lists of strings, each of which identifies a lookup table (consisting of one or more subtables)
+ private Map/*<LookupSpec,List<String>>*/ lookups;
- // map from subtable names to glyph subtables
- private Map /*<String,GlyphSubtable>*/ subtables;
+ // map from lookup identifiers to lookup tables
+ private Map/*<String,LookupTable>*/ lookupTables;
+
+ // if true, then prevent further subtable addition
+ private boolean frozen;
/**
* Instantiate glyph table with specified lookups.
+ * @param gdef glyph definition table that applies
* @param lookups map from lookup specs to lookup tables
*/
- public GlyphTable ( Map /*<LookupSpec,List>*/ lookups ) {
- if ( ( lookups == null ) || ( lookups.size() == 0 ) ) {
- throw new IllegalArgumentException ( "lookups must be non-empty map" );
+ public GlyphTable ( GlyphTable gdef, Map/*<LookupSpec,List<String>>*/ lookups ) {
+ if ( ( gdef != null ) && ! ( gdef instanceof GlyphDefinitionTable ) ) {
+ throw new IllegalArgumentException ( "bad glyph definition table" );
+ } else if ( lookups == null ) {
+ throw new IllegalArgumentException ( "lookups must be non-null map" );
} else {
+ this.gdef = gdef;
this.lookups = lookups;
- this.subtables = new LinkedHashMap();
+ this.lookupTables = new LinkedHashMap/*<String,List<LookupTable>>*/();
}
}
/**
- * Obain array of lookup specifications.
- * @return (possibly empty) array of all lookup specifications
+ * Obtain glyph definition table.
+ * @return (possibly null) glyph definition table
*/
- public LookupSpec[] getLookups() {
+ public GlyphDefinitionTable getGlyphDefinitions() {
+ return (GlyphDefinitionTable) gdef;
+ }
+
+ /**
+ * Obtain list of all lookup specifications.
+ * @return (possibly empty) list of all lookup specifications
+ */
+ public List/*<LookupSpec>*/ getLookups() {
return matchLookupSpecs ( "*", "*", "*" );
}
/**
- * Obain array of lookup subtables.
- * @return (possibly empty) array of all lookup subtables
+ * Obtain ordered list of all lookup tables, where order is by lookup identifier, which
+ * lexicographic ordering follows the lookup list order.
+ * @return (possibly empty) ordered list of all lookup tables
*/
- public GlyphSubtable[] getSubtables() {
- Collection values = subtables.values();
- return (GlyphSubtable[]) values.toArray ( new GlyphSubtable [ values.size() ] );
+ public List/*<LookupTable>*/ getLookupTables() {
+ TreeSet/*<String>*/ lids = new TreeSet/*<String>*/ ( lookupTables.keySet() );
+ List/*<LookupTable>*/ ltl = new ArrayList/*<LookupTable>*/ ( lids.size() );
+ for ( Iterator it = lids.iterator(); it.hasNext(); ) {
+ String lid = (String) it.next();
+ ltl.add ( lookupTables.get ( lid ) );
+ }
+ return ltl;
}
/**
* Add a subtable.
* @param subtable a (non-null) glyph subtable
*/
- public void addSubtable ( GlyphSubtable subtable ) {
- subtables.put ( subtable.getID(), subtable );
+ protected void addSubtable ( GlyphSubtable subtable ) {
+ // ensure table is not frozen
+ if ( frozen ) {
+ throw new IllegalStateException ( "glyph table is frozen, subtable addition prohibited" );
+ }
+ // set subtable's table reference to this table
+ subtable.setTable ( this );
+ // add subtable to this table's subtable collection
+ String lid = subtable.getLookupId();
+ if ( lookupTables.containsKey ( lid ) ) {
+ LookupTable lt = (LookupTable) lookupTables.get ( lid );
+ lt.addSubtable ( subtable );
+ } else {
+ LookupTable lt = new LookupTable ( lid, subtable );
+ lookupTables.put ( lid, lt );
+ }
+ }
+
+ /**
+ * Freeze subtables, i.e., do not allow further subtable addition, and
+ * create resulting cached state.
+ */
+ protected void freezeSubtables() {
+ if ( ! frozen ) {
+ for ( Iterator it = lookupTables.values().iterator(); it.hasNext(); ) {
+ LookupTable lt = (LookupTable) it.next();
+ lt.freezeSubtables ( lookupTables );
+ }
+ frozen = true;
+ }
}
/**
* @param feature a feature identifier
* @return a (possibly empty) array of matching lookup specifications
*/
- public LookupSpec[] matchLookupSpecs ( String script, String language, String feature ) {
+ public List/*<LookupSpec>*/ matchLookupSpecs ( String script, String language, String feature ) {
Set/*<LookupSpec>*/ keys = lookups.keySet();
- List matches = new ArrayList();
+ List/*<LookupSpec>*/ matches = new ArrayList/*<LookupSpec>*/();
for ( Iterator it = keys.iterator(); it.hasNext();) {
LookupSpec ls = (LookupSpec) it.next();
if ( ! "*".equals(script) ) {
}
matches.add ( ls );
}
- return (LookupSpec[]) matches.toArray ( new LookupSpec [ matches.size() ] );
+ return matches;
}
/**
* @param script a script identifier
* @param language a language identifier
* @param feature a feature identifier
- * @return a (possibly empty) map of matching lookup specifications and their corresponding subtables
+ * @return a (possibly empty) map from matching lookup specifications to lists of corresponding lookup tables
*/
- public Map/*<LookupSpec,GlyphSubtable[]>*/ matchLookups ( String script, String language, String feature ) {
- LookupSpec[] lsa = matchLookupSpecs ( script, language, feature );
+ public Map/*<LookupSpec,List<LookupTable>>*/ matchLookups ( String script, String language, String feature ) {
+ List/*<LookupSpec>*/ lsl = matchLookupSpecs ( script, language, feature );
Map lm = new LinkedHashMap();
- for ( int i = 0, n = lsa.length; i < n; i++ ) {
- lm.put ( lsa [ i ], findSubtables ( lsa [ i ] ) );
+ for ( Iterator it = lsl.iterator(); it.hasNext(); ) {
+ LookupSpec ls = (LookupSpec) it.next();
+ lm.put ( ls, findLookupTables ( ls ) );
}
return lm;
}
/**
- * Find glyph subtables that match a secific lookup specification.
+ * Obtain ordered list of glyph lookup tables that match a specific lookup specification.
* @param ls a (non-null) lookup specification
- * @return a (possibly empty) array of subtables whose lookup specification matches the specified lookup spec
+ * @return a (possibly empty) ordered list of lookup tables whose corresponding lookup specifications match the specified lookup spec
*/
- public GlyphSubtable[] findSubtables ( LookupSpec ls ) {
- GlyphSubtable[] staEmpty = new GlyphSubtable [ 0 ];
- List ids;
- if ( ( ids = (List) lookups.get ( ls ) ) != null ) {
- List stl = new ArrayList();
+ public List/*<LookupTable>*/ findLookupTables ( LookupSpec ls ) {
+ TreeSet/*<LookupTable>*/ lts = new TreeSet/*<LookupTable>*/();
+ List/*<String>*/ ids;
+ if ( ( ids = (List/*<String>*/) lookups.get ( ls ) ) != null ) {
for ( Iterator it = ids.iterator(); it.hasNext();) {
- String id = (String) it.next();
- GlyphSubtable st;
- if ( ( st = (GlyphSubtable) subtables.get ( id ) ) != null ) {
- stl.add ( st );
+ String lid = (String) it.next();
+ LookupTable lt;
+ if ( ( lt = (LookupTable) lookupTables.get ( lid ) ) != null ) {
+ lts.add ( lt );
+ }
+ }
+ }
+ return new ArrayList/*<LookupTable>*/ ( lts );
+ }
+
+ /**
+ * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
+ * where the order of the array is in accordance to the order of the applicable lookup list.
+ * @param features array of feature identifiers to apply
+ * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
+ * @return ordered array of assembled lookup table use specifications
+ */
+ public UseSpec[] assembleLookups ( String[] features, Map/*<LookupSpec,List<LookupTable>>*/ lookups ) {
+ TreeSet/*<UseSpec>*/ uss = new TreeSet/*<UseSpec>*/();
+ for ( int i = 0, n = features.length; i < n; i++ ) {
+ String feature = features[i];
+ for ( Iterator it = lookups.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry/*<LookupSpec,List<LookupTable>>*/ e = (Map.Entry/*<LookupSpec,List<LookupTable>>*/) it.next();
+ LookupSpec ls = (LookupSpec) e.getKey();
+ if ( ls.getFeature().equals ( feature ) ) {
+ List/*<LookupTable>*/ ltl = (List/*<LookupTable>*/) e.getValue();
+ if ( ltl != null ) {
+ for ( Iterator ltit = ltl.iterator(); ltit.hasNext(); ) {
+ LookupTable lt = (LookupTable) ltit.next();
+ uss.add ( new UseSpec ( lt, feature ) );
+ }
+ }
}
}
- return (GlyphSubtable[]) stl.toArray ( staEmpty );
- } else {
- return staEmpty;
}
+ return (UseSpec[]) uss.toArray ( new UseSpec [ uss.size() ] );
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer(super.toString());
+ sb.append("{");
+ sb.append("lookups={");
+ sb.append(lookups.toString());
+ sb.append("},lookupTables={");
+ sb.append(lookupTables.toString());
+ sb.append("}}");
+ return sb.toString();
}
/**
return t;
}
- /** {@inheritDoc} */
- public String toString() {
- StringBuffer sb = new StringBuffer(super.toString());
- sb.append("{");
- sb.append("lookups={");
- sb.append(lookups.toString());
- sb.append("},subtables={");
- sb.append(subtables.toString());
- sb.append("}}");
- return sb.toString();
+ /**
+ * Resolve references to lookup tables in a collection of rules sets.
+ * @param rsa array of rule sets
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public static void resolveLookupReferences ( RuleSet[] rsa, Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( ( rsa != null ) && ( lookupTables != null ) ) {
+ for ( int i = 0, n = rsa.length; i < n; i++ ) {
+ RuleSet rs = rsa [ i ];
+ if ( rs != null ) {
+ rs.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
}
/**
* A structure class encapsulating a lookup specification as a <script,language,feature> tuple.
*/
- public static class LookupSpec {
+ public static class LookupSpec implements Comparable {
private final String script;
private final String language;
throw new IllegalArgumentException ( "language must be non-empty string" );
} else if ( ( feature == null ) || ( feature.length() == 0 ) ) {
throw new IllegalArgumentException ( "feature must be non-empty string" );
+ } else if ( script.equals("*") ) {
+ throw new IllegalArgumentException ( "script must not be wildcard" );
+ } else if ( language.equals("*") ) {
+ throw new IllegalArgumentException ( "language must not be wildcard" );
+ } else if ( feature.equals("*") ) {
+ throw new IllegalArgumentException ( "feature must not be wildcard" );
} else {
this.script = script;
this.language = language;
/** {@inheritDoc} */
public int hashCode() {
- int h = 0;
- h = 31 * h + script.hashCode();
- h = 31 * h + language.hashCode();
- h = 31 * h + feature.hashCode();
- return h;
+ int hc = 0;
+ hc = 7 * hc + ( hc ^ script.hashCode() );
+ hc = 11 * hc + ( hc ^ language.hashCode() );
+ hc = 17 * hc + ( hc ^ feature.hashCode() );
+ return hc;
}
/** {@inheritDoc} */
}
}
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ int d;
+ if ( o instanceof LookupSpec ) {
+ LookupSpec ls = (LookupSpec) o;
+ if ( ( d = script.compareTo ( ls.script ) ) == 0 ) {
+ if ( ( d = language.compareTo ( ls.language ) ) == 0 ) {
+ if ( ( d = feature.compareTo ( ls.feature ) ) == 0 ) {
+ d = 0;
+ }
+ }
+ }
+ } else {
+ d = -1;
+ }
+ return d;
+ }
+
/** {@inheritDoc} */
public String toString() {
StringBuffer sb = new StringBuffer(super.toString());
}
+ /**
+ * The <code>LookupTable</code> class comprising an identifier and an ordered list
+ * of glyph subtables, each of which employ the same lookup identifier.
+ */
+ public static class LookupTable implements Comparable {
+
+ private final String id; // lookup identifiers
+ private final List/*<GlyphSubtable>*/ subtables; // list of subtables
+ private boolean doesSub; // performs substitutions
+ private boolean doesPos; // performs positioning
+ private boolean frozen; // if true, then don't permit further subtable additions
+ // frozen state
+ private GlyphSubtable[] subtablesArray;
+ private static GlyphSubtable[] subtablesArrayEmpty = new GlyphSubtable[0];
+
+ /**
+ * Instantiate a LookupTable.
+ * @param id the lookup table's identifier
+ * @param subtable an initial subtable (or null)
+ */
+ public LookupTable ( String id, GlyphSubtable subtable ) {
+ this ( id, makeSingleton ( subtable ) );
+ }
+
+ /**
+ * Instantiate a LookupTable.
+ * @param id the lookup table's identifier
+ * @param subtables a pre-poplated list of subtables or null
+ */
+ public LookupTable ( String id, List/*<GlyphSubtable>*/ subtables ) {
+ assert id != null;
+ assert id.length() != 0;
+ this.id = id;
+ this.subtables = new LinkedList/*<GlyphSubtable>*/();
+ if ( subtables != null ) {
+ for ( Iterator it = subtables.iterator(); it.hasNext(); ) {
+ GlyphSubtable st = (GlyphSubtable) it.next();
+ addSubtable ( st );
+ }
+ }
+ }
+
+ /** @return the identifier */
+ public String getId() {
+ return id;
+ }
+
+ /** @return the subtables as an array */
+ public GlyphSubtable[] getSubtables() {
+ if ( frozen ) {
+ return ( subtablesArray != null ) ? subtablesArray : subtablesArrayEmpty;
+ } else {
+ if ( doesSub ) {
+ return (GlyphSubtable[]) subtables.toArray ( new GlyphSubstitutionSubtable [ subtables.size() ] );
+ } else if ( doesPos ) {
+ return (GlyphSubtable[]) subtables.toArray ( new GlyphPositioningSubtable [ subtables.size() ] );
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * Add a subtable into this lookup table's collecion of subtables according to its
+ * natural order.
+ * @param subtable to add
+ * @return true if subtable was not already present, otherwise false
+ */
+ public boolean addSubtable ( GlyphSubtable subtable ) {
+ boolean added = false;
+ // ensure table is not frozen
+ if ( frozen ) {
+ throw new IllegalStateException ( "glyph table is frozen, subtable addition prohibited" );
+ }
+ // validate subtable to ensure consistency with current subtables
+ validateSubtable ( subtable );
+ // insert subtable into ordered list
+ for ( ListIterator/*<GlyphSubtable>*/ lit = subtables.listIterator(0); lit.hasNext(); ) {
+ GlyphSubtable st = (GlyphSubtable) lit.next();
+ int d;
+ if ( ( d = subtable.compareTo ( st ) ) < 0 ) {
+ // insert within list
+ lit.set ( subtable );
+ lit.add ( st );
+ added = true;
+ } else if ( d == 0 ) {
+ // duplicate entry is ignored
+ added = false; subtable = null;
+ }
+ }
+ // append at end of list
+ if ( ! added && ( subtable != null ) ) {
+ subtables.add ( subtable );
+ added = true;
+ }
+ return added;
+ }
+
+ private void validateSubtable ( GlyphSubtable subtable ) {
+ if ( subtable == null ) {
+ throw new IllegalArgumentException ( "subtable must be non-null" );
+ }
+ if ( subtable instanceof GlyphSubstitutionSubtable ) {
+ if ( doesPos ) {
+ throw new IllegalArgumentException ( "subtable must be positioning subtable, but is: " + subtable );
+ } else {
+ doesSub = true;
+ }
+ }
+ if ( subtable instanceof GlyphPositioningSubtable ) {
+ if ( doesSub ) {
+ throw new IllegalArgumentException ( "subtable must be substitution subtable, but is: " + subtable );
+ } else {
+ doesPos = true;
+ }
+ }
+ if ( subtables.size() > 0 ) {
+ GlyphSubtable st = (GlyphSubtable) subtables.get(0);
+ if ( ! st.isCompatible ( subtable ) ) {
+ throw new IllegalArgumentException ( "subtable " + subtable + " is not compatible with subtable " + st );
+ }
+ }
+ }
+
+ /**
+ * Freeze subtables, i.e., do not allow further subtable addition, and
+ * create resulting cached state. In addition, resolve any references to
+ * lookup tables that appear in this lookup table's subtables.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void freezeSubtables ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( ! frozen ) {
+ GlyphSubtable[] sta = getSubtables();
+ resolveLookupReferences ( sta, lookupTables );
+ this.subtablesArray = sta;
+ this.frozen = true;
+ }
+ }
+
+ private void resolveLookupReferences ( GlyphSubtable[] subtables, Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( subtables != null ) {
+ for ( int i = 0, n = subtables.length; i < n; i++ ) {
+ GlyphSubtable st = subtables [ i ];
+ if ( st != null ) {
+ st.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /**
+ * Determine if this glyph table performs substitution.
+ * @return true if it performs substitution
+ */
+ public boolean performsSubstitution() {
+ return doesSub;
+ }
+
+ /**
+ * Perform substitution processing using this lookup table's subtables.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language, String feature, ScriptContextTester sct ) {
+ if ( performsSubstitution() ) {
+ return GlyphSubstitutionSubtable.substitute ( gs, script, language, feature, (GlyphSubstitutionSubtable[]) subtablesArray, sct );
+ } else {
+ return gs;
+ }
+ }
+
+ /**
+ * Perform substitution processing on an existing glyph substitution state object using this lookup table's subtables.
+ * @param ss a glyph substitution state object
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSubstitutionState ss, int sequenceIndex ) {
+ if ( performsSubstitution() ) {
+ return GlyphSubstitutionSubtable.substitute ( ss, (GlyphSubstitutionSubtable[]) subtablesArray, sequenceIndex );
+ } else {
+ return ss.getInput();
+ }
+ }
+
+ /**
+ * Determine if this glyph table performs positioning.
+ * @return true if it performs positioning
+ */
+ public boolean performsPositioning() {
+ return doesPos;
+ }
+
+ /**
+ * Perform positioning processing using this lookup table's subtables.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param feature a feature identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, String feature, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ if ( performsPositioning() ) {
+ return GlyphPositioningSubtable.position ( gs, script, language, feature, fontSize, (GlyphPositioningSubtable[]) subtablesArray, widths, adjustments, sct );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Perform positioning processing on an existing glyph positioning state object using this lookup table's subtables.
+ * @param ps a glyph positioning state object
+ * @param sequenceIndex if non negative, then apply subtables only at specified sequence index
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphPositioningState ps, int sequenceIndex ) {
+ if ( performsPositioning() ) {
+ return GlyphPositioningSubtable.position ( ps, (GlyphPositioningSubtable[]) subtablesArray, sequenceIndex );
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return true if identifier of the specified lookup table is the same
+ * as the identifier of this lookup table
+ */
+ public boolean equals ( Object o ) {
+ if ( o instanceof LookupTable ) {
+ LookupTable lt = (LookupTable) o;
+ return id.equals ( lt.id );
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ * @return the result of comparing the identifier of the specified lookup table with
+ * the identifier of this lookup table
+ */
+ public int compareTo ( Object o ) {
+ if ( o instanceof LookupTable ) {
+ LookupTable lt = (LookupTable) o;
+ return id.compareTo ( lt.id );
+ } else {
+ return -1;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "id = " + id );
+ sb.append ( ", subtables = " + subtables );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ private static List/*<GlyphSubtable>*/ makeSingleton ( GlyphSubtable subtable ) {
+ if ( subtable == null ) {
+ return null;
+ } else {
+ List/*<GlyphSubtable>*/ stl = new ArrayList/*<GlyphSubtable>*/ ( 1 );
+ stl.add ( subtable );
+ return stl;
+ }
+ }
+
+ }
+
+ /**
+ * The <code>UseSpec</code> class comprises a lookup table reference
+ * and the feature that selected the lookup table.
+ */
+ public static class UseSpec implements Comparable {
+
+ /** lookup table to apply */
+ private final LookupTable lookupTable;
+ /** feature that caused selection of the lookup table */
+ private final String feature;
+
+ /**
+ * Construct a glyph lookup table use specification.
+ * @param lookupTable a glyph lookup table
+ * @param feature a feature that caused lookup table selection
+ */
+ public UseSpec ( LookupTable lookupTable, String feature ) {
+ this.lookupTable = lookupTable;
+ this.feature = feature;
+ }
+
+ /** @return the lookup table */
+ public LookupTable getLookupTable() {
+ return lookupTable;
+ }
+
+ /** @return the feature that selected this lookup table */
+ public String getFeature() {
+ return feature;
+ }
+
+ /**
+ * Perform substitution processing using this use specification's lookup table.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ public GlyphSequence substitute ( GlyphSequence gs, String script, String language, ScriptContextTester sct ) {
+ return lookupTable.substitute ( gs, script, language, feature, sct );
+ }
+
+ /**
+ * Perform positioning processing using this use specification's lookup table.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public boolean position ( GlyphSequence gs, String script, String language, int fontSize, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ return lookupTable.position ( gs, script, language, feature, fontSize, widths, adjustments, sct );
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ return lookupTable.hashCode();
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof UseSpec ) {
+ UseSpec u = (UseSpec) o;
+ return lookupTable.equals ( u.lookupTable );
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ if ( o instanceof UseSpec ) {
+ UseSpec u = (UseSpec) o;
+ return lookupTable.compareTo ( u.lookupTable );
+ } else {
+ return -1;
+ }
+ }
+
+ }
+
+ /**
+ * The <code>RuleLookup</code> class implements a rule lookup record, comprising
+ * a glyph sequence index and a lookup table index (in an applicable lookup list).
+ */
+ public static class RuleLookup {
+
+ private final int sequenceIndex; // index into input glyph sequence
+ private final int lookupIndex; // lookup list index
+ private LookupTable lookup; // resolved lookup table
+
+ /**
+ * Instantiate a RuleLookup.
+ * @param sequenceIndex the index into the input sequence
+ * @param lookupIndex the lookup table index
+ */
+ public RuleLookup ( int sequenceIndex, int lookupIndex ) {
+ this.sequenceIndex = sequenceIndex;
+ this.lookupIndex = lookupIndex;
+ this.lookup = null;
+ }
+
+ /** @return the sequence index */
+ public int getSequenceIndex() {
+ return sequenceIndex;
+ }
+
+ /** @return the lookup index */
+ public int getLookupIndex() {
+ return lookupIndex;
+ }
+
+ /** @return the lookup table */
+ public LookupTable getLookup() {
+ return lookup;
+ }
+
+ /**
+ * Resolve references to lookup tables.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( lookupTables != null ) {
+ String lid = "lu" + Integer.toString ( lookupIndex );
+ LookupTable lt = (LookupTable) lookupTables.get ( lid );
+ if ( lt != null ) {
+ this.lookup = lt;
+ } else {
+ log.warn ( "unable to resolve glyph lookup table reference '" + lid + "' amongst lookup tables: " + lookupTables.values() );
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ sequenceIndex = " + sequenceIndex + ", lookupIndex = " + lookupIndex + " }";
+ }
+
+ }
+
+ /**
+ * The <code>Rule</code> class implements an array of rule lookup records.
+ */
+ public abstract static class Rule {
+
+ private final RuleLookup[] lookups; // rule lookups
+ private final int inputSequenceLength; // input sequence length
+
+ /**
+ * Instantiate a Rule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength the number of glyphs in the input sequence for this rule
+ */
+ protected Rule ( RuleLookup[] lookups, int inputSequenceLength ) {
+ assert lookups != null;
+ this.lookups = lookups;
+ this.inputSequenceLength = inputSequenceLength;
+ }
+
+ /** @return the lookups */
+ public RuleLookup[] getLookups() {
+ return lookups;
+ }
+
+ /** @return the input sequence length */
+ public int getInputSequenceLength() {
+ return inputSequenceLength;
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( lookups != null ) {
+ for ( int i = 0, n = lookups.length; i < n; i++ ) {
+ RuleLookup l = lookups [ i ];
+ if ( l != null ) {
+ l.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ lookups = " + Arrays.toString ( lookups ) + ", inputSequenceLength = " + inputSequenceLength + " }";
+ }
+
+ }
+
+ /**
+ * The <code>GlyphSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph sequence.
+ */
+ public static class GlyphSequenceRule extends Rule {
+
+ private final int[] glyphs; // glyphs
+
+ /**
+ * Instantiate a GlyphSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param glyphs the rule's glyph sequence to match, starting with second glyph in sequence
+ */
+ public GlyphSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] glyphs ) {
+ super ( lookups, inputSequenceLength );
+ assert glyphs != null;
+ this.glyphs = glyphs;
+ }
+
+ /**
+ * Obtain glyphs. N.B. that this array starts with the second
+ * glyph of the input sequence.
+ * @return the glyphs
+ */
+ public int[] getGlyphs() {
+ return glyphs;
+ }
+
+ /**
+ * Obtain glyphs augmented by specified first glyph entry.
+ * @param firstGlyph to fill in first glyph entry
+ * @return the glyphs augmented by first glyph
+ */
+ public int[] getGlyphs ( int firstGlyph ) {
+ int[] ga = new int [ glyphs.length + 1 ];
+ ga [ 0 ] = firstGlyph;
+ System.arraycopy ( glyphs, 0, ga, 1, glyphs.length );
+ return ga;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", glyphs = " + Arrays.toString ( glyphs ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ClassSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph class sequence.
+ */
+ public static class ClassSequenceRule extends Rule {
+
+ private final int[] classes; // glyph classes
+
+ /**
+ * Instantiate a ClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param classes the rule's glyph class sequence to match, starting with second glyph in sequence
+ */
+ public ClassSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] classes ) {
+ super ( lookups, inputSequenceLength );
+ assert classes != null;
+ this.classes = classes;
+ }
+
+ /**
+ * Obtain glyph classes. N.B. that this array starts with the class of the second
+ * glyph of the input sequence.
+ * @return the classes
+ */
+ public int[] getClasses() {
+ return classes;
+ }
+
+ /**
+ * Obtain glyph classes augmented by specified first class entry.
+ * @param firstClass to fill in first class entry
+ * @return the classes augmented by first class
+ */
+ public int[] getClasses ( int firstClass ) {
+ int[] ca = new int [ classes.length + 1 ];
+ ca [ 0 ] = firstClass;
+ System.arraycopy ( classes, 0, ca, 1, classes.length );
+ return ca;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", classes = " + Arrays.toString( classes ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>CoverageSequenceRule</code> class implements a subclass of <code>Rule</code>
+ * that supports matching on a specific glyph coverage sequence.
+ */
+ public static class CoverageSequenceRule extends Rule {
+
+ private final GlyphCoverageTable[] coverages; // glyph coverages
+
+ /**
+ * Instantiate a ClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param coverages the rule's glyph coverage sequence to match, starting with first glyph in sequence
+ */
+ public CoverageSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages ) {
+ super ( lookups, inputSequenceLength );
+ assert coverages != null;
+ this.coverages = coverages;
+ }
+
+ /** @return the coverages */
+ public GlyphCoverageTable[] getCoverages() {
+ return coverages;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", coverages = " + Arrays.toString( coverages ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedGlyphSequenceRule</code> class implements a subclass of <code>GlyphSequenceRule</code>
+ * that supports matching on a specific glyph sequence in a specific chained contextual.
+ */
+ public static class ChainedGlyphSequenceRule extends GlyphSequenceRule {
+
+ private final int[] backtrackGlyphs; // backtrack glyphs
+ private final int[] lookaheadGlyphs; // lookahead glyphs
+
+ /**
+ * Instantiate a ChainedGlyphSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param glyphs the rule's input glyph sequence to match, starting with second glyph in sequence
+ * @param backtrackGlyphs the rule's backtrack glyph sequence to match, starting with first glyph in sequence
+ * @param lookaheadGlyphs the rule's lookahead glyph sequence to match, starting with first glyph in sequence
+ */
+ public ChainedGlyphSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] glyphs, int[] backtrackGlyphs, int[] lookaheadGlyphs ) {
+ super ( lookups, inputSequenceLength, glyphs );
+ assert backtrackGlyphs != null;
+ assert lookaheadGlyphs != null;
+ this.backtrackGlyphs = backtrackGlyphs;
+ this.lookaheadGlyphs = lookaheadGlyphs;
+ }
+
+ /** @return the backtrack glyphs */
+ public int[] getBacktrackGlyphs() {
+ return backtrackGlyphs;
+ }
+
+ /** @return the lookahead glyphs */
+ public int[] getLookaheadGlyphs() {
+ return lookaheadGlyphs;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", glyphs = " + Arrays.toString ( getGlyphs() ) );
+ sb.append ( ", backtrackGlyphs = " + Arrays.toString ( backtrackGlyphs ) );
+ sb.append ( ", lookaheadGlyphs = " + Arrays.toString ( lookaheadGlyphs ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedClassSequenceRule</code> class implements a subclass of <code>ClassSequenceRule</code>
+ * that supports matching on a specific glyph class sequence in a specific chained contextual.
+ */
+ public static class ChainedClassSequenceRule extends ClassSequenceRule {
+
+ private final int[] backtrackClasses; // backtrack classes
+ private final int[] lookaheadClasses; // lookahead classes
+
+ /**
+ * Instantiate a ChainedClassSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param classes the rule's input glyph class sequence to match, starting with second glyph in sequence
+ * @param backtrackClasses the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
+ * @param lookaheadClasses the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
+ */
+ public ChainedClassSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, int[] classes, int[] backtrackClasses, int[] lookaheadClasses ) {
+ super ( lookups, inputSequenceLength, classes );
+ assert backtrackClasses != null;
+ assert lookaheadClasses != null;
+ this.backtrackClasses = backtrackClasses;
+ this.lookaheadClasses = lookaheadClasses;
+ }
+
+ /** @return the backtrack classes */
+ public int[] getBacktrackClasses() {
+ return backtrackClasses;
+ }
+
+ /** @return the lookahead classes */
+ public int[] getLookaheadClasses() {
+ return lookaheadClasses;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", classes = " + Arrays.toString ( getClasses() ) );
+ sb.append ( ", backtrackClasses = " + Arrays.toString ( backtrackClasses ) );
+ sb.append ( ", lookaheadClasses = " + Arrays.toString ( lookaheadClasses ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>ChainedCoverageSequenceRule</code> class implements a subclass of <code>CoverageSequenceRule</code>
+ * that supports matching on a specific glyph class sequence in a specific chained contextual.
+ */
+ public static class ChainedCoverageSequenceRule extends CoverageSequenceRule {
+
+ private final GlyphCoverageTable[] backtrackCoverages; // backtrack coverages
+ private final GlyphCoverageTable[] lookaheadCoverages; // lookahead coverages
+
+ /**
+ * Instantiate a ChainedCoverageSequenceRule.
+ * @param lookups the rule's lookups
+ * @param inputSequenceLength number of glyphs constituting input sequence (to be consumed)
+ * @param coverages the rule's input glyph class sequence to match, starting with first glyph in sequence
+ * @param backtrackCoverages the rule's backtrack glyph class sequence to match, starting with first glyph in sequence
+ * @param lookaheadCoverages the rule's lookahead glyph class sequence to match, starting with first glyph in sequence
+ */
+ public ChainedCoverageSequenceRule ( RuleLookup[] lookups, int inputSequenceLength, GlyphCoverageTable[] coverages, GlyphCoverageTable[] backtrackCoverages, GlyphCoverageTable[] lookaheadCoverages ) {
+ super ( lookups, inputSequenceLength, coverages );
+ assert backtrackCoverages != null;
+ assert lookaheadCoverages != null;
+ this.backtrackCoverages = backtrackCoverages;
+ this.lookaheadCoverages = lookaheadCoverages;
+ }
+
+ /** @return the backtrack coverages */
+ public GlyphCoverageTable[] getBacktrackCoverages() {
+ return backtrackCoverages;
+ }
+
+ /** @return the lookahead coverages */
+ public GlyphCoverageTable[] getLookaheadCoverages() {
+ return lookaheadCoverages;
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( "{ " );
+ sb.append ( "lookups = " + Arrays.toString ( getLookups() ) );
+ sb.append ( ", coverages = " + Arrays.toString ( getCoverages() ) );
+ sb.append ( ", backtrackCoverages = " + Arrays.toString ( backtrackCoverages ) );
+ sb.append ( ", lookaheadCoverages = " + Arrays.toString ( lookaheadCoverages ) );
+ sb.append ( " }" );
+ return sb.toString();
+ }
+
+ }
+
+ /**
+ * The <code>RuleSet</code> class implements a collection of rules, which
+ * may or may not be the same rule type.
+ */
+ public static class RuleSet {
+
+ private final Rule[] rules; // set of rules
+
+ /**
+ * Instantiate a Rule Set.
+ * @param rules the rules
+ * @throws IllegalArgumentException if rules or some element of rules is null
+ */
+ public RuleSet ( Rule[] rules ) throws IllegalArgumentException {
+ // enforce rules array instance
+ if ( rules == null ) {
+ throw new IllegalArgumentException ( "rules[] is null" );
+ }
+ this.rules = rules;
+ }
+
+ /** @return the rules */
+ public Rule[] getRules() {
+ return rules;
+ }
+
+ /**
+ * Resolve references to lookup tables, e.g., in RuleLookup, to the lookup tables themselves.
+ * @param lookupTables map from lookup table identifers, e.g. "lu4", to lookup tables
+ */
+ public void resolveLookupReferences ( Map/*<String,LookupTable>*/ lookupTables ) {
+ if ( rules != null ) {
+ for ( int i = 0, n = rules.length; i < n; i++ ) {
+ Rule r = rules [ i ];
+ if ( r != null ) {
+ r.resolveLookupReferences ( lookupTables );
+ }
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ public String toString() {
+ return "{ rules = " + Arrays.toString ( rules ) + " }";
+ }
+
+ }
+
+ /**
+ * The <code>HomogenousRuleSet</code> class implements a collection of rules, which
+ * must be the same rule type (i.e., same concrete rule class) or null.
+ */
+ public static class HomogeneousRuleSet extends RuleSet {
+
+ /**
+ * Instantiate a Homogeneous Rule Set.
+ * @param rules the rules
+ * @throws IllegalArgumentException if some rule[i] is not an instance of rule[0]
+ */
+ public HomogeneousRuleSet ( Rule[] rules ) throws IllegalArgumentException {
+ super ( rules );
+ // find first non-null rule
+ Rule r0 = null;
+ for ( int i = 1, n = rules.length; ( r0 == null ) && ( i < n ); i++ ) {
+ if ( rules[i] != null ) {
+ r0 = rules[i];
+ }
+ }
+ // enforce rule instance homogeneity
+ if ( r0 != null ) {
+ Class c = r0.getClass();
+ for ( int i = 1, n = rules.length; i < n; i++ ) {
+ Rule r = rules[i];
+ if ( ( r != null ) && ! c.isInstance ( r ) ) {
+ throw new IllegalArgumentException ( "rules[" + i + "] is not an instance of " + c.getName() );
+ }
+ }
+ }
+
+ }
+
+ }
+
}
--- /dev/null
+/*
+ * 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.fonts;
+
+/**
+ * Interface for testing glyph properties according to glyph identifier.
+ * @author Glenn Adams
+ */
+public interface GlyphTester {
+
+ /**
+ * Perform a test on a glyph identifier.
+ * @param gi glyph identififer
+ * @return true if test is satisfied
+ */
+ boolean test ( int gi );
+
+}
+++ /dev/null
-/*
- * 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.fonts;
-
-/**
- * A utility class for glyphs and glyph sequences.
- * @author Glenn Adams
- */
-public final class GlyphUtils {
-
- private GlyphUtils() {
- }
-
- /**
- * Map a glyph (or character) code sequence to a string, used only
- * for debugging and logging purposes.
- * @param cs character (glyph) id sequence
- * @return a string representation of code sequence
- */
- public static String toString ( CharSequence cs ) {
- StringBuffer sb = new StringBuffer();
- sb.append ( '[' );
- for ( int i = 0, n = cs.length(); i < n; i++ ) {
- int c = cs.charAt ( i );
- if ( i > 0 ) {
- sb.append ( ',' );
- }
- sb.append ( Integer.toString ( c ) );
- }
- sb.append ( ']' );
- return sb.toString();
- }
-
-}
*/
public boolean performsPositioning() {
load(true);
- if ( realFontDescriptor instanceof Substitutable ) {
+ if ( realFontDescriptor instanceof Positionable ) {
return ((Positionable)realFontDescriptor).performsPositioning();
} else {
return false;
/**
* {@inheritDoc}
*/
- public int[] performPositioning ( CharSequence cs, String script, String language ) {
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
load(true);
- if ( realFontDescriptor instanceof Substitutable ) {
- return ((Positionable)realFontDescriptor).performPositioning(cs, script, language);
+ if ( realFontDescriptor instanceof Positionable ) {
+ return ((Positionable)realFontDescriptor)
+ .performPositioning(cs, script, language, fontSize);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language ) {
+ load(true);
+ if ( realFontDescriptor instanceof Positionable ) {
+ return ((Positionable)realFontDescriptor)
+ .performPositioning(cs, script, language);
} else {
return null;
}
package org.apache.fop.fonts;
-//Java
import java.nio.CharBuffer;
+import java.nio.IntBuffer;
import java.text.DecimalFormat;
import java.util.Map;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.util.CharUtilities;
/**
* Generic MultiByte (CID) font
*/
public class MultiByteFont extends CIDFont implements Substitutable, Positionable {
+ /** logging instance */
+ private static final Log log // CSOK: ConstantNameCheck
+ = LogFactory.getLog(MultiByteFont.class);
+
private static int uniqueCounter = -1;
private String ttcName = null;
private BFEntry[] bfentries = null;
/* advanced typographic support */
+ private GlyphDefinitionTable gdef;
private GlyphSubstitutionTable gsub;
private GlyphPositioningTable gpos;
return subset.getSubsetChars();
}
+ /**
+ * Establishes the glyph definition table.
+ * @param gdef the glyph definition table to be used by this font
+ */
+ public void setGDEF ( GlyphDefinitionTable gdef ) {
+ if ( ( this.gdef == null ) || ( gdef == null ) ) {
+ this.gdef = gdef;
+ } else {
+ throw new IllegalStateException ( "font already associated with GDEF table" );
+ }
+ }
+
+ /**
+ * Obtain glyph definition table.
+ * @return glyph definition table or null if none is associated with font
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
/**
* Establishes the glyph substitution table.
* @param gsub the glyph substitution table to be used by this font
}
/** {@inheritDoc} */
- public int[] performPositioning ( CharSequence cs, String script, String language ) {
+ public int[][]
+ performPositioning ( CharSequence cs, String script, String language, int fontSize ) {
if ( gpos != null ) {
- return gpos.position ( mapCharsToGlyphs ( cs ), script, language );
+ GlyphSequence gs = mapCharsToGlyphs ( cs );
+ int[][] adjustments = new int [ gs.getGlyphCount() ] [ 4 ];
+ if ( gpos.position ( gs, script, language, fontSize, this.width, adjustments ) ) {
+ return scaleAdjustments ( adjustments, fontSize );
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int[][] performPositioning ( CharSequence cs, String script, String language ) {
+ throw new UnsupportedOperationException();
+ }
+
+
+ private int[][] scaleAdjustments ( int[][] adjustments, int fontSize ) {
+ if ( adjustments != null ) {
+ for ( int i = 0, n = adjustments.length; i < n; i++ ) {
+ int[] gpa = adjustments [ i ];
+ for ( int k = 0; k < 4; k++ ) {
+ gpa [ k ] = ( gpa [ k ] * fontSize ) / 1000;
+ }
+ }
+ return adjustments;
} else {
return null;
}
* @returns a CharSequence containing glyph indices
*/
private GlyphSequence mapCharsToGlyphs ( CharSequence cs ) {
- CharBuffer cb = CharBuffer.allocate ( cs.length() );
+ IntBuffer cb = IntBuffer.allocate ( cs.length() );
+ IntBuffer gb = IntBuffer.allocate ( cs.length() );
int gi, giMissing = findGlyphIndex ( Typeface.NOT_FOUND );
for ( int i = 0, n = cs.length(); i < n; i++ ) {
int cc = cs.charAt ( i );
if ( gi == SingleByteEncoding.NOT_FOUND_CODE_POINT ) {
gi = giMissing;
}
- cb.put ( (char) gi );
+ cb.put ( cc );
+ gb.put ( gi );
}
- cb.rewind();
- return new GlyphSequence ( cs, (CharSequence) cb, null );
+ cb.flip();
+ gb.flip();
+ return new GlyphSequence ( cb, gb, null );
}
/**
* Map sequence GS, comprising a sequence of Glyph Indices, to output sequence CS,
* comprising a sequence of UTF-16 encoded Unicode Code Points.
- * @param gs a CharSequence containing glyph indices
+ * @param gs a GlyphSequence containing glyph indices
* @returns a CharSequence containing UTF-16 encoded Unicode characters
*/
private CharSequence mapGlyphsToChars ( GlyphSequence gs ) {
- CharBuffer cb = CharBuffer.allocate ( gs.length() );
- int cc, ccMissing = Typeface.NOT_FOUND;
- for ( int i = 0, n = gs.length(); i < n; i++ ) {
- int gi = gs.charAt ( i );
- cc = findCharacterFromGlyphIndex ( gi );
- if ( cc == 0 ) {
- cc = ccMissing;
- }
- if ( cc > 0x10FFFF ) {
+ int ng = gs.getGlyphCount();
+ CharBuffer cb = CharBuffer.allocate ( ng );
+ int ccMissing = Typeface.NOT_FOUND;
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int gi = gs.getGlyph ( i );
+ int cc = findCharacterFromGlyphIndex ( gi );
+ if ( ( cc == 0 ) || ( cc > 0x10FFFF ) ) {
cc = ccMissing;
+ log.warn("Unable to map glyph index " + gi
+ + " to Unicode scalar in font '"
+ + getFullName() + "', substituting missing character '"
+ + (char) cc + "'");
}
if ( cc > 0x00FFFF ) {
int sh, sl;
cb.put ( (char) cc );
}
}
- cb.rewind();
+ cb.flip();
return (CharSequence) cb;
}
* @param cs character sequence to map to position offsets (advancement adjustments)
* @param script a script identifier
* @param language a language identifier
- * @return array (sequence) of pairs of position offsets, one pair for each element of character sequence
+ * @param fontSize font size
+ * @return array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence, or null if no non-zero adjustment applies
*/
- int[] performPositioning ( CharSequence cs, String script, String language );
+ int[][] performPositioning ( CharSequence cs, String script, String language, int fontSize );
+
+ /**
+ * Perform glyph positioning using an implied font size.
+ * @param cs character sequence to map to position offsets (advancement adjustments)
+ * @param script a script identifier
+ * @param language a language identifier
+ * @return array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence, or null if no non-zero adjustment applies
+ */
+ int[][] performPositioning ( CharSequence cs, String script, String language );
}
--- /dev/null
+/*
+ * 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.fonts;
+
+/**
+ * Interface for providing script specific context testers.
+ * @author Glenn Adams
+ */
+public interface ScriptContextTester {
+
+ /**
+ * Obtain a glyph context tester for the specified feature.
+ * @param feature a feature identifier
+ * @return a glyph context tester or null if none available for the specified feature
+ */
+ GlyphContextTester getTester ( String feature );
+
+}
package org.apache.fop.fonts;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
// CSOFF: InnerAssignmentCheck
// CSOFF: LineLengthCheck
+// CSOFF: ParameterNumberCheck
/**
* Abstract script processor base class for which an implementation of the substitution and positioning methods
}
/** @return script identifier */
- public String getScript() {
+ public final String getScript() {
return script;
}
+ /**
+ * Obtain script specific substitution features.
+ * @return array of suppported substitution features or null
+ */
+ public abstract String[] getSubstitutionFeatures();
+
+ /**
+ * Obtain script specific substitution context tester.
+ * @return substitution context tester or null
+ */
+ public abstract ScriptContextTester getSubstitutionContextTester();
+
/**
* Perform substitution processing using a specific set of lookup tables.
+ * @param gsub the glyph substitution table that applies
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
* @param lookups a mapping from lookup specifications to glyph subtables to use for substitution processing
* @return the substituted (output) glyph sequence
*/
- public abstract GlyphSequence substitute ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups );
+ public final GlyphSequence substitute ( GlyphSubstitutionTable gsub, GlyphSequence gs, String script, String language, Map/*<LookupSpec,List<LookupTable>>>*/ lookups ) {
+ return substitute ( gs, script, language, assembleLookups ( gsub, getSubstitutionFeatures(), lookups ), getSubstitutionContextTester() );
+ }
+
+ /**
+ * Perform substitution processing using a specific set of ordered glyph table use specifications.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param usa an ordered array of glyph table use specs
+ * @param sct a script specific context tester (or null)
+ * @return the substituted (output) glyph sequence
+ */
+ public final GlyphSequence substitute ( GlyphSequence gs, String script, String language, GlyphTable.UseSpec[] usa, ScriptContextTester sct ) {
+ assert usa != null;
+ for ( int i = 0, n = usa.length; i < n; i++ ) {
+ GlyphTable.UseSpec us = usa [ i ];
+ gs = us.substitute ( gs, script, language, sct );
+ }
+ return gs;
+ }
+
+ /**
+ * Obtain script specific positioning features.
+ * @return array of suppported positioning features or null
+ */
+ public abstract String[] getPositioningFeatures();
+
+ /**
+ * Obtain script specific positioning context tester.
+ * @return positioning context tester or null
+ */
+ public abstract ScriptContextTester getPositioningContextTester();
/**
* Perform positioning processing using a specific set of lookup tables.
+ * @param gpos the glyph positioning table that applies
* @param gs an input glyph sequence
* @param script a script identifier
* @param language a language identifier
+ * @param fontSize size in device units
* @param lookups a mapping from lookup specifications to glyph subtables to use for positioning processing
- * @return the substituted (output) glyph sequence
+ * @param widths array of default advancements for each glyph
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @return true if some adjustment is not zero; otherwise, false
+ */
+ public final boolean position ( GlyphPositioningTable gpos, GlyphSequence gs, String script, String language, int fontSize, Map/*<LookupSpec,List<LookupTable>>*/ lookups, int[] widths, int[][] adjustments ) {
+ return position ( gs, script, language, fontSize, assembleLookups ( gpos, getPositioningFeatures(), lookups ), widths, adjustments, getPositioningContextTester() );
+ }
+
+ /**
+ * Perform positioning processing using a specific set of ordered glyph table use specifications.
+ * @param gs an input glyph sequence
+ * @param script a script identifier
+ * @param language a language identifier
+ * @param fontSize size in device units
+ * @param usa an ordered array of glyph table use specs
+ * @param widths array of default advancements for each glyph in font
+ * @param adjustments accumulated adjustments array (sequence) of 4-tuples of placement [PX,PY] and advance [AX,AY] adjustments, in that order,
+ * with one 4-tuple for each element of glyph sequence
+ * @param sct a script specific context tester (or null)
+ * @return true if some adjustment is not zero; otherwise, false
*/
- public abstract int[] position ( GlyphSequence gs, String script, String language, Map/*<LookupSpec,GlyphSubtable[]>*/ lookups );
+ public final boolean position ( GlyphSequence gs, String script, String language, int fontSize, GlyphTable.UseSpec[] usa, int[] widths, int[][] adjustments, ScriptContextTester sct ) {
+ assert usa != null;
+ boolean adjusted = false;
+ for ( int i = 0, n = usa.length; i < n; i++ ) {
+ GlyphTable.UseSpec us = usa [ i ];
+ if ( us.position ( gs, script, language, fontSize, widths, adjustments, sct ) ) {
+ adjusted = true;
+ }
+ }
+ return adjusted;
+ }
+
+ /**
+ * Assemble ordered array of lookup table use specifications according to the specified features and candidate lookups,
+ * where the order of the array is in accordance to the order of the applicable lookup list.
+ * @param table the governing glyph table
+ * @param features array of feature identifiers to apply
+ * @param lookups a mapping from lookup specifications to lists of look tables from which to select lookup tables according to the specified features
+ * @return ordered array of assembled lookup table use specifications
+ */
+ public final GlyphTable.UseSpec[] assembleLookups ( GlyphTable table, String[] features, Map/*<LookupSpec,List<LookupTable>>*/ lookups ) {
+ return table.assembleLookups ( features, lookups );
+ }
/**
* Obtain script processor instance associated with specified script.
List arguments = new java.util.ArrayList();
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
- if ("-d".equals(args[i]) || "-q".equals(args[i])) {
+ if ("-t".equals(args[i]) || "-d".equals(args[i]) || "-q".equals(args[i])) {
options.put(args[i], "");
} else if ((i + 1) < args.length && !args[i + 1].startsWith("-")) {
options.put(args[i], args[i + 1]);
*/
protected static void determineLogLevel(Map options) {
//Determine log level
- if (options.get("-d") != null) {
+ if (options.get("-t") != null) {
+ setLogLevel("trace");
+ } else if (options.get("-d") != null) {
setLogLevel("debug");
} else if (options.get("-q") != null) {
setLogLevel("error");
import org.apache.commons.logging.LogFactory;
import org.apache.fop.Version;
import org.apache.fop.fonts.FontUtil;
-import org.apache.fop.fonts.GlyphCoverageTable;
-import org.apache.fop.fonts.GlyphPositioningTable;
-import org.apache.fop.fonts.GlyphSubstitutionTable;
-import org.apache.fop.fonts.GlyphSubtable;
-import org.apache.fop.fonts.GlyphTable;
import org.apache.fop.fonts.truetype.FontFileReader;
import org.apache.fop.fonts.truetype.TTFCmapEntry;
import org.apache.fop.fonts.truetype.TTFFile;
"java " + TTFReader.class.getName() + " [options] fontfile.ttf xmlfile.xml");
System.out.println();
System.out.println("where options can be:");
+ System.out.println("-t Trace mode");
System.out.println("-d Debug mode");
System.out.println("-q Quiet mode");
System.out.println("-enc ansi");
generateDOM4Kerning(root, ttf, isCid);
- generateDOM4ScriptExtensions(root, ttf, isCid);
-
return doc;
}
}
}
- private void generateDOM4LookupReferences ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups ) {
- boolean usedLookup = false;
- Document d = parent.getOwnerDocument();
- for ( int i = 0, m = lookups.length; i < m; i++ ) {
- GlyphTable.LookupSpec ls = lookups [ i ];
- GlyphSubtable[] sta = gsub.findSubtables ( ls );
- for ( int j = 0, n = sta.length; j < n; j++ ) {
- GlyphSubtable st = sta [ j ];
- Element e = d.createElement("use-lookup");
- e.setAttribute ( "ref", st.getID() );
- parent.appendChild(e);
- }
- }
- }
-
- private void generateDOM4Features ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups, String scriptTag, String languageTag ) {
- Document d = parent.getOwnerDocument();
- Set features = new java.util.LinkedHashSet();
- for ( int i = 0, n = lookups.length; i < n; i++ ) {
- GlyphTable.LookupSpec ls = lookups [ i ];
- features.add ( ls.getFeature() );
- }
- for ( Iterator it = features.iterator(); it.hasNext();) {
- String featureTag = (String) it.next();
- Element e = d.createElement("feature");
- e.setAttribute ( "tag", featureTag );
- generateDOM4LookupReferences ( e, gsub, gsub.matchLookupSpecs ( scriptTag, languageTag, featureTag ) );
- if ( e.hasChildNodes() ) {
- parent.appendChild(e);
- }
- }
- }
-
- private void generateDOM4Languages ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups, String scriptTag ) {
- Document d = parent.getOwnerDocument();
- Set languages = new java.util.LinkedHashSet();
- for ( int i = 0, n = lookups.length; i < n; i++ ) {
- GlyphTable.LookupSpec ls = lookups [ i ];
- languages.add ( ls.getLanguage() );
- }
- for ( Iterator it = languages.iterator(); it.hasNext();) {
- String languageTag = (String) it.next();
- Element e = d.createElement("lang");
- e.setAttribute ( "tag", languageTag );
- generateDOM4Features ( e, gsub, gsub.matchLookupSpecs ( scriptTag, languageTag, "*" ), scriptTag, languageTag );
- parent.appendChild(e);
- }
- }
-
- private void generateDOM4Scripts ( Element parent, GlyphSubstitutionTable gsub, GlyphTable.LookupSpec[] lookups ) {
- Document d = parent.getOwnerDocument();
- Set scripts = new java.util.LinkedHashSet();
- for ( int i = 0, n = lookups.length; i < n; i++ ) {
- GlyphTable.LookupSpec ls = lookups [ i ];
- scripts.add ( ls.getScript() );
- }
- for ( Iterator it = scripts.iterator(); it.hasNext();) {
- String scriptTag = (String) it.next();
- Element e = d.createElement("script");
- e.setAttribute ( "tag", scriptTag );
- generateDOM4Languages ( e, gsub, gsub.matchLookupSpecs ( scriptTag, "*", "*" ), scriptTag );
- parent.appendChild(e);
- }
- }
-
- private void generateDOM4Coverage ( Element parent, GlyphCoverageTable coverage ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("coverage");
- int type = coverage.getType();
- e.setAttribute ( "format", Integer.toString ( type ) );
- List entries = coverage.getEntries();
- if ( type == GlyphCoverageTable.GLYPH_COVERAGE_TYPE_MAPPED ) {
- for ( Iterator it = entries.iterator(); it.hasNext();) {
- Integer gid = (Integer) it.next();
- if ( gid != null ) {
- Element g = d.createElement("gid");
- g.appendChild(d.createTextNode(gid.toString()));
- e.appendChild(g);
- }
- }
- } else if ( type == GlyphCoverageTable.GLYPH_COVERAGE_TYPE_RANGE ) {
- for ( Iterator it = entries.iterator(); it.hasNext();) {
- GlyphCoverageTable.CoverageRange cr = (GlyphCoverageTable.CoverageRange) it.next();
- if ( cr != null ) {
- Element r = d.createElement("range");
- r.setAttribute ( "gs", Integer.toString(cr.getStart()) );
- r.setAttribute ( "ge", Integer.toString(cr.getEnd()) );
- r.setAttribute ( "ci", Integer.toString(cr.getIndex()) );
- e.appendChild(r);
- }
- }
- }
- parent.appendChild(e);
- }
-
- private void generateDOM4GSUBSingleEntries ( Element parent, List entries, int type, int format ) {
- if ( entries.size() > 0 ) {
- Document d = parent.getOwnerDocument();
- for ( Iterator it = entries.iterator(); it.hasNext();) {
- Integer gid = (Integer) it.next();
- if ( gid != null ) {
- Element e = d.createElement("gid");
- e.appendChild(d.createTextNode(gid.toString()));
- parent.appendChild(e);
- }
- }
- }
- }
-
- private void generateDOM4GSUBMultipleEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4GSUBAlternateEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4LigatureSet ( Element parent, GlyphSubstitutionTable.LigatureSet lset ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("ligs");
- GlyphSubstitutionTable.Ligature[] la = lset.getLigatures();
- for ( int i = 0, m = la.length; i < m; i++ ) {
- GlyphSubstitutionTable.Ligature l = la [ i ];
- Element le = d.createElement("lig");
- le.setAttribute ( "gid", Integer.toString( l.getLigature() ) );
- int[] ca = l.getComponents();
- StringBuffer sb = new StringBuffer();
- for ( int j = 0, n = ca.length; j < n; j++ ) {
- if ( j > 0 ) {
- sb.append ( ' ' );
- }
- sb.append ( Integer.toString ( ca [ j ] ) );
- }
- if ( sb.length() > 0 ) {
- le.appendChild ( d.createTextNode ( sb.toString() ) );
- e.appendChild ( le );
- }
- }
- parent.appendChild(e);
- }
-
- private void generateDOM4GSUBLigatureEntries ( Element parent, List entries, int type, int format ) {
- if ( entries.size() > 0 ) {
- for ( Iterator it = entries.iterator(); it.hasNext();) {
- GlyphSubstitutionTable.LigatureSet lset = (GlyphSubstitutionTable.LigatureSet) it.next();
- if ( lset != null ) {
- generateDOM4LigatureSet ( parent, lset );
- }
- }
- }
- }
-
- private void generateDOM4GSUBContextEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4GSUBChainingContextEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4GSUBExtensionEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4GSUBReverseChainingSingleEntries ( Element parent, List entries, int type, int format ) {
- // [TBD] - implement me
- }
-
- private void generateDOM4GSUBEntries ( Element parent, List entries, int type, int format ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("entries");
- switch ( type ) {
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_SINGLE:
- generateDOM4GSUBSingleEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_MULTIPLE:
- generateDOM4GSUBMultipleEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_ALTERNATE:
- generateDOM4GSUBAlternateEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE:
- generateDOM4GSUBLigatureEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT:
- generateDOM4GSUBContextEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT:
- generateDOM4GSUBChainingContextEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION:
- generateDOM4GSUBExtensionEntries ( e, entries, type, format );
- break;
- case GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE:
- generateDOM4GSUBReverseChainingSingleEntries ( e, entries, type, format );
- break;
- default:
- break;
- }
- parent.appendChild(e);
- }
-
- private void generateDOM4GSUBSubtable ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable st ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("lst");
- e.setAttribute ( "format", Integer.toString ( st.getFormat() ) );
- generateDOM4Coverage ( e, st.getCoverage() );
- generateDOM4GSUBEntries ( e, st.getEntries(), st.getType(), st.getFormat() );
- parent.appendChild(e);
- }
-
- private void generateDOM4GSUBSubtables ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable[] subtables ) {
- if ( subtables.length > 0 ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("lookup");
- GlyphSubtable st0 = subtables[0];
- int st0Type = st0.getType();
- e.setAttribute ( "id", st0.getID() );
- e.setAttribute ( "type", st0.getTypeName() );
- for ( int i = 0, n = subtables.length; i < n; i++ ) {
- GlyphSubtable st = subtables[i];
- if ( st.getType() == st0Type ) {
- generateDOM4GSUBSubtable ( e, gsub, st );
- }
- }
- parent.appendChild(e);
- }
- }
-
- private GlyphSubtable[] matchSubtables ( GlyphSubtable[] subtables, String id ) {
- List matches = new java.util.ArrayList();
- for ( int i = 0, n = subtables.length; i < n; i++ ) {
- GlyphSubtable st = subtables [ i ];
- if ( st.getID().equals ( id ) ) {
- matches.add ( st );
- }
- }
- return (GlyphSubtable[]) matches.toArray ( new GlyphSubtable[matches.size()] );
- }
-
- private void generateDOM4GSUBLookups ( Element parent, GlyphSubstitutionTable gsub, GlyphSubtable[] subtables ) {
- Set lus = new java.util.LinkedHashSet();
- for ( int i = 0, n = subtables.length; i < n; i++ ) {
- GlyphSubtable st = subtables [ i ];
- lus.add ( st.getID() );
- }
- for ( Iterator it = lus.iterator(); it.hasNext();) {
- String id = (String) it.next();
- generateDOM4GSUBSubtables ( parent, gsub, matchSubtables ( subtables, id ) );
- }
- }
-
- private void generateDOM4GSUB ( Element parent, GlyphSubstitutionTable gsub ) {
- Document d = parent.getOwnerDocument();
- Element e = d.createElement("gsub");
- parent.appendChild(e);
- generateDOM4Scripts ( e, gsub, gsub.getLookups() );
- generateDOM4GSUBLookups ( e, gsub, gsub.getSubtables() );
- }
-
- private void generateDOM4GPOS ( Element parent, GlyphPositioningTable gpos ) {
- Document d = parent.getOwnerDocument();
- Element te = d.createElement("gpos");
- parent.appendChild(te);
- }
-
- private void generateDOM4ScriptExtensions(Element parent, TTFFile ttf, boolean isCid) {
- if ( ttf.hasScriptExtension() ) {
- Document d = parent.getOwnerDocument();
- Element se = d.createElement("script-extras");
- parent.appendChild(se);
- GlyphSubstitutionTable st;
- if ( ( st = ttf.getGSUB() ) != null ) {
- generateDOM4GSUB ( se, st );
- }
- GlyphPositioningTable pt;
- if ( ( pt = ttf.getGPOS() ) != null ) {
- generateDOM4GPOS ( se, pt );
- }
- }
- }
-
/**
* Bugzilla 40739, check that attr has a metrics-version attribute
* compatible with ours.
package org.apache.fop.fonts.truetype;
import java.io.IOException;
+import java.util.Arrays;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import org.apache.fop.fonts.FontUtil;
+import org.apache.fop.fonts.GlyphClassTable;
import org.apache.fop.fonts.GlyphCoverageTable;
+import org.apache.fop.fonts.GlyphDefinitionSubtable;
+import org.apache.fop.fonts.GlyphDefinitionTable;
+import org.apache.fop.fonts.GlyphMappingTable;
import org.apache.fop.fonts.GlyphPositioningSubtable;
import org.apache.fop.fonts.GlyphPositioningTable;
import org.apache.fop.fonts.GlyphSubstitutionSubtable;
import org.apache.fop.fonts.GlyphSubtable;
import org.apache.fop.fonts.GlyphTable;
+import org.apache.fop.util.CharUtilities;
+
// CSOFF: AvoidNestedBlocksCheck
// CSOFF: NoWhitespaceAfterCheck
// CSOFF: InnerAssignmentCheck
static final int MAX_CHAR_CODE = 255;
static final int ENC_BUF_SIZE = 1024;
- /** Set to true to get even more debug output than with level DEBUG */
- public static final boolean TRACE_ENABLED = false;
-
private String encoding = "WinAnsiEncoding"; // Default encoding
private short firstChar = 0;
private Map kerningTab; // for CIDs
private Map ansiKerningTab; // For winAnsiEncoding
private List cmaps;
- private List unicodeMapping;
+ private Set unicodeMappings;
private int upem; // unitsPerEm from "head" table
private int nhmtx; // Number of horizontal metrics
private Map/*<String,Object[3]>*/ seScripts;
private Map/*<String,Object[2]>*/ seLanguages;
private Map/*<String,List<String>>*/ seFeatures;
- private List seCoverage;
+ private GlyphMappingTable seMapping;
private List seEntries;
private List seSubtables;
+ private GlyphDefinitionTable gdef;
private GlyphSubstitutionTable gsub;
private GlyphPositioningTable gpos;
/**
* Key-value helper class
*/
- class UnicodeMapping {
+ class UnicodeMapping implements Comparable {
private int unicodeIndex;
private int glyphIndex;
public int getUnicodeIndex() {
return unicodeIndex;
}
+
+
+ /** {@inheritDoc} */
+ public int hashCode() {
+ int hc = unicodeIndex;
+ hc = 19 * hc + ( hc ^ glyphIndex );
+ return hc;
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals ( Object o ) {
+ if ( o instanceof UnicodeMapping ) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if ( unicodeIndex != m.unicodeIndex ) {
+ return false;
+ } else if ( glyphIndex != m.glyphIndex ) {
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ /** {@inheritDoc} */
+ public int compareTo ( Object o ) {
+ if ( o instanceof UnicodeMapping ) {
+ UnicodeMapping m = (UnicodeMapping) o;
+ if ( unicodeIndex > m.unicodeIndex ) {
+ return 1;
+ } else if ( unicodeIndex < m.unicodeIndex ) {
+ return -1;
+ } else {
+ return 0;
+ }
+ } else {
+ return -1;
+ }
+ }
}
/**
*/
private boolean readCMAP(FontFileReader in) throws IOException {
- unicodeMapping = new java.util.ArrayList();
+ unicodeMappings = new java.util.TreeSet();
seekTab(in, "cmap", 2);
int numCMap = in.readTTFUShort(); // Number of cmap subtables
glyphIdx = (in.readTTFUShort() + cmapDeltas[i])
& 0xffff;
- unicodeMapping.add(new UnicodeMapping(glyphIdx, j));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
if (encodingID == 0 && j >= 0xF020 && j <= 0xF0FF) {
int mapped = j - 0xF000;
if (!eightBitGlyphs.get(mapped)) {
//Only map if Unicode code point hasn't been mapped before
- unicodeMapping.add(new UnicodeMapping(glyphIdx, mapped));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, mapped));
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(mapped));
}
}
+ mtxTab.length);
}
- unicodeMapping.add(new UnicodeMapping(glyphIdx, j));
+ unicodeMappings.add(new UnicodeMapping(glyphIdx, j));
if (glyphIdx < mtxTab.length) {
mtxTab[glyphIdx].getUnicodeIndex().add(new Integer(j));
} else {
return false;
}
// Create cmaps for bfentries
+ augmentCMaps();
createCMaps();
- // print_max_min();
readKerning(in);
+ readGDEF(in);
readGSUB(in);
readGPOS(in);
guessVerticalMetricsFromGlyphBBox();
return true;
}
+ /**
+ * Augment the previously ingested CMAP data with new entries to ensure
+ * that every glyph index has a corresponding Unicode value. This is required
+ * by GSUB/GPOS processing which can emit glyph indices that are not in the
+ * normal CMAP (and, for which, on other platforms, the glyph indices are used
+ * directly for rendering purposes (rather than character codes). However, in
+ * the case of FOP IF representation, character codes are used, and, consequently
+ * every glyph needs some character value. Here, we assign them to the Unicode
+ * private use range, starting at 0xE000 up to 0xF8FF. If there are existing
+ * assignments in this range, we just skip over them. Note that it is possible
+ * to exhaust this range of 6400 code values in the case a font has an
+ * extraordinary number of unmapped glyphs. In that case, we do not make
+ * any further assignments, but print a warning message.
+ */
+ private void augmentCMaps() {
+ int numMapped = 0;
+ int numUnmapped = 0;
+ int nextPrivateUse = 0xE000;
+ int firstPrivate = 0;
+ int lastPrivate = 0;
+ int firstUnmapped = 0;
+ int lastUnmapped = 0;
+ for ( int i = 0, n = numberOfGlyphs; i < n; i++ ) {
+ Integer uc = glyphToUnicode ( i );
+ if ( uc == null ) {
+ while ( ( nextPrivateUse < 0xF900 ) && ( unicodeToGlyphMap.get(new Integer(nextPrivateUse)) != null ) ) {
+ nextPrivateUse++;
+ }
+ if ( nextPrivateUse < 0xF900 ) {
+ int pu = nextPrivateUse;
+ unicodeMappings.add ( new UnicodeMapping ( i, pu ) );
+ if ( firstPrivate == 0 ) {
+ firstPrivate = pu;
+ }
+ lastPrivate = pu;
+ numMapped++;
+ } else {
+ if ( firstUnmapped == 0 ) {
+ firstUnmapped = i;
+ }
+ lastUnmapped = i;
+ numUnmapped++;
+ }
+ }
+ }
+ if ( numMapped > 0 ) {
+ if (log.isDebugEnabled()) {
+ log.debug ( "augment CMAP for "
+ + numMapped
+ + " glyphs, mapped to private use characters in the range ["
+ + CharUtilities.format ( firstPrivate ) + ","
+ + CharUtilities.format ( lastPrivate ) + "] (inclusive)" );
+ }
+ }
+ if ( numUnmapped > 0 ) {
+ log.warn ( "Exhausted private use area: unable to map "
+ + numUnmapped + " glyphs in glyph index range ["
+ + firstUnmapped + "," + lastUnmapped + "] (inclusive) of font '" + getFullName() + "'" );
+ }
+ }
+
private void createCMaps() {
cmaps = new java.util.ArrayList();
TTFCmapEntry tce = new TTFCmapEntry();
- Iterator e = unicodeMapping.listIterator();
+ Iterator e = unicodeMappings.iterator();
UnicodeMapping um = (UnicodeMapping)e.next();
UnicodeMapping lastMapping = um;
int mtxSize = Math.max(numberOfGlyphs, nhmtx);
mtxTab = new TTFMtxEntry[mtxSize];
- if (TRACE_ENABLED) {
- log.debug("*** Widths array: \n");
+ if (log.isTraceEnabled()) {
+ log.trace("*** Widths array: \n");
}
for (int i = 0; i < mtxSize; i++) {
mtxTab[i] = new TTFMtxEntry();
mtxTab[i].setWx(in.readTTFUShort());
mtxTab[i].setLsb(in.readTTFUShort());
- if (TRACE_ENABLED) {
- if (log.isDebugEnabled()) {
- log.debug(" width[" + i + "] = "
- + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
- }
+ if (log.isTraceEnabled()) {
+ log.trace(" width[" + i + "] = "
+ + convertTTFUnit2PDFUnit(mtxTab[i].getWx()) + ";");
}
}
}
}
+ /** helper method for formatting an integer array for output */
private String toString ( int[] ia ) {
StringBuffer sb = new StringBuffer();
if ( ( ia == null ) || ( ia.length == 0 ) ) {
return ( gsub != null ) || ( gpos != null );
}
+ /**
+ * Returns the GDEF table or null if none present.
+ * @return the GDEF table
+ */
+ public GlyphDefinitionTable getGDEF() {
+ return gdef;
+ }
+
/**
* Returns the GSUB table or null if none present.
* @return the GSUB table
return gpos;
}
+ static final class GDEFLookupType {
+ static final int GLYPH_CLASS = 1;
+ static final int ATTACHMENT_POINT = 2;
+ static final int LIGATURE_CARET = 3;
+ static final int MARK_ATTACHMENT = 4;
+ private GDEFLookupType() {
+ }
+ public static int getSubtableType ( int lt ) {
+ int st;
+ switch ( lt ) {
+ case GDEFLookupType.GLYPH_CLASS:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_GLYPH_CLASS;
+ break;
+ case GDEFLookupType.ATTACHMENT_POINT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_ATTACHMENT_POINT;
+ break;
+ case GDEFLookupType.LIGATURE_CARET:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_LIGATURE_CARET;
+ break;
+ case GDEFLookupType.MARK_ATTACHMENT:
+ st = GlyphDefinitionTable.GDEF_LOOKUP_TYPE_MARK_ATTACHMENT;
+ break;
+ default:
+ st = -1;
+ break;
+ }
+ return st;
+ }
+ public static String toString(int type) {
+ String s;
+ switch ( type ) {
+ case GLYPH_CLASS:
+ s = "GlyphClass";
+ break;
+ case ATTACHMENT_POINT:
+ s = "AttachmentPoint";
+ break;
+ case LIGATURE_CARET:
+ s = "LigatureCaret";
+ break;
+ case MARK_ATTACHMENT:
+ s = "MarkAttachment";
+ break;
+ default:
+ s = "?";
+ break;
+ }
+ return s;
+ }
+ }
+
static final class GSUBLookupType {
static final int SINGLE = 1;
static final int MULTIPLE = 2;
static final int ALTERNATE = 3;
static final int LIGATURE = 4;
- static final int CONTEXT = 5;
- static final int CHAINED_CONTEXT = 6;
+ static final int CONTEXTUAL = 5;
+ static final int CHAINED_CONTEXTUAL = 6;
static final int EXTENSION = 7;
static final int REVERSE_CHAINED_SINGLE = 8;
private GSUBLookupType() {
case GSUBLookupType.LIGATURE:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_LIGATURE;
break;
- case GSUBLookupType.CONTEXT:
- st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXT;
+ case GSUBLookupType.CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CONTEXTUAL;
break;
- case GSUBLookupType.CHAINED_CONTEXT:
- st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINING_CONTEXT;
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_CHAINED_CONTEXTUAL;
break;
case GSUBLookupType.EXTENSION:
st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_EXTENSION_SUBSTITUTION;
break;
case GSUBLookupType.REVERSE_CHAINED_SINGLE:
- st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINING_CONTEXT_SINGLE;
+ st = GlyphSubstitutionTable.GSUB_LOOKUP_TYPE_REVERSE_CHAINED_SINGLE;
break;
default:
st = -1;
case LIGATURE:
s = "Ligature";
break;
- case CONTEXT:
- s = "Context";
+ case CONTEXTUAL:
+ s = "Contextual";
break;
- case CHAINED_CONTEXT:
- s = "ChainedContext";
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
break;
case EXTENSION:
s = "Extension";
static final int MARK_TO_BASE = 4;
static final int MARK_TO_LIGATURE = 5;
static final int MARK_TO_MARK = 6;
- static final int CONTEXT = 7;
- static final int CHAINED_CONTEXT = 8;
+ static final int CONTEXTUAL = 7;
+ static final int CHAINED_CONTEXTUAL = 8;
static final int EXTENSION = 9;
private GPOSLookupType() {
}
case MARK_TO_MARK:
s = "MarkToMark";
break;
- case CONTEXT:
- s = "Context";
+ case CONTEXTUAL:
+ s = "Contextual";
break;
- case CHAINED_CONTEXT:
- s = "ChainedContext";
+ case CHAINED_CONTEXTUAL:
+ s = "ChainedContextual";
break;
case EXTENSION:
s = "Extension";
}
}
- private void readCoverageTableFormat1(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+ private GlyphCoverageTable readCoverageTableFormat1(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip ( 2 );
for ( int i = 0, n = ng; i < n; i++ ) {
int g = in.readTTFUShort();
ga[i] = g;
- seCoverage.add ( Integer.valueOf(g) );
+ entries.add ( Integer.valueOf(g) );
}
// dump info if debugging
if (log.isDebugEnabled()) {
log.debug(label + " glyphs: " + toString(ga) );
}
+ return GlyphCoverageTable.createCoverageTable ( entries );
}
- private void readCoverageTableFormat2(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+ private GlyphCoverageTable readCoverageTableFormat2(FontFileReader in, String label, long tableOffset, int coverageFormat) throws IOException {
+ List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip ( 2 );
// read range record count
int nr = in.readTTFUShort();
- int[] rsa = new int[nr];
- int[] rea = new int[nr];
- int[] rxa = new int[nr];
for ( int i = 0, n = nr; i < n; i++ ) {
// read range start
int s = in.readTTFUShort();
// read range end
int e = in.readTTFUShort();
- // read range coverage index
- int x = in.readTTFUShort();
+ // read range coverage (mapping) index
+ int m = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + x );
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m );
}
- rsa[i] = s;
- rea[i] = e;
- rxa[i] = x;
- seCoverage.add ( new GlyphCoverageTable.CoverageRange ( s, e, x ) );
+ entries.add ( new GlyphCoverageTable.MappingRange ( s, e, m ) );
}
+ return GlyphCoverageTable.createCoverageTable ( entries );
}
- private void readCoverageTable(FontFileReader in, String label, long tableOffset) throws IOException {
+ private GlyphCoverageTable readCoverageTable(FontFileReader in, String label, long tableOffset) throws IOException {
+ GlyphCoverageTable gct;
long cp = in.getCurrentPos();
in.seekSet(tableOffset);
// read coverage table format
int cf = in.readTTFUShort();
if ( cf == 1 ) {
- readCoverageTableFormat1 ( in, label, tableOffset, cf );
+ gct = readCoverageTableFormat1 ( in, label, tableOffset, cf );
} else if ( cf == 2 ) {
- readCoverageTableFormat2 ( in, label, tableOffset, cf );
+ gct = readCoverageTableFormat2 ( in, label, tableOffset, cf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported coverage table format: " + cf );
}
in.seekSet ( cp );
+ return gct;
}
- /* not used yet
- private void readClassDefTableFormat1(FontFileReader in, long tableOffset, int classFormat) throws IOException {
+ private GlyphClassTable readClassDefTableFormat1(FontFileReader in, String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip ( 2 );
+ // read start glyph
+ int sg = in.readTTFUShort();
+ entries.add ( Integer.valueOf(sg) );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read glyph classes
+ int[] ca = new int[ng];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ int gc = in.readTTFUShort();
+ ca[i] = gc;
+ entries.add ( Integer.valueOf(gc) );
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " glyph classes: " + toString(ca) );
+ }
+ return GlyphClassTable.createClassTable ( entries );
}
- private void readClassDefTableFormat2(FontFileReader in, long tableOffset, int classFormat) throws IOException {
+ private GlyphClassTable readClassDefTableFormat2(FontFileReader in, String label, long tableOffset, int classFormat) throws IOException {
+ List entries = new java.util.ArrayList();
in.seekSet(tableOffset);
// skip over format (already known)
in.skip ( 2 );
+ // read range record count
+ int nr = in.readTTFUShort();
+ for ( int i = 0, n = nr; i < n; i++ ) {
+ // read range start
+ int s = in.readTTFUShort();
+ // read range end
+ int e = in.readTTFUShort();
+ // read range glyph class (mapping) index
+ int m = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(label + " range[" + i + "]: [" + s + "," + e + "]: " + m );
+ }
+ entries.add ( new GlyphClassTable.MappingRange ( s, e, m ) );
+ }
+ return GlyphClassTable.createClassTable ( entries );
}
- private void readClassDefTable(FontFileReader in, long tableOffset) throws IOException {
+ private GlyphClassTable readClassDefTable(FontFileReader in, String label, long tableOffset) throws IOException {
+ GlyphClassTable gct;
long cp = in.getCurrentPos();
in.seekSet(tableOffset);
// read class table format
int cf = in.readTTFUShort();
if ( cf == 1 ) {
- readClassDefTableFormat1 ( in, tableOffset, cf );
+ gct = readClassDefTableFormat1 ( in, label, tableOffset, cf );
} else if ( cf == 2 ) {
- readClassDefTableFormat2 ( in, tableOffset, cf );
+ gct = readClassDefTableFormat2 ( in, label, tableOffset, cf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported class definition table format: " + cf );
}
in.seekSet ( cp );
+ return gct;
}
- */
private void readSingleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
int dg = in.readTTFShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " single substitution format: " + subtableFormat + " (delta)" );
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (delta)" );
log.debug(tableTag + " single substitution coverage table offset: " + co );
log.debug(tableTag + " single substitution delta: " + dg );
}
// read coverage table
- readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
+ seMapping = readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
seEntries.add ( Integer.valueOf ( dg ) );
}
int ng = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " single substitution format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " single substitution subtable format: " + subtableFormat + " (mapped)" );
log.debug(tableTag + " single substitution coverage table offset: " + co );
log.debug(tableTag + " single substitution glyph count: " + ng );
}
// read coverage table
- readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
+ seMapping = readCoverageTable ( in, tableTag + " single substitution coverage", subtableOffset + co );
// read glyph substitutions
int[] gsa = new int[ng];
for ( int i = 0, n = ng; i < n; i++ ) {
private int readSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readSingleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 2 ) {
readSingleSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported single substitution subtable format: " + sf );
}
return sf;
}
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " multiple substitution format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " multiple substitution subtable format: " + subtableFormat + " (mapped)" );
log.debug(tableTag + " multiple substitution coverage table offset: " + co );
log.debug(tableTag + " multiple substitution sequence count: " + ns );
}
// read coverage table
- readCoverageTable ( in, tableTag + " multiple substitution coverage", subtableOffset + co );
+ seMapping = readCoverageTable ( in, tableTag + " multiple substitution coverage", subtableOffset + co );
// read sequence table offsets
int[] soa = new int[ns];
for ( int i = 0, n = ns; i < n; i++ ) {
soa[i] = in.readTTFUShort();
}
// read sequence tables
+ int[][] gsa = new int [ ns ] [];
for ( int i = 0, n = ns; i < n; i++ ) {
int so = soa[i];
- in.seekSet(subtableOffset + so);
- // read glyph count
- int ng = in.readTTFUShort();
- int[] ga = new int[ng];
- for ( int j = 0; j < ng; j++ ) {
- int gs = in.readTTFUShort();
- ga[j] = gs;
+ int[] ga;
+ if ( so > 0 ) {
+ in.seekSet(subtableOffset + so);
+ // read glyph count
+ int ng = in.readTTFUShort();
+ ga = new int[ng];
+ for ( int j = 0; j < ng; j++ ) {
+ ga[j] = in.readTTFUShort();
+ }
+ } else {
+ ga = null;
}
if (log.isDebugEnabled()) {
log.debug(tableTag + " multiple substitution sequence[" + i + "]: " + toString ( ga ) );
}
+ gsa [ i ] = ga;
}
+ seEntries.add ( gsa );
}
private int readMultipleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readMultipleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported multiple substitution subtable format: " + sf );
}
return sf;
}
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " alternate substitution format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " alternate substitution subtable format: " + subtableFormat + " (mapped)" );
log.debug(tableTag + " alternate substitution coverage table offset: " + co );
log.debug(tableTag + " alternate substitution alternate set count: " + ns );
}
// read coverage table
- readCoverageTable ( in, tableTag + " alternate substitution coverage", subtableOffset + co );
+ seMapping = readCoverageTable ( in, tableTag + " alternate substitution coverage", subtableOffset + co );
// read alternate set table offsets
int[] soa = new int[ns];
for ( int i = 0, n = ns; i < n; i++ ) {
if (log.isDebugEnabled()) {
log.debug(tableTag + " alternate substitution alternate set[" + i + "]: " + toString ( ga ) );
}
+ seEntries.add ( ga );
}
}
private int readAlternateSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readAlternateSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported alternate substitution subtable format: " + sf );
}
return sf;
}
int ns = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " ligature substitution format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " ligature substitution subtable format: " + subtableFormat + " (mapped)" );
log.debug(tableTag + " ligature substitution coverage table offset: " + co );
log.debug(tableTag + " ligature substitution ligature set count: " + ns );
}
// read coverage table
- readCoverageTable ( in, tableTag + " ligature substitution coverage", subtableOffset + co );
+ seMapping = readCoverageTable ( in, tableTag + " ligature substitution coverage", subtableOffset + co );
// read ligature set table offsets
int[] soa = new int[ns];
for ( int i = 0, n = ns; i < n; i++ ) {
private int readLigatureSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
readLigatureSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported ligature substitution subtable format: " + sf );
}
return sf;
}
- private int readContextSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private GlyphTable.RuleLookup[] readRuleLookups(FontFileReader in, int numLookups, String header) throws IOException {
+ GlyphTable.RuleLookup[] la = new GlyphTable.RuleLookup [ numLookups ];
+ for ( int i = 0, n = numLookups; i < n; i++ ) {
+ int sequenceIndex = in.readTTFUShort();
+ int lookupIndex = in.readTTFUShort();
+ la [ i ] = new GlyphTable.RuleLookup ( sequenceIndex, lookupIndex );
+ // dump info if debugging and header is non-null
+ if ( log.isDebugEnabled() && ( header != null ) ) {
+ log.debug(header + "lookup[" + i + "]: " + la[i]);
+ }
+ }
+ return la;
+ }
+
+ private void readContextualSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " contextual substitution rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " contextual substitution rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
}
- private void readChainedContextSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ private void readContextualSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
- // read subrule set count
- int ns = in.readTTFUShort();
+ // read class def table offset
+ int cdo = in.readTTFUShort();
+ // read class rule set count
+ int ngc = in.readTTFUShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (simple)" );
- log.debug(tableTag + " chained context substitution coverage table offset: " + co );
- log.debug(tableTag + " chained context substitution subrule set count: " + ns );
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " contextual substitution class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " contextual substitution class set offset[" + i + "]: " + csoa[i] );
+ }
}
// read coverage table
- readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + co );
- // read subrule set table offsets
- int[] soa = new int[ns];
- for ( int i = 0, n = ns; i < n; i++ ) {
- soa[i] = in.readTTFUShort();
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
}
- // read subrule set tables
- for ( int i = 0, n = ns; i < n; i++ ) {
- int so = soa[i];
- in.seekSet(subtableOffset + so);
- // read subrule table count
- int nst = in.readTTFUShort();
- int[] stoa = new int[nst];
- for ( int j = 0; j < nst; j++ ) {
- stoa[j] = in.readTTFUShort();
- }
- for ( int j = 0; j < nst; j++ ) {
- int sto = stoa[j];
- in.seekSet(subtableOffset + so + sto);
- // read backtrack glyph count
- int nbg = in.readTTFUShort();
- int[] bga = new int[nbg];
- // read backtrack glyphs
- for ( int k = 0; k < nbg; k++ ) {
- bga[k] = in.readTTFUShort();
- }
- // read input glyph count
- int nig = in.readTTFUShort();
- int[] iga = new int [ nig - 1 ];
- // read input glyphs
- for ( int k = 0; k < nig - 1; k++ ) {
- iga[k] = in.readTTFUShort();
- }
- // read lookahead glyph count
- int nlg = in.readTTFUShort();
- int[] lga = new int[nlg];
- // read lookahead glyphs
- for ( int k = 0; k < nlg; k++ ) {
- lga[k] = in.readTTFUShort();
- }
- // read substitution lookup record count
- int nsl = in.readTTFUShort();
- int[] sia = new int[nsl];
- int[] lia = new int[nsl];
- // read substitution lookup records
- for ( int k = 0; k < nsl; k++ ) {
- // read sequence index
- sia[k] = in.readTTFUShort();
- // read lookup list index
- lia[k] = in.readTTFUShort();
+ // read class definition table
+ GlyphClassTable cdt;
+ if ( cdo > 0 ) {
+ cdt = readClassDefTable ( in, tableTag + " contextual substitution class definition", subtableOffset + cdo );
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
}
- if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution subrule set[" + i + "]: backtrack [" + toString(bga) + "]" );
- log.debug(tableTag + " chained context substitution subrule set[" + i + "]: input [" + toString(iga) + "]" );
- log.debug(tableTag + " chained context substitution subrule set[" + i + "]: lookahead [" + toString(lga) + "]" );
- log.debug(tableTag + " chained context substitution lookup count: " + nsl );
- for ( int k = 0; k < nsl; k++ ) {
- log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[k] + "," + lia[k] + "]" );
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes );
+ } else {
+ assert ro > 0 : "unexpected null subclass rule offset";
+ r = null;
}
+ ra [ j ] = r;
}
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read glyph (input sequence length) count
+ int ng = in.readTTFUShort();
+ // read substitution lookup count
+ int nl = in.readTTFUShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ gcoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual substitution format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " contextual substitution glyph input sequence length count: " + ng );
+ log.debug(tableTag + " contextual substitution lookup count: " + nl );
+ for ( int i = 0; i < ng; i++ ) {
+ log.debug(tableTag + " contextual substitution coverage table offset[" + i + "]: " + gcoa[i] );
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if ( gco > 0 ) {
+ gct = readCoverageTable ( in, tableTag + " contextual substitution coverage[" + i + "]", subtableOffset + gco );
+ } else {
+ gct = null;
}
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( gca != null ) && ( gca.length > 0 );
+ seMapping = gca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readContextualSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readContextualSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readContextualSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readContextualSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported contextual substitution subtable format: " + sf );
}
+ return sf;
}
- private void readChainedContextSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ private void readChainedContextualSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read coverage offset
int co = in.readTTFUShort();
- // read backtrack classdef table offset
- int bo = in.readTTFUShort();
- // read input classdef table offset
- int io = in.readTTFUShort();
- // read lookahead classdef table offset
- int lo = in.readTTFUShort();
- // read subclass set count
- int ns = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (class based)" );
- log.debug(tableTag + " chained context substitution coverage table offset: " + co );
- log.debug(tableTag + " chained context substitution backtrack classdef table offset: " + bo );
- log.debug(tableTag + " chained context substitution input classdef table offset: " + io );
- log.debug(tableTag + " chained context substitution lookahead classdef table offset: " + lo );
- log.debug(tableTag + " chained context substitution subclass set count: " + ns );
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual substitution rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " chained contextual substitution rule set offset[" + i + "]: " + rsoa[i] );
+ }
}
// read coverage table
- readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + co );
- // read subclass set table offsets
- int[] soa = new int[ns];
- for ( int i = 0, n = ns; i < n; i++ ) {
- soa[i] = in.readTTFUShort();
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " chained contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) {
+ backtrackGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) {
+ lookaheadGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
}
- // read subclass set tables
- for ( int i = 0, n = ns; i < n; i++ ) {
- int so = soa[i];
- if ( so == 0 ) {
- continue;
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readChainedContextualSubTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack class def table offset
+ int bcdo = in.readTTFUShort();
+ // read input class def table offset
+ int icdo = in.readTTFUShort();
+ // read lookahead class def table offset
+ int lcdo = in.readTTFUShort();
+ // read class set count
+ int ngc = in.readTTFUShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual substitution class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " chained contextual substitution class set offset[" + i + "]: " + csoa[i] );
}
- in.seekSet(subtableOffset + so);
- // read subclass rule table count
- int nst = in.readTTFUShort();
- int[] stoa = new int[nst];
- for ( int j = 0; j < nst; j++ ) {
- stoa[j] = in.readTTFUShort();
- }
- for ( int j = 0; j < nst; j++ ) {
- int sto = stoa[j];
- in.seekSet(subtableOffset + so + sto);
- // read backtrack class count
- int nbc = in.readTTFUShort();
- int[] bca = new int[nbc];
- // read backtrack classes
- for ( int k = 0; k < nbc; k++ ) {
- bca[k] = in.readTTFUShort();
- }
- // read input class count
- int nic = in.readTTFUShort();
- int[] ica = new int [ nic - 1 ];
- // read inpput classes
- for ( int k = 0; k < nic - 1; k++ ) {
- ica[k] = in.readTTFUShort();
- }
- // read lookahead class count
- int nlc = in.readTTFUShort();
- int[] lca = new int[nlc];
- // read lookahead classes
- for ( int k = 0; k < nlc; k++ ) {
- lca[k] = in.readTTFUShort();
- }
- // read substitution lookup record count
- int nsl = in.readTTFUShort();
- int[] sia = new int[nsl];
- int[] lia = new int[nsl];
- // read substitution lookup records
- for ( int k = 0; k < nsl; k++ ) {
- // read sequence index
- sia[k] = in.readTTFUShort();
- // read lookup list index
- lia[k] = in.readTTFUShort();
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " chained contextual substitution coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if ( bcdo > 0 ) {
+ bcdt = readClassDefTable ( in, tableTag + " contextual substitution backtrack class definition", subtableOffset + bcdo );
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if ( icdo > 0 ) {
+ icdt = readClassDefTable ( in, tableTag + " contextual substitution input class definition", subtableOffset + icdo );
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if ( lcdo > 0 ) {
+ lcdt = readClassDefTable ( in, tableTag + " contextual substitution lookahead class definition", subtableOffset + lcdo );
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
}
- if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution subclass set[" + i + "]: backtrack [" + toString(bca) + "]" );
- log.debug(tableTag + " chained context substitution subclass set[" + i + "]: input [" + toString(ica) + "]" );
- log.debug(tableTag + " chained context substitution subclass set[" + i + "]: lookahead [" + toString(lca) + "]" );
- log.debug(tableTag + " chained context substitution lookup count: " + nsl );
- for ( int k = 0; k < nsl; k++ ) {
- log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[k] + "," + lia[k] + "]" );
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ChainedClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read backtrack glyph class count
+ int nbc = in.readTTFUShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) {
+ backtrackClasses [ k ] = in.readTTFUShort();
+ }
+ // read input glyph class count
+ int nic = in.readTTFUShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph class count
+ int nlc = in.readTTFUShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) {
+ lookaheadClasses [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual substitution lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses );
+ } else {
+ r = null;
}
+ ra [ j ] = r;
}
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
}
- }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( icdt );
+ seEntries.add ( bcdt );
+ seEntries.add ( lcdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
}
- private void readChainedContextSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ private void readChainedContextualSubTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
String tableTag = "GSUB";
in.seekSet(subtableOffset);
// skip over format (already known)
in.skip ( 2 );
// read backtrack glyph count
int nbg = in.readTTFUShort();
- // read backtrack glyph coverage table offsets
- int[] boa = new int[nbg];
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
for ( int i = 0; i < nbg; i++ ) {
- boa[i] = in.readTTFUShort();
+ bgcoa [ i ] = in.readTTFUShort();
}
// read input glyph count
int nig = in.readTTFUShort();
- // read input glyph coverage table offsets
- int[] ioa = new int[nig];
+ // read backtrack glyph coverage offsets
+ int[] igcoa = new int [ nig ];
for ( int i = 0; i < nig; i++ ) {
- ioa[i] = in.readTTFUShort();
+ igcoa [ i ] = in.readTTFUShort();
}
// read lookahead glyph count
int nlg = in.readTTFUShort();
- // read lookahead glyph coverage table offsets
- int[] loa = new int[nlg];
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
for ( int i = 0; i < nlg; i++ ) {
- loa[i] = in.readTTFUShort();
- }
- // read substitution lookup record count
- int nsl = in.readTTFUShort();
- int[] sia = new int[nsl];
- int[] lia = new int[nsl];
- // read substitution lookup records
- for ( int i = 0; i < nsl; i++ ) {
- // read sequence index
- sia[i] = in.readTTFUShort();
- // read lookup list index
- lia[i] = in.readTTFUShort();
+ lgcoa [ i ] = in.readTTFUShort();
}
+ // read substitution lookup count
+ int nl = in.readTTFUShort();
// dump info if debugging
if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution format: " + subtableFormat + " (coverage based)" );
- log.debug(tableTag + " chained context substitution backtrack coverage table offsets: " + toString(boa) );
- log.debug(tableTag + " chained context substitution input coverage table offsets: " + toString(ioa) );
- log.debug(tableTag + " chained context substitution lookahead coverage table offsets: " + toString(loa) );
- log.debug(tableTag + " chained context substitution lookup count: " + nsl );
- for ( int i = 0; i < nsl; i++ ) {
- log.debug(tableTag + " chained context substitution lookup[" + i + "]: [" + sia[i] + "," + lia[i] + "]" );
+ log.debug(tableTag + " chained contextual substitution format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " chained contextual substitution backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual substitution input glyph count: " + nig );
+ for ( int i = 0; i < nig; i++ ) {
+ log.debug(tableTag + " chained contextual substitution input coverage table offset[" + i + "]: " + igcoa[i] );
}
+ log.debug(tableTag + " chained contextual substitution lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual substitution lookup count: " + nl );
}
// read backtrack coverage tables
- for ( int i = 0; i < boa.length; i++ ) {
- if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution backtrack coverage table[" + i + "]" );
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( in, tableTag + " chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
}
- readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + boa [ i ] );
+ bgca[i] = bgct;
}
// read input coverage tables
- for ( int i = 0; i < ioa.length; i++ ) {
- if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution input coverage table[" + i + "]" );
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for ( int i = 0; i < nig; i++ ) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if ( igco > 0 ) {
+ igct = readCoverageTable ( in, tableTag + " chained contextual substitution input coverage[" + i + "]", subtableOffset + igco );
+ } else {
+ igct = null;
}
- readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + ioa [ i ] );
+ igca[i] = igct;
}
// read lookahead coverage tables
- for ( int i = 0; i < loa.length; i++ ) {
- if (log.isDebugEnabled()) {
- log.debug(tableTag + " chained context substitution lookahead coverage table[" + i + "]" );
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( in, tableTag + " chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
}
- readCoverageTable ( in, tableTag + " chained context substitution coverage", subtableOffset + loa [ i ] );
+ lgca[i] = lgct;
}
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual substitution lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( igca != null ) && ( igca.length > 0 );
+ seMapping = igca[0];
+ seEntries.add ( rsa );
}
- private int readChainedContextSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private int readChainedContextualSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
if ( sf == 1 ) {
- readChainedContextSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ readChainedContextualSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 2 ) {
- readChainedContextSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ readChainedContextualSubTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
} else if ( sf == 3 ) {
- readChainedContextSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ readChainedContextualSubTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported chained contextual substitution subtable format: " + sf );
}
return sf;
}
- private int readExtensionSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private void readExtensionSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read extension lookup type
+ int lt = in.readTTFUShort();
+ // read extension offset
+ long eo = in.readTTFULong();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension substitution subtable format: " + subtableFormat );
+ log.debug(tableTag + " extension substitution lookup type: " + lt );
+ log.debug(tableTag + " extension substitution lookup table offset: " + eo );
+ }
+ // read referenced subtable from extended offset
+ readGSUBSubtable ( in, lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo );
}
- private int readReverseChainedSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private int readExtensionSubTable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read substitution subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readExtensionSubTableFormat1 ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported extension substitution subtable format: " + sf );
+ }
return sf;
}
- private void readGSUBSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
- initSESubState();
- int subtableFormat = -1;
- switch ( lookupType ) {
- case GSUBLookupType.SINGLE:
- subtableFormat = readSingleSubTable ( in, lookupType, lookupFlags, subtableOffset );
- break;
- case GSUBLookupType.MULTIPLE:
- subtableFormat = readMultipleSubTable ( in, lookupType, lookupFlags, subtableOffset );
- break;
- case GSUBLookupType.ALTERNATE:
- subtableFormat = readAlternateSubTable ( in, lookupType, lookupFlags, subtableOffset );
- break;
+ private void readReverseChainedSingleSubTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GSUB";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for ( int i = 0; i < nbg; i++ ) {
+ bgcoa [ i ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for ( int i = 0; i < nlg; i++ ) {
+ lgcoa [ i ] = in.readTTFUShort();
+ }
+ // read substitution (output) glyph count
+ int ng = in.readTTFUShort();
+ // read substitution (output) glyphs
+ int[] glyphs = new int [ ng ];
+ for ( int i = 0, n = ng; i < n; i++ ) {
+ glyphs [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " reverse chained contextual substitution format: " + subtableFormat );
+ log.debug(tableTag + " reverse chained contextual substitution coverage table offset: " + co );
+ log.debug(tableTag + " reverse chained contextual substitution backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " reverse chained contextual substitution backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " reverse chained contextual substitution lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " reverse chained contextual substitution lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " reverse chained contextual substitution glyphs: " + toString(glyphs) );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution coverage", subtableOffset + co );
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa[i];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa[i];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( in, tableTag + " reverse chained contextual substitution lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( bgca );
+ seEntries.add ( lgca );
+ seEntries.add ( glyphs );
+ }
+
+ private int readReverseChainedSingleSubTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read substitution subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readReverseChainedSingleSubTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported reverse chained single substitution subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readGSUBSubtable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
+ initSESubState();
+ int subtableFormat = -1;
+ switch ( lookupType ) {
+ case GSUBLookupType.SINGLE:
+ subtableFormat = readSingleSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.MULTIPLE:
+ subtableFormat = readMultipleSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ break;
+ case GSUBLookupType.ALTERNATE:
+ subtableFormat = readAlternateSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ break;
case GSUBLookupType.LIGATURE:
subtableFormat = readLigatureSubTable ( in, lookupType, lookupFlags, subtableOffset );
break;
- case GSUBLookupType.CONTEXT:
- subtableFormat = readContextSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ case GSUBLookupType.CONTEXTUAL:
+ subtableFormat = readContextualSubTable ( in, lookupType, lookupFlags, subtableOffset );
break;
- case GSUBLookupType.CHAINED_CONTEXT:
- subtableFormat = readChainedContextSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ case GSUBLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualSubTable ( in, lookupType, lookupFlags, subtableOffset );
break;
case GSUBLookupType.REVERSE_CHAINED_SINGLE:
subtableFormat = readReverseChainedSingleSubTable ( in, lookupType, lookupFlags, subtableOffset );
break;
case GSUBLookupType.EXTENSION:
- subtableFormat = readExtensionSubTable ( in, lookupType, lookupFlags, subtableOffset );
+ subtableFormat = readExtensionSubTable ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset );
break;
default:
break;
resetSESubState();
}
+ private GlyphPositioningTable.DeviceTable readPosDeviceTable(FontFileReader in, long subtableOffset, long deviceTableOffset) throws IOException {
+ long cp = in.getCurrentPos();
+ in.seekSet(subtableOffset + deviceTableOffset);
+ // read start size
+ int ss = in.readTTFUShort();
+ // read end size
+ int es = in.readTTFUShort();
+ // read delta format
+ int df = in.readTTFUShort();
+ // read deltas
+ int n = ( es - ss ) + 1;
+ int[] da = new int [ n ];
+ int s1, m1, dm, dd, s2;
+ if ( df == 1 ) {
+ s1 = 14; m1 = 0x3; dm = 1; dd = 4; s2 = 2;
+ } else if ( df == 2 ) {
+ s1 = 12; m1 = 0xF; dm = 7; dd = 16; s2 = 4;
+ } else if ( df == 3 ) {
+ s1 = 8; m1 = 0xFF; dm = 127; dd = 256; s2 = 8;
+ } else {
+ throw new UnsupportedOperationException ( "unsupported device table delta format: " + df );
+ }
+ for ( int i = 0; ( i < n ) && ( s2 > 0 );) {
+ int p = in.readTTFUShort();
+ for ( int j = 0, k = 16 / s2; j < k; j++ ) {
+ int d = ( p >> s1 ) & m1;
+ if ( d > dm ) {
+ d -= dd;
+ }
+ if ( i < n ) {
+ da [ i++ ] = d;
+ } else {
+ break;
+ }
+ p <<= s2;
+ }
+ }
+ in.seekSet(cp);
+ return new GlyphPositioningTable.DeviceTable ( ss, es, da );
+ }
+
+ private GlyphPositioningTable.Value readPosValue(FontFileReader in, long subtableOffset, int valueFormat) throws IOException {
+ // XPlacement
+ int xp;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT ) != 0 ) {
+ xp = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ xp = 0;
+ }
+ // YPlacement
+ int yp;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT ) != 0 ) {
+ yp = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ yp = 0;
+ }
+ // XAdvance
+ int xa;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE ) != 0 ) {
+ xa = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ xa = 0;
+ }
+ // YAdvance
+ int ya;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE ) != 0 ) {
+ ya = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ } else {
+ ya = 0;
+ }
+ // XPlaDevice
+ GlyphPositioningTable.DeviceTable xpd;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_PLACEMENT_DEVICE ) != 0 ) {
+ int xpdo = in.readTTFUShort();
+ xpd = readPosDeviceTable ( in, subtableOffset, xpdo );
+ } else {
+ xpd = null;
+ }
+ // YPlaDevice
+ GlyphPositioningTable.DeviceTable ypd;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_PLACEMENT_DEVICE ) != 0 ) {
+ int ypdo = in.readTTFUShort();
+ ypd = readPosDeviceTable ( in, subtableOffset, ypdo );
+ } else {
+ ypd = null;
+ }
+ // XAdvDevice
+ GlyphPositioningTable.DeviceTable xad;
+ if ( ( valueFormat & GlyphPositioningTable.Value.X_ADVANCE_DEVICE ) != 0 ) {
+ int xado = in.readTTFUShort();
+ xad = readPosDeviceTable ( in, subtableOffset, xado );
+ } else {
+ xad = null;
+ }
+ // YAdvDevice
+ GlyphPositioningTable.DeviceTable yad;
+ if ( ( valueFormat & GlyphPositioningTable.Value.Y_ADVANCE_DEVICE ) != 0 ) {
+ int yado = in.readTTFUShort();
+ yad = readPosDeviceTable ( in, subtableOffset, yado );
+ } else {
+ yad = null;
+ }
+ return new GlyphPositioningTable.Value ( xp, yp, xa, ya, xpd, ypd, xad, yad );
+ }
+
+ private void readSinglePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format
+ int vf = in.readTTFUShort();
+ // read value
+ GlyphPositioningTable.Value v = readPosValue ( in, subtableOffset, vf );
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (delta)" );
+ log.debug(tableTag + " single positioning coverage table offset: " + co );
+ log.debug(tableTag + " single positioning value: " + v );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " single positioning coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ seEntries.add ( v );
+ }
+
+ private void readSinglePosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format
+ int vf = in.readTTFUShort();
+ // read value count
+ int nv = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning subtable format: " + subtableFormat + " (mapped)" );
+ log.debug(tableTag + " single positioning coverage table offset: " + co );
+ log.debug(tableTag + " single positioning value count: " + nv );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " single positioning coverage", subtableOffset + co );
+ // read positioning values
+ GlyphPositioningTable.Value[] pva = new GlyphPositioningTable.Value[nv];
+ for ( int i = 0, n = nv; i < n; i++ ) {
+ GlyphPositioningTable.Value pv = readPosValue ( in, subtableOffset, vf );
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " single positioning value[" + i + "]: " + pv );
+ }
+ pva[i] = pv;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( pva );
+ }
+
private int readSinglePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read positionining subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readSinglePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readSinglePosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported single positioning subtable format: " + sf );
+ }
return sf;
}
+ private GlyphPositioningTable.PairValues readPosPairValues(FontFileReader in, long subtableOffset, boolean hasGlyph, int vf1, int vf2) throws IOException {
+ // read glyph (if present)
+ int glyph;
+ if ( hasGlyph ) {
+ glyph = in.readTTFUShort();
+ } else {
+ glyph = 0;
+ }
+ // read first value (if present)
+ GlyphPositioningTable.Value v1;
+ if ( vf1 != 0 ) {
+ v1 = readPosValue ( in, subtableOffset, vf1 );
+ } else {
+ v1 = null;
+ }
+ // read second value (if present)
+ GlyphPositioningTable.Value v2;
+ if ( vf2 != 0 ) {
+ v2 = readPosValue ( in, subtableOffset, vf2 );
+ } else {
+ v2 = null;
+ }
+ return new GlyphPositioningTable.PairValues ( glyph, v1, v2 );
+ }
+
+ private GlyphPositioningTable.PairValues[] readPosPairSetTable(FontFileReader in, long subtableOffset, int pairSetTableOffset, int vf1, int vf2) throws IOException {
+ String tableTag = "GPOS";
+ long cp = in.getCurrentPos();
+ in.seekSet(subtableOffset + pairSetTableOffset);
+ // read pair values count
+ int npv = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table offset: " + pairSetTableOffset );
+ log.debug(tableTag + " pair set table values count: " + npv );
+ }
+ // read pair values
+ GlyphPositioningTable.PairValues[] pva = new GlyphPositioningTable.PairValues [ npv ];
+ for ( int i = 0, n = npv; i < n; i++ ) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues ( in, subtableOffset, true, vf1, vf2 );
+ pva [ i ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "]: " + pv);
+ }
+ }
+ in.seekSet(cp);
+ return pva;
+ }
+
+ private void readPairPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format for first glyph
+ int vf1 = in.readTTFUShort();
+ // read value format for second glyph
+ int vf2 = in.readTTFUShort();
+ // read number (count) of pair sets
+ int nps = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " pair positioning coverage table offset: " + co );
+ log.debug(tableTag + " pair positioning value format #1: " + vf1 );
+ log.debug(tableTag + " pair positioning value format #2: " + vf2 );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " pair positioning coverage", subtableOffset + co );
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nps ][];
+ for ( int i = 0, n = nps; i < n; i++ ) {
+ // read pair set offset
+ int pso = in.readTTFUShort();
+ // read pair set table at offset
+ pvm [ i ] = readPosPairSetTable ( in, subtableOffset, pso, vf1, vf2 );
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( pvm );
+ }
+
+ private void readPairPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read value format for first glyph
+ int vf1 = in.readTTFUShort();
+ // read value format for second glyph
+ int vf2 = in.readTTFUShort();
+ // read class def 1 offset
+ int cd1o = in.readTTFUShort();
+ // read class def 2 offset
+ int cd2o = in.readTTFUShort();
+ // read number (count) of classes in class def 1 table
+ int nc1 = in.readTTFUShort();
+ // read number (count) of classes in class def 2 table
+ int nc2 = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " pair positioning coverage table offset: " + co );
+ log.debug(tableTag + " pair positioning value format #1: " + vf1 );
+ log.debug(tableTag + " pair positioning value format #2: " + vf2 );
+ log.debug(tableTag + " pair positioning class def table #1 offset: " + cd1o );
+ log.debug(tableTag + " pair positioning class def table #2 offset: " + cd2o );
+ log.debug(tableTag + " pair positioning class #1 count: " + nc1 );
+ log.debug(tableTag + " pair positioning class #2 count: " + nc2 );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " pair positioning coverage", subtableOffset + co );
+ // read class definition table #1
+ GlyphClassTable cdt1 = readClassDefTable ( in, tableTag + " pair positioning class definition #1", subtableOffset + cd1o );
+ // read class definition table #2
+ GlyphClassTable cdt2 = readClassDefTable ( in, tableTag + " pair positioning class definition #2", subtableOffset + cd2o );
+ // read pair value matrix
+ GlyphPositioningTable.PairValues[][] pvm = new GlyphPositioningTable.PairValues [ nc1 ] [ nc2 ];
+ for ( int i = 0; i < nc1; i++ ) {
+ for ( int j = 0; j < nc2; j++ ) {
+ GlyphPositioningTable.PairValues pv = readPosPairValues ( in, subtableOffset, false, vf1, vf2 );
+ pvm [ i ] [ j ] = pv;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " pair set table value[" + i + "][" + j + "]: " + pv);
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt1 );
+ seEntries.add ( cdt2 );
+ seEntries.add ( Integer.valueOf ( nc1 ) );
+ seEntries.add ( Integer.valueOf ( nc2 ) );
+ seEntries.add ( pvm );
+ }
+
private int readPairPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read positioning subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readPairPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readPairPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported pair positioning subtable format: " + sf );
+ }
return sf;
}
+ private GlyphPositioningTable.Anchor readPosAnchor(FontFileReader in, long anchorTableOffset) throws IOException {
+ GlyphPositioningTable.Anchor a;
+ long cp = in.getCurrentPos();
+ in.seekSet(anchorTableOffset);
+ // read anchor table format
+ int af = in.readTTFUShort();
+ if ( af == 1 ) {
+ // read x coordinate
+ int x = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ a = new GlyphPositioningTable.Anchor ( x, y );
+ } else if ( af == 2 ) {
+ // read x coordinate
+ int x = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read anchor point index
+ int ap = in.readTTFUShort();
+ a = new GlyphPositioningTable.Anchor ( x, y, ap );
+ } else if ( af == 3 ) {
+ // read x coordinate
+ int x = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read y coordinate
+ int y = convertTTFUnit2PDFUnit ( in.readTTFShort() );
+ // read x device table offset
+ int xdo = in.readTTFUShort();
+ // read y device table offset
+ int ydo = in.readTTFUShort();
+ // read x device table (if present)
+ GlyphPositioningTable.DeviceTable xd;
+ if ( xdo != 0 ) {
+ xd = readPosDeviceTable ( in, cp, xdo );
+ } else {
+ xd = null;
+ }
+ // read y device table (if present)
+ GlyphPositioningTable.DeviceTable yd;
+ if ( ydo != 0 ) {
+ yd = readPosDeviceTable ( in, cp, ydo );
+ } else {
+ yd = null;
+ }
+ a = new GlyphPositioningTable.Anchor ( x, y, xd, yd );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported positioning anchor format: " + af );
+ }
+ in.seekSet(cp);
+ return a;
+ }
+
+ private void readCursivePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read entry/exit count
+ int ec = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " cursive positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " cursive positioning coverage table offset: " + co );
+ log.debug(tableTag + " cursive positioning entry/exit count: " + ec );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " cursive positioning coverage", subtableOffset + co );
+ // read entry/exit records
+ GlyphPositioningTable.Anchor[] aa = new GlyphPositioningTable.Anchor [ ec * 2 ];
+ for ( int i = 0, n = ec; i < n; i++ ) {
+ // read entry anchor offset
+ int eno = in.readTTFUShort();
+ // read exit anchor offset
+ int exo = in.readTTFUShort();
+ // read entry anchor
+ GlyphPositioningTable.Anchor ena;
+ if ( eno > 0 ) {
+ ena = readPosAnchor ( in, subtableOffset + eno );
+ } else {
+ ena = null;
+ }
+ // read exit anchor
+ GlyphPositioningTable.Anchor exa;
+ if ( exo > 0 ) {
+ exa = readPosAnchor ( in, subtableOffset + exo );
+ } else {
+ exa = null;
+ }
+ aa [ ( i * 2 ) + 0 ] = ena;
+ aa [ ( i * 2 ) + 1 ] = exa;
+ if (log.isDebugEnabled()) {
+ if ( ena != null ) {
+ log.debug(tableTag + " cursive entry anchor [" + i + "]: " + ena );
+ }
+ if ( exa != null ) {
+ log.debug(tableTag + " cursive exit anchor [" + i + "]: " + exa );
+ }
+ }
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( aa );
+ }
+
private int readCursivePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readCursivePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported cursive positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToBasePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark coverage offset
+ int mco = in.readTTFUShort();
+ // read base coverage offset
+ int bco = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark array offset
+ int mao = in.readTTFUShort();
+ // read base array offset
+ int bao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-base positioning mark coverage table offset: " + mco );
+ log.debug(tableTag + " mark-to-base positioning base coverage table offset: " + bco );
+ log.debug(tableTag + " mark-to-base positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-base positioning mark array offset: " + mao );
+ log.debug(tableTag + " mark-to-base positioning base array offset: " + bao );
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable ( in, tableTag + " mark-to-base positioning mark coverage", subtableOffset + mco );
+ // read base coverage table
+ GlyphCoverageTable bct = readCoverageTable ( in, tableTag + " mark-to-base positioning base coverage", subtableOffset + bco );
+ // read mark anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + mao);
+ // read mark count
+ int nm = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark count: " + nm );
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for ( int i = 0; i < nm; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + mao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning mark anchor[" + i + "]: " + ma);
+ }
+
+ }
+ // read base anchor matrix
+ // seek to base array
+ in.seekSet(subtableOffset + bao);
+ // read base count
+ int nb = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base count: " + nb );
+ }
+ // read anchor matrix, where i:{0...baseCount - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] bam = new GlyphPositioningTable.Anchor [ nb ] [ nmc ];
+ for ( int i = 0; i < nb; i++ ) {
+ for ( int j = 0; j < nmc; j++ ) {
+ // read base anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + bao + ao );
+ } else {
+ a = null;
+ }
+ bam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-base positioning base anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add ( bct );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( maa );
+ seEntries.add ( bam );
+ }
+
+ private int readMarkToBasePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToBasePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported mark-to-base positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToLigaturePosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark coverage offset
+ int mco = in.readTTFUShort();
+ // read ligature coverage offset
+ int lco = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark array offset
+ int mao = in.readTTFUShort();
+ // read ligature array offset
+ int lao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-ligature positioning mark coverage table offset: " + mco );
+ log.debug(tableTag + " mark-to-ligature positioning ligature coverage table offset: " + lco );
+ log.debug(tableTag + " mark-to-ligature positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-ligature positioning mark array offset: " + mao );
+ log.debug(tableTag + " mark-to-ligature positioning ligature array offset: " + lao );
+ }
+ // read mark coverage table
+ GlyphCoverageTable mct = readCoverageTable ( in, tableTag + " mark-to-ligature positioning mark coverage", subtableOffset + mco );
+ // read ligature coverage table
+ GlyphCoverageTable lct = readCoverageTable ( in, tableTag + " mark-to-ligature positioning ligature coverage", subtableOffset + lco );
+ // read mark anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + mao);
+ // read mark count
+ int nm = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark count: " + nm );
+ }
+ // read mark anchor array, where i:{0...markCount}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm ];
+ for ( int i = 0; i < nm; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + mao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning mark anchor[" + i + "]: " + ma);
+ }
+ }
+ // read ligature anchor matrix
+ // seek to ligature array
+ in.seekSet(subtableOffset + lao);
+ // read ligature count
+ int nl = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature count: " + nl );
+ }
+ // read ligature attach table offsets
+ int[] laoa = new int [ nl ];
+ for ( int i = 0; i < nl; i++ ) {
+ laoa [ i ] = in.readTTFUShort();
+ }
+ // iterate over ligature attach tables, recording maximum component count
+ int mxc = 0;
+ for ( int i = 0; i < nl; i++ ) {
+ int lato = laoa [ i ];
+ in.seekSet ( subtableOffset + lao + lato );
+ // read component count
+ int cc = in.readTTFUShort();
+ if ( cc > mxc ) {
+ mxc = cc;
+ }
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning maximum component count: " + mxc );
+ }
+ // read anchor matrix, where i:{0...ligatureCount - 1}, j:{0...maxComponentCount - 1}, k:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][][] lam = new GlyphPositioningTable.Anchor [ nl ][][];
+ for ( int i = 0; i < nl; i++ ) {
+ int lato = laoa [ i ];
+ // seek to ligature attach table for ligature[i]
+ in.seekSet ( subtableOffset + lao + lato );
+ // read component count
+ int cc = in.readTTFUShort();
+ GlyphPositioningTable.Anchor[][] lcm = new GlyphPositioningTable.Anchor [ cc ] [ nmc ];
+ for ( int j = 0; j < cc; j++ ) {
+ for ( int k = 0; k < nmc; k++ ) {
+ // read ligature anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + lao + lato + ao );
+ } else {
+ a = null;
+ }
+ lcm [ j ] [ k ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-ligature positioning ligature anchor[" + i + "][" + j + "][" + k + "]: " + a);
+ }
+ }
+ }
+ lam [ i ] = lcm;
+ }
+ // store results
+ seMapping = mct;
+ seEntries.add ( lct );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( Integer.valueOf ( mxc ) );
+ seEntries.add ( maa );
+ seEntries.add ( lam );
+ }
+
+ private int readMarkToLigaturePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToLigaturePosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported mark-to-ligature positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readMarkToMarkPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark #1 coverage offset
+ int m1co = in.readTTFUShort();
+ // read mark #2 coverage offset
+ int m2co = in.readTTFUShort();
+ // read mark class count
+ int nmc = in.readTTFUShort();
+ // read mark #1 array offset
+ int m1ao = in.readTTFUShort();
+ // read mark #2 array offset
+ int m2ao = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " mark-to-mark positioning mark #1 coverage table offset: " + m1co );
+ log.debug(tableTag + " mark-to-mark positioning mark #2 coverage table offset: " + m2co );
+ log.debug(tableTag + " mark-to-mark positioning mark class count: " + nmc );
+ log.debug(tableTag + " mark-to-mark positioning mark #1 array offset: " + m1ao );
+ log.debug(tableTag + " mark-to-mark positioning mark #2 array offset: " + m2ao );
+ }
+ // read mark #1 coverage table
+ GlyphCoverageTable mct1 = readCoverageTable ( in, tableTag + " mark-to-mark positioning mark #1 coverage", subtableOffset + m1co );
+ // read mark #2 coverage table
+ GlyphCoverageTable mct2 = readCoverageTable ( in, tableTag + " mark-to-mark positioning mark #2 coverage", subtableOffset + m2co );
+ // read mark #1 anchor array
+ // seek to mark array
+ in.seekSet(subtableOffset + m1ao);
+ // read mark count
+ int nm1 = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 count: " + nm1 );
+ }
+ // read mark anchor array, where i:{0...mark1Count}
+ GlyphPositioningTable.MarkAnchor[] maa = new GlyphPositioningTable.MarkAnchor [ nm1 ];
+ for ( int i = 0; i < nm1; i++ ) {
+ // read mark class
+ int mc = in.readTTFUShort();
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + m1ao + ao );
+ } else {
+ a = null;
+ }
+ GlyphPositioningTable.MarkAnchor ma;
+ if ( a != null ) {
+ ma = new GlyphPositioningTable.MarkAnchor ( mc, a );
+ } else {
+ ma = null;
+ }
+ maa [ i ] = ma;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #1 anchor[" + i + "]: " + ma);
+ }
+ }
+ // read mark #2 anchor matrix
+ // seek to mark #2 array
+ in.seekSet(subtableOffset + m2ao);
+ // read mark #2 count
+ int nm2 = in.readTTFUShort();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 count: " + nm2 );
+ }
+ // read anchor matrix, where i:{0...mark2Count - 1}, j:{0...markClassCount - 1}
+ GlyphPositioningTable.Anchor[][] mam = new GlyphPositioningTable.Anchor [ nm2 ] [ nmc ];
+ for ( int i = 0; i < nm2; i++ ) {
+ for ( int j = 0; j < nmc; j++ ) {
+ // read mark anchor offset
+ int ao = in.readTTFUShort();
+ GlyphPositioningTable.Anchor a;
+ if ( ao > 0 ) {
+ a = readPosAnchor ( in, subtableOffset + m2ao + ao );
+ } else {
+ a = null;
+ }
+ mam [ i ] [ j ] = a;
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark-to-mark positioning mark #2 anchor[" + i + "][" + j + "]: " + a);
+ }
+ }
+ }
+ // store results
+ seMapping = mct1;
+ seEntries.add ( mct2 );
+ seEntries.add ( Integer.valueOf ( nmc ) );
+ seEntries.add ( maa );
+ seEntries.add ( mam );
+ }
+
+ private int readMarkToMarkPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readMarkToMarkPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported mark-to-mark positioning subtable format: " + sf );
+ }
+ return sf;
+ }
+
+ private void readContextualPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " contextual positioning rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.GlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ ng - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.GlyphSequenceRule ( lookups, ng, glyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read class def table offset
+ int cdo = in.readTTFUShort();
+ // read class rule set count
+ int ngc = in.readTTFUShort();
+ // read class rule set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " contextual positioning class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " contextual positioning class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read class definition table
+ GlyphClassTable cdt;
+ if ( cdo > 0 ) {
+ cdt = readClassDefTable ( in, tableTag + " contextual positioning class definition", subtableOffset + cdo );
+ } else {
+ cdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ int ro = roa [ j ];
+ GlyphTable.ClassSequenceRule r;
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read glyph count
+ int ng = in.readTTFUShort();
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read classes
+ int[] classes = new int [ ng - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ClassSequenceRule ( lookups, ng, classes );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( cdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
+ }
+
+ private void readContextualPosTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read glyph (input sequence length) count
+ int ng = in.readTTFUShort();
+ // read positioning lookup count
+ int nl = in.readTTFUShort();
+ // read glyph coverage offsets, one per glyph input sequence length count
+ int[] gcoa = new int [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ gcoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " contextual positioning glyph input sequence length count: " + ng );
+ log.debug(tableTag + " contextual positioning lookup count: " + nl );
+ for ( int i = 0; i < ng; i++ ) {
+ log.debug(tableTag + " contextual positioning coverage table offset[" + i + "]: " + gcoa[i] );
+ }
+ }
+ // read coverage tables
+ GlyphCoverageTable[] gca = new GlyphCoverageTable [ ng ];
+ for ( int i = 0; i < ng; i++ ) {
+ int gco = gcoa [ i ];
+ GlyphCoverageTable gct;
+ if ( gco > 0 ) {
+ gct = readCoverageTable ( in, tableTag + " contextual positioning coverage[" + i + "]", subtableOffset + gcoa[i] );
+ } else {
+ gct = null;
+ }
+ gca [ i ] = gct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.CoverageSequenceRule ( lookups, ng, gca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( gca != null ) && ( gca.length > 0 );
+ seMapping = gca[0];
+ seEntries.add ( rsa );
+ }
+
+ private int readContextualPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read positioning subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readContextualPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readContextualPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readContextualPosTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported contextual positioning subtable format: " + sf );
+ }
return sf;
}
- private int readMarkToBasePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private void readChainedContextualPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read rule set count
+ int nrs = in.readTTFUShort();
+ // read rule set offsets
+ int[] rsoa = new int [ nrs ];
+ for ( int i = 0; i < nrs; i++ ) {
+ rsoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyphs)" );
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual positioning rule set count: " + nrs );
+ for ( int i = 0; i < nrs; i++ ) {
+ log.debug(tableTag + " chained contextual positioning rule set offset[" + i + "]: " + rsoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " chained contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ nrs ];
+ String header = null;
+ for ( int i = 0; i < nrs; i++ ) {
+ GlyphTable.RuleSet rs;
+ int rso = rsoa [ i ];
+ if ( rso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + rso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedGlyphSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + rso + ro );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyphs
+ int[] backtrackGlyphs = new int [ nbg ];
+ for ( int k = 0, nk = backtrackGlyphs.length; k < nk; k++ ) {
+ backtrackGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read glyphs
+ int[] glyphs = new int [ nig - 1 ];
+ for ( int k = 0, nk = glyphs.length; k < nk; k++ ) {
+ glyphs [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read lookahead glyphs
+ int[] lookaheadGlyphs = new int [ nlg ];
+ for ( int k = 0, nk = lookaheadGlyphs.length; k < nk; k++ ) {
+ lookaheadGlyphs [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ChainedGlyphSequenceRule ( lookups, nig, glyphs, backtrackGlyphs, lookaheadGlyphs );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( rsa );
}
- private int readMarkToLigaturePosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private void readChainedContextualPosTableFormat2(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read backtrack class def table offset
+ int bcdo = in.readTTFUShort();
+ // read input class def table offset
+ int icdo = in.readTTFUShort();
+ // read lookahead class def table offset
+ int lcdo = in.readTTFUShort();
+ // read class set count
+ int ngc = in.readTTFUShort();
+ // read class set offsets
+ int[] csoa = new int [ ngc ];
+ for ( int i = 0; i < ngc; i++ ) {
+ csoa [ i ] = in.readTTFUShort();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph classes)" );
+ log.debug(tableTag + " chained contextual positioning coverage table offset: " + co );
+ log.debug(tableTag + " chained contextual positioning class set count: " + ngc );
+ for ( int i = 0; i < ngc; i++ ) {
+ log.debug(tableTag + " chained contextual positioning class set offset[" + i + "]: " + csoa[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct;
+ if ( co > 0 ) {
+ ct = readCoverageTable ( in, tableTag + " chained contextual positioning coverage", subtableOffset + co );
+ } else {
+ ct = null;
+ }
+ // read backtrack class definition table
+ GlyphClassTable bcdt;
+ if ( bcdo > 0 ) {
+ bcdt = readClassDefTable ( in, tableTag + " contextual positioning backtrack class definition", subtableOffset + bcdo );
+ } else {
+ bcdt = null;
+ }
+ // read input class definition table
+ GlyphClassTable icdt;
+ if ( icdo > 0 ) {
+ icdt = readClassDefTable ( in, tableTag + " contextual positioning input class definition", subtableOffset + icdo );
+ } else {
+ icdt = null;
+ }
+ // read lookahead class definition table
+ GlyphClassTable lcdt;
+ if ( lcdo > 0 ) {
+ lcdt = readClassDefTable ( in, tableTag + " contextual positioning lookahead class definition", subtableOffset + lcdo );
+ } else {
+ lcdt = null;
+ }
+ // read rule sets
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet [ ngc ];
+ String header = null;
+ for ( int i = 0; i < ngc; i++ ) {
+ int cso = csoa [ i ];
+ GlyphTable.RuleSet rs;
+ if ( cso > 0 ) {
+ // seek to rule set [ i ]
+ in.seekSet ( subtableOffset + cso );
+ // read rule count
+ int nr = in.readTTFUShort();
+ // read rule offsets
+ int[] roa = new int [ nr ];
+ GlyphTable.Rule[] ra = new GlyphTable.Rule [ nr ];
+ for ( int j = 0; j < nr; j++ ) {
+ roa [ j ] = in.readTTFUShort();
+ }
+ // read glyph sequence rules
+ for ( int j = 0; j < nr; j++ ) {
+ GlyphTable.ChainedClassSequenceRule r;
+ int ro = roa [ j ];
+ if ( ro > 0 ) {
+ // seek to rule [ j ]
+ in.seekSet ( subtableOffset + cso + ro );
+ // read backtrack glyph class count
+ int nbc = in.readTTFUShort();
+ // read backtrack glyph classes
+ int[] backtrackClasses = new int [ nbc ];
+ for ( int k = 0, nk = backtrackClasses.length; k < nk; k++ ) {
+ backtrackClasses [ k ] = in.readTTFUShort();
+ }
+ // read input glyph class count
+ int nic = in.readTTFUShort();
+ // read input glyph classes
+ int[] classes = new int [ nic - 1 ];
+ for ( int k = 0, nk = classes.length; k < nk; k++ ) {
+ classes [ k ] = in.readTTFUShort();
+ }
+ // read lookahead glyph class count
+ int nlc = in.readTTFUShort();
+ // read lookahead glyph classes
+ int[] lookaheadClasses = new int [ nlc ];
+ for ( int k = 0, nk = lookaheadClasses.length; k < nk; k++ ) {
+ lookaheadClasses [ k ] = in.readTTFUShort();
+ }
+ // read rule lookup count
+ int nl = in.readTTFUShort();
+ // read rule lookups
+ if (log.isDebugEnabled()) {
+ header = tableTag + " contextual positioning lookups @rule[" + i + "][" + j + "]: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ r = new GlyphTable.ChainedClassSequenceRule ( lookups, nic, classes, backtrackClasses, lookaheadClasses );
+ } else {
+ r = null;
+ }
+ ra [ j ] = r;
+ }
+ rs = new GlyphTable.HomogeneousRuleSet ( ra );
+ } else {
+ rs = null;
+ }
+ rsa [ i ] = rs;
+ }
+ // store results
+ seMapping = ct;
+ seEntries.add ( icdt );
+ seEntries.add ( bcdt );
+ seEntries.add ( lcdt );
+ seEntries.add ( Integer.valueOf ( ngc ) );
+ seEntries.add ( rsa );
}
- private int readMarkToMarkPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private void readChainedContextualPosTableFormat3(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read backtrack glyph count
+ int nbg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] bgcoa = new int [ nbg ];
+ for ( int i = 0; i < nbg; i++ ) {
+ bgcoa [ i ] = in.readTTFUShort();
+ }
+ // read input glyph count
+ int nig = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] igcoa = new int [ nig ];
+ for ( int i = 0; i < nig; i++ ) {
+ igcoa [ i ] = in.readTTFUShort();
+ }
+ // read lookahead glyph count
+ int nlg = in.readTTFUShort();
+ // read backtrack glyph coverage offsets
+ int[] lgcoa = new int [ nlg ];
+ for ( int i = 0; i < nlg; i++ ) {
+ lgcoa [ i ] = in.readTTFUShort();
+ }
+ // read positioning lookup count
+ int nl = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " chained contextual positioning subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " chained contextual positioning backtrack glyph count: " + nbg );
+ for ( int i = 0; i < nbg; i++ ) {
+ log.debug(tableTag + " chained contextual positioning backtrack coverage table offset[" + i + "]: " + bgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning input glyph count: " + nig );
+ for ( int i = 0; i < nig; i++ ) {
+ log.debug(tableTag + " chained contextual positioning input coverage table offset[" + i + "]: " + igcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning lookahead glyph count: " + nlg );
+ for ( int i = 0; i < nlg; i++ ) {
+ log.debug(tableTag + " chained contextual positioning lookahead coverage table offset[" + i + "]: " + lgcoa[i] );
+ }
+ log.debug(tableTag + " chained contextual positioning lookup count: " + nl );
+ }
+ // read backtrack coverage tables
+ GlyphCoverageTable[] bgca = new GlyphCoverageTable[nbg];
+ for ( int i = 0; i < nbg; i++ ) {
+ int bgco = bgcoa [ i ];
+ GlyphCoverageTable bgct;
+ if ( bgco > 0 ) {
+ bgct = readCoverageTable ( in, tableTag + " chained contextual positioning backtrack coverage[" + i + "]", subtableOffset + bgco );
+ } else {
+ bgct = null;
+ }
+ bgca[i] = bgct;
+ }
+ // read input coverage tables
+ GlyphCoverageTable[] igca = new GlyphCoverageTable[nig];
+ for ( int i = 0; i < nig; i++ ) {
+ int igco = igcoa [ i ];
+ GlyphCoverageTable igct;
+ if ( igco > 0 ) {
+ igct = readCoverageTable ( in, tableTag + " chained contextual positioning input coverage[" + i + "]", subtableOffset + igco );
+ } else {
+ igct = null;
+ }
+ igca[i] = igct;
+ }
+ // read lookahead coverage tables
+ GlyphCoverageTable[] lgca = new GlyphCoverageTable[nlg];
+ for ( int i = 0; i < nlg; i++ ) {
+ int lgco = lgcoa [ i ];
+ GlyphCoverageTable lgct;
+ if ( lgco > 0 ) {
+ lgct = readCoverageTable ( in, tableTag + " chained contextual positioning lookahead coverage[" + i + "]", subtableOffset + lgco );
+ } else {
+ lgct = null;
+ }
+ lgca[i] = lgct;
+ }
+ // read rule lookups
+ String header = null;
+ if (log.isDebugEnabled()) {
+ header = tableTag + " chained contextual positioning lookups: ";
+ }
+ GlyphTable.RuleLookup[] lookups = readRuleLookups ( in, nl, header );
+ // construct rule, rule set, and rule set array
+ GlyphTable.Rule r = new GlyphTable.ChainedCoverageSequenceRule ( lookups, nig, igca, bgca, lgca );
+ GlyphTable.RuleSet rs = new GlyphTable.HomogeneousRuleSet ( new GlyphTable.Rule[] {r} );
+ GlyphTable.RuleSet[] rsa = new GlyphTable.RuleSet[] {rs};
+ // store results
+ assert ( igca != null ) && ( igca.length > 0 );
+ seMapping = igca[0];
+ seEntries.add ( rsa );
}
- private int readContextPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private int readChainedContextualPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read positioning subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readChainedContextualPosTableFormat1 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 2 ) {
+ readChainedContextualPosTableFormat2 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else if ( sf == 3 ) {
+ readChainedContextualPosTableFormat3 ( in, lookupType, lookupFlags, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported chained contextual positioning subtable format: " + sf );
+ }
return sf;
}
- private int readChainedContextPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private void readExtensionPosTableFormat1(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset, int subtableFormat) throws IOException {
+ String tableTag = "GPOS";
in.seekSet(subtableOffset);
- // read substitution format
- int sf = in.readTTFUShort();
- // [TBD] - implement me
- return sf;
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read extension lookup type
+ int lt = in.readTTFUShort();
+ // read extension offset
+ long eo = in.readTTFULong();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " extension positioning subtable format: " + subtableFormat );
+ log.debug(tableTag + " extension positioning lookup type: " + lt );
+ log.debug(tableTag + " extension positioning lookup table offset: " + eo );
+ }
+ // read referenced subtable from extended offset
+ readGPOSSubtable ( in, lt, lookupFlags, lookupSequence, subtableSequence, subtableOffset + eo );
}
- private int readExtensionPosTable(FontFileReader in, int lookupType, int lookupFlags, long subtableOffset) throws IOException {
+ private int readExtensionPosTable(FontFileReader in, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, long subtableOffset) throws IOException {
in.seekSet(subtableOffset);
- // read substitution format
+ // read positioning subtable format
int sf = in.readTTFUShort();
- // [TBD] - implement me
+ if ( sf == 1 ) {
+ readExtensionPosTableFormat1 ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported extension positioning subtable format: " + sf );
+ }
return sf;
}
case GPOSLookupType.MARK_TO_MARK:
subtableFormat = readMarkToMarkPosTable ( in, lookupType, lookupFlags, subtableOffset );
break;
- case GPOSLookupType.CONTEXT:
- subtableFormat = readContextPosTable ( in, lookupType, lookupFlags, subtableOffset );
+ case GPOSLookupType.CONTEXTUAL:
+ subtableFormat = readContextualPosTable ( in, lookupType, lookupFlags, subtableOffset );
break;
- case GPOSLookupType.CHAINED_CONTEXT:
- subtableFormat = readChainedContextPosTable ( in, lookupType, lookupFlags, subtableOffset );
+ case GPOSLookupType.CHAINED_CONTEXTUAL:
+ subtableFormat = readChainedContextualPosTable ( in, lookupType, lookupFlags, subtableOffset );
break;
case GPOSLookupType.EXTENSION:
- subtableFormat = readExtensionPosTable ( in, lookupType, lookupFlags, subtableOffset );
+ subtableFormat = readExtensionPosTable ( in, lookupType, lookupFlags, lookupSequence, subtableSequence, subtableOffset );
break;
default:
break;
}
}
+ private void readGDEFClassDefTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initSESubState();
+ in.seekSet(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable ( in, tableTag + " glyph class definition table", subtableOffset );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.GLYPH_CLASS, 0, lookupSequence, 0, 1 );
+ resetSESubState();
+ }
+
+ private void readGDEFAttachmentTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initSESubState();
+ in.seekSet(subtableOffset);
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " attachment point coverage table offset: " + co );
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " attachment point coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.ATTACHMENT_POINT, 0, lookupSequence, 0, 1 );
+ resetSESubState();
+ }
+
+ private void readGDEFLigatureCaretTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initSESubState();
+ in.seekSet(subtableOffset);
+ // read coverage offset
+ int co = in.readTTFUShort();
+ // read ligature glyph count
+ int nl = in.readTTFUShort();
+ // read ligature glyph table offsets
+ int[] lgto = new int [ nl ];
+ for ( int i = 0; i < nl; i++ ) {
+ lgto [ i ] = in.readTTFUShort();
+ }
+
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " ligature caret coverage table offset: " + co );
+ log.debug(tableTag + " ligature caret ligature glyph count: " + nl );
+ for ( int i = 0; i < nl; i++ ) {
+ log.debug(tableTag + " ligature glyph table offset[" + i + "]: " + lgto[i] );
+ }
+ }
+ // read coverage table
+ GlyphCoverageTable ct = readCoverageTable ( in, tableTag + " ligature caret coverage", subtableOffset + co );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.LIGATURE_CARET, 0, lookupSequence, 0, 1 );
+ resetSESubState();
+ }
+
+ private void readGDEFMarkAttachmentTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ initSESubState();
+ in.seekSet(subtableOffset);
+ // subtable is a bare class definition table
+ GlyphClassTable ct = readClassDefTable ( in, tableTag + " glyph class definition table", subtableOffset );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
+ resetSESubState();
+ }
+
+ private void readGDEFMarkGlyphsTableFormat1(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset, int subtableFormat) throws IOException {
+ initSESubState();
+ in.seekSet(subtableOffset);
+ // skip over format (already known)
+ in.skip ( 2 );
+ // read mark set class count
+ int nmc = in.readTTFUShort();
+ long[] mso = new long [ nmc ];
+ // read mark set coverage offsets
+ for ( int i = 0; i < nmc; i++ ) {
+ mso [ i ] = in.readTTFULong();
+ }
+ // dump info if debugging
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " mark set subtable format: " + subtableFormat + " (glyph sets)" );
+ log.debug(tableTag + " mark set class count: " + nmc );
+ for ( int i = 0; i < nmc; i++ ) {
+ log.debug(tableTag + " mark set coverage table offset[" + i + "]: " + mso[i] );
+ }
+ }
+ // read mark set coverage tables, one per class
+ GlyphCoverageTable[] msca = new GlyphCoverageTable[nmc];
+ for ( int i = 0; i < nmc; i++ ) {
+ msca[i] = readCoverageTable ( in, tableTag + " mark set coverage[" + i + "]", subtableOffset + mso[i] );
+ }
+ // create combined class table from per-class coverage tables
+ GlyphClassTable ct = GlyphClassTable.createClassTable ( Arrays.asList ( msca ) );
+ // store results
+ seMapping = ct;
+ // extract subtable
+ extractSESubState ( GlyphTable.GLYPH_TABLE_TYPE_DEFINITION, GDEFLookupType.MARK_ATTACHMENT, 0, lookupSequence, 0, 1 );
+ resetSESubState();
+ }
+
+ private void readGDEFMarkGlyphsTable(FontFileReader in, String tableTag, int lookupSequence, long subtableOffset) throws IOException {
+ in.seekSet(subtableOffset);
+ // read mark set subtable format
+ int sf = in.readTTFUShort();
+ if ( sf == 1 ) {
+ readGDEFMarkGlyphsTableFormat1 ( in, tableTag, lookupSequence, subtableOffset, sf );
+ } else {
+ throw new UnsupportedOperationException ( "unsupported mark glyph sets subtable format: " + sf );
+ }
+ }
+
+ /**
+ * Read the GDEF table.
+ * @param in FontFileReader to read from
+ * @throws IOException In case of a I/O problem
+ */
+ private void readGDEF(FontFileReader in) throws IOException {
+ String tableTag = "GDEF";
+ // Initialize temporary state
+ initSEState();
+ // Read glyph definition (GDEF) table
+ TTFDirTabEntry dirTab = (TTFDirTabEntry)dirTabs.get(tableTag);
+ if ( gdef != null ) {
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + ": ignoring duplicate table");
+ }
+ } else if (dirTab != null) {
+ seekTab(in, tableTag, 0);
+ long version = in.readTTFULong();
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " version: " + ( version / 65536 ) + "." + ( version % 65536 ));
+ }
+ // glyph class definition table offset (may be null)
+ int cdo = in.readTTFUShort();
+ // attach point list offset (may be null)
+ int apo = in.readTTFUShort();
+ // ligature caret list offset (may be null)
+ int lco = in.readTTFUShort();
+ // mark attach class definition table offset (may be null)
+ int mao = in.readTTFUShort();
+ // mark glyph sets definition table offset (may be null)
+ int mgo;
+ if ( version >= 0x00010002 ) {
+ mgo = in.readTTFUShort();
+ } else {
+ mgo = 0;
+ }
+ if (log.isDebugEnabled()) {
+ log.debug(tableTag + " glyph class definition table offset: " + cdo );
+ log.debug(tableTag + " attachment point list offset: " + apo );
+ log.debug(tableTag + " ligature caret list offset: " + lco );
+ log.debug(tableTag + " mark attachment class definition table offset: " + mao );
+ log.debug(tableTag + " mark glyph set definitions table offset: " + mgo );
+ }
+ // initialize subtable sequence number
+ int seqno = 0;
+ // obtain offset to start of gdef table
+ long to = dirTab.getOffset();
+ // (optionally) read glyph class definition subtable
+ if ( cdo != 0 ) {
+ readGDEFClassDefTable ( in, tableTag, seqno++, to + cdo );
+ }
+ // (optionally) read glyph attachment point subtable
+ if ( apo != 0 ) {
+ readGDEFAttachmentTable ( in, tableTag, seqno++, to + apo );
+ }
+ // (optionally) read ligature caret subtable
+ if ( lco != 0 ) {
+ readGDEFLigatureCaretTable ( in, tableTag, seqno++, to + lco );
+ }
+ // (optionally) read mark attachment class subtable
+ if ( mao != 0 ) {
+ readGDEFMarkAttachmentTable ( in, tableTag, seqno++, to + mao );
+ }
+ // (optionally) read mark glyph sets subtable
+ if ( mgo != 0 ) {
+ readGDEFMarkGlyphsTable ( in, tableTag, seqno++, to + mgo );
+ }
+ GlyphDefinitionTable gdef;
+ if ( ( gdef = constructGDEF() ) != null ) {
+ this.gdef = gdef;
+ }
+ }
+ }
+
/**
* Read the GSUB table.
* @param in FontFileReader to read from
}
}
+ /**
+ * Construct the (internal representation of the) GDEF table based on previously
+ * parsed state.
+ * @returns glyph definition table or null if insufficient or invalid state
+ */
+ private GlyphDefinitionTable constructGDEF() {
+ GlyphDefinitionTable gdef = null;
+ List subtables;
+ if ( ( subtables = constructGDEFSubtables() ) != null ) {
+ if ( subtables.size() > 0 ) {
+ gdef = new GlyphDefinitionTable ( subtables );
+ }
+ }
+ resetSEState();
+ return gdef;
+ }
+
/**
* Construct the (internal representation of the) GSUB table based on previously
* parsed state.
List subtables;
if ( ( subtables = constructGSUBSubtables() ) != null ) {
if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
- gsub = new GlyphSubstitutionTable ( lookups, subtables );
+ gsub = new GlyphSubstitutionTable ( gdef, lookups, subtables );
}
}
}
List subtables;
if ( ( subtables = constructGPOSSubtables() ) != null ) {
if ( ( lookups.size() > 0 ) && ( subtables.size() > 0 ) ) {
- gpos = new GlyphPositioningTable ( lookups, subtables );
+ gpos = new GlyphPositioningTable ( gdef, lookups, subtables );
}
}
}
return lookups;
}
+ private List constructGDEFSubtables() {
+ List/*<GlyphDefinitionSubtable>*/ subtables = new java.util.ArrayList();
+ if ( seSubtables != null ) {
+ for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+ Object[] stp = (Object[]) it.next();
+ GlyphSubtable st;
+ if ( ( st = constructGDEFSubtable ( stp ) ) != null ) {
+ subtables.add ( st );
+ }
+ }
+ }
+ return subtables;
+ }
+
+ private GlyphSubtable constructGDEFSubtable ( Object[] stp ) {
+ GlyphSubtable st = null;
+ assert ( stp != null ) && ( stp.length == 8 );
+ Integer tt = (Integer) stp[0];
+ Integer lt = (Integer) stp[1];
+ Integer ln = (Integer) stp[2];
+ Integer lf = (Integer) stp[3];
+ Integer sn = (Integer) stp[4];
+ Integer sf = (Integer) stp[5];
+ GlyphMappingTable mapping = (GlyphMappingTable) stp[6];
+ List entries = (List) stp[7];
+ if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) {
+ int type = GDEFLookupType.getSubtableType ( lt.intValue() );
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
+ int flags = lf.intValue();
+ int format = sf.intValue();
+ st = GlyphDefinitionTable.createSubtable ( type, lid, sequence, flags, format, mapping, entries );
+ }
+ return st;
+ }
+
private List constructGSUBSubtables() {
List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
if ( seSubtables != null ) {
Integer lt = (Integer) stp[1];
Integer ln = (Integer) stp[2];
Integer lf = (Integer) stp[3];
- // Integer sn = (Integer) stp[4]; // not used yet
+ Integer sn = (Integer) stp[4];
Integer sf = (Integer) stp[5];
- List coverage = (List) stp[6];
+ GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
List entries = (List) stp[7];
if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION ) {
int type = GSUBLookupType.getSubtableType ( lt.intValue() );
- String id = "lu" + ln.intValue();
- int sequence = ln.intValue();
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
int flags = lf.intValue();
int format = sf.intValue();
- st = GlyphSubstitutionTable.createSubtable ( type, id, sequence, flags, format, coverage, entries );
+ st = GlyphSubstitutionTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
}
return st;
}
private List constructGPOSSubtables() {
List/*<GlyphSubtable>*/ subtables = new java.util.ArrayList();
+ if ( seSubtables != null ) {
+ for ( Iterator it = seSubtables.iterator(); it.hasNext();) {
+ Object[] stp = (Object[]) it.next();
+ GlyphSubtable st;
+ if ( ( st = constructGPOSSubtable ( stp ) ) != null ) {
+ subtables.add ( st );
+ }
+ }
+ }
return subtables;
}
+ private GlyphSubtable constructGPOSSubtable ( Object[] stp ) {
+ GlyphSubtable st = null;
+ assert ( stp != null ) && ( stp.length == 8 );
+ Integer tt = (Integer) stp[0];
+ Integer lt = (Integer) stp[1];
+ Integer ln = (Integer) stp[2];
+ Integer lf = (Integer) stp[3];
+ Integer sn = (Integer) stp[4];
+ Integer sf = (Integer) stp[5];
+ GlyphCoverageTable coverage = (GlyphCoverageTable) stp[6];
+ List entries = (List) stp[7];
+ if ( tt.intValue() == GlyphTable.GLYPH_TABLE_TYPE_POSITIONING ) {
+ int type = GSUBLookupType.getSubtableType ( lt.intValue() );
+ String lid = "lu" + ln.intValue();
+ int sequence = sn.intValue();
+ int flags = lf.intValue();
+ int format = sf.intValue();
+ st = GlyphPositioningTable.createSubtable ( type, lid, sequence, flags, format, coverage, entries );
+ }
+ return st;
+ }
+
private void initSEState() {
seScripts = new java.util.LinkedHashMap();
seLanguages = new java.util.LinkedHashMap();
}
private void initSESubState() {
- seCoverage = new java.util.ArrayList();
+ seMapping = null;
seEntries = new java.util.ArrayList();
}
private void extractSESubState ( int tableType, int lookupType, int lookupFlags, int lookupSequence, int subtableSequence, int subtableFormat ) {
- if ( ( seCoverage != null ) && ( seCoverage.size() > 0 ) ) {
- if ( ( seEntries != null ) && ( seEntries.size() > 0 ) ) {
+ if ( seEntries != null ) {
+ if ( ( tableType == GlyphTable.GLYPH_TABLE_TYPE_DEFINITION ) || ( seEntries.size() > 0 ) ) {
if ( seSubtables != null ) {
Integer tt = Integer.valueOf ( tableType );
Integer lt = Integer.valueOf ( lookupType );
Integer lf = Integer.valueOf ( lookupFlags );
Integer sn = Integer.valueOf ( subtableSequence );
Integer sf = Integer.valueOf ( subtableFormat );
- seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seCoverage, seEntries } );
+ seSubtables.add ( new Object[] { tt, lt, ln, lf, sn, sf, seMapping, seEntries } );
}
}
}
}
private void resetSESubState() {
- seCoverage = null;
+ seMapping = null;
seEntries = null;
}
*
* @param glyphIndex
* @return unicode code point
- * @throws IOException if glyphIndex not found
*/
- private Integer glyphToUnicode(int glyphIndex) throws IOException {
+ private Integer glyphToUnicode(int glyphIndex) {
return (Integer) glyphToUnicodeMap.get(new Integer(glyphIndex));
}
*
* @param unicodeIndex unicode code point
* @return glyph index
- * @throws IOException if unicodeIndex not found
*/
private Integer unicodeToGlyph(int unicodeIndex) throws IOException {
final Integer result
private void copyAdvanced ( TTFFile ttf ) {
if ( returnFont instanceof MultiByteFont ) {
MultiByteFont mbf = (MultiByteFont) returnFont;
+ mbf.setGDEF ( ttf.getGDEF() );
mbf.setGSUB ( ttf.getGSUB() );
mbf.setGPOS ( ttf.getGPOS() );
}
}
} else {
int[] levels = ( level >= 0 ) ? new int[] {level} : null;
- text.addWord(String.valueOf(ch), 0, null, levels, blockProgressionOffset);
+ text.addWord(String.valueOf(ch), 0, null, levels, null, blockProgressionOffset);
}
TraitSetter.setProducerID(text, node.getId());
TraitSetter.addTextDecoration(text, node.getTextDecoration());
import org.apache.fop.fo.properties.StructurePointerPropertySet;
import org.apache.fop.fonts.Font;
import org.apache.fop.fonts.FontSelector;
+import org.apache.fop.fonts.GlyphPositioningTable;
import org.apache.fop.layoutmgr.InlineKnuthSequence;
import org.apache.fop.layoutmgr.KnuthBox;
import org.apache.fop.layoutmgr.KnuthElement;
private boolean breakOppAfter;
private final Font font;
private final int level;
+ private final int[][] gposAdjustments;
- AreaInfo( // CSOK: ParameterNumber
- int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
- MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
- Font font, int level) {
+ AreaInfo // CSOK: ParameterNumber
+ (int startIndex, int breakIndex, int wordSpaceCount, int letterSpaceCount,
+ MinOptMax areaIPD, boolean isHyphenated, boolean isSpace, boolean breakOppAfter,
+ Font font, int level, int[][] gposAdjustments) {
assert startIndex <= breakIndex;
this.startIndex = startIndex;
this.breakIndex = breakIndex;
this.breakOppAfter = breakOppAfter;
this.font = font;
this.level = level;
+ this.gposAdjustments = gposAdjustments;
}
private int getCharLength() {
* be used to influence the start position of the first letter. The entry i+1 defines the
* cursor advancement after the character i. A null entry means no special advancement.
*/
- private final MinOptMax[] letterAdjustArray; //size = textArray.length + 1
+ private final MinOptMax[] letterSpaceAdjustArray; //size = textArray.length + 1
/** Font used for the space between words. */
private Font spaceFont = null;
*/
public TextLayoutManager(FOText node) {
foText = node;
- letterAdjustArray = new MinOptMax[node.length() + 1];
+ letterSpaceAdjustArray = new MinOptMax[node.length() + 1];
areaInfos = new ArrayList();
}
}
for (int i = areaInfo.startIndex; i < areaInfo.breakIndex; i++) {
- MinOptMax letterAdjustment = letterAdjustArray[i + 1];
- if (letterAdjustment != null && letterAdjustment.isElastic()) {
+ MinOptMax letterSpaceAdjustment = letterSpaceAdjustArray[i + 1];
+ if (letterSpaceAdjustment != null && letterSpaceAdjustment.isElastic()) {
letterSpaceCount++;
}
}
private int blockProgressionDimension;
private AreaInfo areaInfo;
private StringBuffer wordChars;
- private int[] letterAdjust;
- private int letterAdjustIndex;
+ private int[] letterSpaceAdjust;
+ private int letterSpaceAdjustIndex;
private int[] wordLevels;
private int wordLevelsCount;
private int wordIPD;
+ private int[][] gposAdjustments;
+ private int gposAdjustmentsIndex;
private TextArea textArea;
private void addWord(int startIndex, int endIndex, int charLength) {
int blockProgressionOffset = 0;
+ boolean gposAdjusted = false;
if (isHyphenated(endIndex)) {
charLength++;
}
AreaInfo wordAreaInfo = getAreaInfo(i);
addWordChars(wordAreaInfo);
addLetterAdjust(wordAreaInfo);
+ if ( addGlyphPositionAdjustments(wordAreaInfo) ) {
+ gposAdjusted = true;
+ }
}
if (isHyphenated(endIndex)) {
addHyphenationChar();
}
- textArea.addWord(wordChars.toString(), wordIPD, letterAdjust, wordLevels,
- blockProgressionOffset);
+ if ( !gposAdjusted ) {
+ gposAdjustments = null;
+ }
+ textArea.addWord(wordChars.toString(), wordIPD, letterSpaceAdjust, wordLevels,
+ gposAdjustments, blockProgressionOffset);
}
private void initWord(int charLength) {
wordChars = new StringBuffer(charLength);
- letterAdjust = new int[charLength];
- letterAdjustIndex = 0;
+ letterSpaceAdjust = new int[charLength];
+ letterSpaceAdjustIndex = 0;
wordLevels = new int[charLength];
wordLevelsCount = 0;
+ gposAdjustments = new int[charLength][4];
+ gposAdjustmentsIndex = 0;
wordIPD = 0;
}
private void addLetterAdjust(AreaInfo wordAreaInfo) {
int letterSpaceCount = wordAreaInfo.letterSpaceCount;
for (int i = wordAreaInfo.startIndex; i < wordAreaInfo.breakIndex; i++) {
- if (letterAdjustIndex > 0) {
- MinOptMax adj = letterAdjustArray[i];
- letterAdjust[letterAdjustIndex] = adj == null ? 0 : adj.getOpt();
+ if (letterSpaceAdjustIndex > 0) {
+ MinOptMax adj = letterSpaceAdjustArray[i];
+ letterSpaceAdjust[letterSpaceAdjustIndex] = adj == null ? 0 : adj.getOpt();
}
if (letterSpaceCount > 0) {
- letterAdjust[letterAdjustIndex] += textArea.getTextLetterSpaceAdjust();
+ letterSpaceAdjust[letterSpaceAdjustIndex]
+ += textArea.getTextLetterSpaceAdjust();
letterSpaceCount--;
}
- letterAdjustIndex++;
+ letterSpaceAdjustIndex++;
+ }
+ }
+
+ private boolean addGlyphPositionAdjustments(AreaInfo wordAreaInfo) {
+ boolean adjusted = false;
+ int[][] gpa = wordAreaInfo.gposAdjustments;
+ if ( gpa != null ) {
+ // ensure that gposAdjustments is of sufficient length
+ int need = gposAdjustmentsIndex + gpa.length;
+ if ( need > gposAdjustments.length ) {
+ int[][] gposAdjustmentsNew = new int [ need ][];
+ System.arraycopy ( gposAdjustments, 0,
+ gposAdjustmentsNew, 0, gposAdjustments.length );
+ for ( int i = gposAdjustments.length; i < need; i++ ) {
+ gposAdjustmentsNew [ i ] = new int[4];
+ }
+ gposAdjustments = gposAdjustmentsNew;
+ }
+ // add gpos adjustments from word area info, incrementing gposAdjustmentsIndex
+ for ( int i = gposAdjustmentsIndex,
+ n = gposAdjustmentsIndex + gpa.length, j = 0; i < n; i++ ) {
+ int[] wpa1 = gposAdjustments [ i ];
+ int[] wpa2 = gpa [ j++ ];
+ for ( int k = 0; k < 4; k++ ) {
+ int a = wpa2 [ k ];
+ if ( a != 0 ) {
+ wpa1 [ k ] += a; adjusted = true;
+ }
+ }
+ gposAdjustmentsIndex++;
+ }
}
+ return adjusted;
}
/**
}
private void addToLetterAdjust(int index, int width) {
- if (letterAdjustArray[index] == null) {
- letterAdjustArray[index] = MinOptMax.getInstance(width);
+ if (letterSpaceAdjustArray[index] == null) {
+ letterSpaceAdjustArray[index] = MinOptMax.getInstance(width);
} else {
- letterAdjustArray[index] = letterAdjustArray[index].plus(width);
+ letterSpaceAdjustArray[index] = letterSpaceAdjustArray[index].plus(width);
}
}
// preserved space or non-breaking space:
// create the AreaInfo object
areaInfo = new AreaInfo(nextStart, nextStart + 1, 1, 0, wordSpaceIPD, false, true,
- breakOpportunity, spaceFont, level);
+ breakOpportunity, spaceFont, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isFixedWidthSpace(ch) || CharUtilities.isZeroWidthSpace(ch)) {
// create the AreaInfo object
Font font = FontSelector.selectFontForCharacterInText(ch, foText, this);
MinOptMax ipd = MinOptMax.getInstance(font.getCharWidth(ch));
areaInfo = new AreaInfo(nextStart, nextStart + 1, 0, 0, ipd, false, true,
- breakOpportunity, font, level);
+ breakOpportunity, font, level, null);
thisStart = nextStart + 1;
} else if (CharUtilities.isExplicitBreak(ch)) {
//mandatory break-character: only advance index
AreaInfo areaInfo = new AreaInfo
( thisStart, nextStart, nextStart - thisStart, 0,
wordSpaceIPD.mult(nextStart - thisStart),
- false, true, breakOpportunity, spaceFont, -1 );
+ false, true, breakOpportunity, spaceFont, -1, null );
addAreaInfo(areaInfo);
// perform mapping (of chars to glyphs ... to glyphs ... to chars)
CharSequence mcs = font.performSubstitution ( ics, script, language );
+ // memoize mapping
foText.addMapping ( s, e, mcs );
+ // compute glyph position adjustment on (substituted) characters
+ int[][] gpa;
+ if ( font.performsPositioning() ) {
+ gpa = font.performPositioning ( mcs, script, language );
+ } else {
+ gpa = null;
+ }
+
MinOptMax ipd = MinOptMax.ZERO;
for ( int i = 0, n = mcs.length(); i < n; i++ ) {
char c = mcs.charAt ( i );
int w = font.getCharWidth ( c );
+ if ( gpa != null ) {
+ w += gpa [ i ] [ GlyphPositioningTable.Value.IDX_X_ADVANCE ];
+ }
ipd = ipd.plus ( w );
}
- // [TBD] - handle kerning
+ // [TBD] - handle kerning - note that standard kerning would only apply in
+ // the off-chance that a font supports substitution, but does not support
+ // positioning and yet has kerning data
+ // if ( ! font.performsPositioning() ) {
+ // // do standard kerning
+ // }
+
// [TBD] - handle letter spacing
return new AreaInfo
- ( s, e, 0, nLS, ipd, endsWithHyphen, false, breakOpportunityChar != 0, font, level );
+ ( s, e, 0, nLS, ipd, endsWithHyphen, false,
+ breakOpportunityChar != 0, font, level, gpa );
}
private AreaInfo processWordNoMapping(int lastIndex, final Font font, AreaInfo prevAreaInfo,
// create and return the AreaInfo object
return new AreaInfo(thisStart, lastIndex, 0,
- iLetterSpaces, wordIPD,
- endsWithHyphen,
- false, breakOpportunityChar != 0, font, level);
+ iLetterSpaces, wordIPD,
+ endsWithHyphen,
+ false, breakOpportunityChar != 0, font, level, null);
}
private AreaInfo processWord(final int alignment, final KnuthSequence sequence,
newIPD = newIPD.plus(font.getCharWidth(ch));
//if (i > startIndex) {
if (i < stopIndex) {
- MinOptMax letterAdjust = letterAdjustArray[i + 1];
+ MinOptMax letterSpaceAdjust = letterSpaceAdjustArray[i + 1];
if (i == stopIndex - 1 && hyphenFollows) {
//the letter adjust here needs to be handled further down during
//element generation because it depends on hyph/no-hyph condition
- letterAdjust = null;
+ letterSpaceAdjust = null;
}
- if (letterAdjust != null) {
- newIPD = newIPD.plus(letterAdjust);
+ if (letterSpaceAdjust != null) {
+ newIPD = newIPD.plus(letterSpaceAdjust);
}
}
}
if (!(nothingChanged && stopIndex == areaInfo.breakIndex && !hyphenFollows)) {
// the new AreaInfo object is not equal to the old one
- changeList.add(new PendingChange(new AreaInfo(startIndex, stopIndex, 0,
- letterSpaceCount, newIPD, hyphenFollows, false, false, font, -1),
+ changeList.add
+ ( new PendingChange
+ ( new AreaInfo(startIndex, stopIndex, 0,
+ letterSpaceCount, newIPD, hyphenFollows,
+ false, false, font, -1, null),
((LeafPosition) pos).getLeafPos()));
nothingChanged = false;
}
MinOptMax widthIfNoBreakOccurs = null;
if (areaInfo.breakIndex < foText.length()) {
//Add in kerning in no-break condition
- widthIfNoBreakOccurs = letterAdjustArray[areaInfo.breakIndex];
+ widthIfNoBreakOccurs = letterSpaceAdjustArray[areaInfo.breakIndex];
}
//if (areaInfo.breakIndex)
sb.append(PDFNumber.doubleOut(lt[5], DEC));
}
- private void writeChar(char ch, StringBuffer sb) {
- if (!useMultiByte) {
+ private static void writeChar(char ch, StringBuffer sb, boolean multibyte) {
+ if (!multibyte) {
if (ch < 32 || ch > 127) {
sb.append("\\").append(Integer.toOctalString((int)ch));
} else {
}
}
+ private void writeChar(char ch, StringBuffer sb) {
+ writeChar ( ch, sb, useMultiByte );
+ }
+
private void checkInTextObject() {
if (!inTextObject) {
throw new IllegalStateException("Not in text object");
bufTJ = new StringBuffer();
}
if (bufTJ.length() == 0) {
- bufTJ.append("[").append(startText);
+ bufTJ.append("[");
+ bufTJ.append(startText);
}
writeChar(codepoint, bufTJ);
}
/**
* Writes a glyph adjust value to the "TJ-Buffer".
+
+ * <p>Assumes the following:</p>
+ * <ol>
+ * <li>if buffer is currently empty, then this is the start of the array object
+ * that encodes the adjustment and character values, and, therfore, a LEFT
+ * SQUARE BRACKET '[' must be prepended; and
+ * </li>
+ * <li>otherwise (the buffer is
+ * not empty), then the last element written to the buffer was a mapped
+ * character, and, therefore, a terminating '>' or ')' followed by a space
+ * must be appended to the buffer prior to appending the adjustment value.
+ * </li>
+ * </ol>
* @param adjust the glyph adjust value in thousands of text unit space.
*/
public void adjustGlyphTJ(double adjust) {
if (bufTJ == null) {
bufTJ = new StringBuffer();
}
- if (bufTJ.length() > 0) {
- bufTJ.append(endText).append(" ");
- }
if (bufTJ.length() == 0) {
bufTJ.append("[");
+ } else {
+ bufTJ.append(endText);
+ bufTJ.append(" ");
}
bufTJ.append(PDFNumber.doubleOut(adjust, DEC - 4));
bufTJ.append(" ");
return bufTJ != null && bufTJ.length() > 0;
}
+ /**
+ * Writes a "Td" command with specified x and y coordinates.
+ * @param x coordinate
+ * @param y coordinate
+ */
+ public void writeTd ( double x, double y ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append(PDFNumber.doubleOut(x, DEC));
+ sb.append(' ');
+ sb.append(PDFNumber.doubleOut(y, DEC));
+ sb.append ( " Td\n" );
+ write ( sb.toString() );
+ }
+
+ /**
+ * Writes a "Tj" command with specified character code.
+ * @param ch character code to write
+ */
+ public void writeTj ( char ch ) {
+ StringBuffer sb = new StringBuffer();
+ sb.append ( '<' );
+ writeChar ( ch, sb, true );
+ sb.append ( '>' );
+ sb.append ( " Tj\n" );
+ write ( sb.toString() );
+ }
+
}
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
/** {@inheritDoc} */
public void drawText( // CSOK: MethodLength
- int x, int y, final int letterSpacing, final int wordSpacing, final int[] dx,
- final String text) throws IFException {
+ int x, int y, final int letterSpacing, final int wordSpacing,
+ final int[][] dp, final String text) throws IFException {
final int fontSize = this.state.getFontSize();
getPaintingState().setFontSize(fontSize);
builder.setCodedFont((byte)fontReference);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
StringBuffer sb = new StringBuffer();
* @param y Y-coordinate of the starting point of the text
* @param letterSpacing additional spacing between characters (may be 0)
* @param wordSpacing additional spacing between words (may be 0)
- * @param dx an array of adjustment values for each character in X-direction (may be null)
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null); if
+ * not null, then adjustments.length must be the same as text.length()
* @param text the text
* @throws IFException if an error occurs while handling this event
*/
void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException;
+ int[][] dp, String text) throws IFException;
/**
* Restricts the current clipping region with the given rectangle.
s = lastAttributes.getValue("word-spacing");
int wordSpacing = (s != null ? Integer.parseInt(s) : 0);
int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx");
+ int[][] dp = XMLUtil.getAttributeAsPositionAdjustments(lastAttributes, "dp");
+ // if only DX present, then convert DX to DP; otherwise use only DP,
+ // effectively ignoring DX
+ if ( ( dp == null ) && ( dx != null ) ) {
+ dp = IFUtil.convertDXToDP ( dx );
+ }
setStructurePointer(lastAttributes);
- painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString());
+ painter.drawText(x, y, letterSpacing, wordSpacing, dp, content.toString());
resetStructurePointer();
}
Font font = getFontFromArea(word.getParentArea());
String s = word.getWord();
- renderText(s, word.getLetterAdjustArray(), word.isReversed(),
+ int[][] dp = word.getGlyphPositionAdjustments();
+ if ( dp == null ) {
+ dp = IFUtil.convertDXToDP ( word.getLetterAdjustArray() );
+ }
+
+ renderText(s, dp, word.isReversed(),
font, (AbstractTextArea)word.getParentArea());
super.renderWord(word);
super.renderSpace(space);
}
+ private void renderText(String s,
+ int[][] dp, boolean reversed,
+ Font font, AbstractTextArea parentArea) {
+ if ( ( dp == null ) || IFUtil.isDPOnlyDX ( dp ) ) {
+ int[] dx = IFUtil.convertDPToDX ( dp );
+ renderTextWithAdjustments ( s, dx, reversed, font, parentArea );
+ } else {
+ renderTextWithAdjustments ( s, dp, reversed, font, parentArea );
+ }
+ }
+
/**
- * Does low-level rendering of text.
+ * Does low-level rendering of text using DX only position adjustments.
* @param s text to render
- * @param letterAdjust an array of widths for letter adjustment (may be null)
+ * @param dx an array of widths for letter adjustment (may be null)
* @param reversed if true then text has been reversed (from logical order)
* @param font to font in use
* @param parentArea the parent text area to retrieve certain traits from
*/
- protected void renderText(String s,
- int[] letterAdjust, boolean reversed,
+ private void renderTextWithAdjustments(String s,
+ int[] dx, boolean reversed,
Font font, AbstractTextArea parentArea) {
int l = s.length();
if (l == 0) {
return;
}
-
- if (letterAdjust != null) {
- textUtil.adjust(letterAdjust[0]);
- }
for (int i = 0; i < l; i++) {
char ch = s.charAt(i);
textUtil.addChar(ch);
int tls = (i < l - 1 ? parentArea.getTextLetterSpaceAdjust() : 0);
glyphAdjust += tls;
}
- if (letterAdjust != null && i < l - 1) {
- glyphAdjust += letterAdjust[i + 1];
+ if (dx != null && i < l) {
+ glyphAdjust += dx[i];
}
-
textUtil.adjust(glyphAdjust);
}
}
+ /**
+ * Does low-level rendering of text using generalized position adjustments.
+ * @param s text to render
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @param reversed if true then text has been reversed (from logical order)
+ * @param font to font in use
+ * @param parentArea the parent text area to retrieve certain traits from
+ */
+ private void renderTextWithAdjustments(String s,
+ int[][] dp, boolean reversed,
+ Font font, AbstractTextArea parentArea) {
+ assert !textUtil.combined;
+ for ( int i = 0, n = s.length(); i < n; i++ ) {
+ textUtil.addChar ( s.charAt ( i ) );
+ if ( dp != null ) {
+ textUtil.adjust ( dp[i] );
+ }
+ }
+ }
+
private class TextUtil {
private static final int INITIAL_BUFFER_SIZE = 16;
- private int[] dx = new int[INITIAL_BUFFER_SIZE];
- private int lastDXPos = 0;
+ private int[][] dp = new int[INITIAL_BUFFER_SIZE][4];
+ // private int lastDPPos = 0; // TBD - not yet used
private final StringBuffer text = new StringBuffer();
private int startx, starty;
private int tls, tws;
text.append(ch);
}
- void adjust(int adjust) {
- if (adjust != 0) {
+ void adjust(int dx) {
+ adjust ( new int[] {
+ dx, // xPlaAdjust
+ 0, // yPlaAdjust
+ dx, // xAdvAdjust
+ 0 // yAdvAdjust
+ } );
+ }
+
+ void adjust(int[] pa) {
+ if ( !IFUtil.isPAIdentity ( pa ) ) {
int idx = text.length();
- if (idx > dx.length - 1) {
- int newSize = Math.max(dx.length, idx + 1) + INITIAL_BUFFER_SIZE;
- int[] newDX = new int[newSize];
- System.arraycopy(dx, 0, newDX, 0, dx.length);
- dx = newDX;
+ if (idx > dp.length - 1) {
+ int newSize = Math.max(dp.length, idx + 1) + INITIAL_BUFFER_SIZE;
+ int[][] newDP = new int[newSize][];
+ // reuse prior PA[0]...PA[dp.length-1]
+ System.arraycopy(dp, 0, newDP, 0, dp.length);
+ // populate new PA[dp.length]...PA[newDP.length-1]
+ for ( int i = dp.length, n = newDP.length; i < n; i++ ) {
+ newDP[i] = new int[4];
+ }
+ dp = newDP;
}
- dx[idx] += adjust;
- lastDXPos = idx;
+ IFUtil.adjustPA ( dp[idx - 1], pa );
+ // lastDPPos = idx;
}
}
void reset() {
if (text.length() > 0) {
text.setLength(0);
- Arrays.fill(dx, 0);
- lastDXPos = 0;
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ Arrays.fill(dp[i], 0);
+ }
+ // lastDPPos = 0;
}
}
void flush() {
if (text.length() > 0) {
try {
- int[] effDX = null;
- if (lastDXPos > 0) {
- int size = lastDXPos + 1;
- effDX = new int[size];
- System.arraycopy(dx, 0, effDX, 0, size);
- }
if (combined) {
- painter.drawText(startx, starty, 0, 0, effDX, text.toString());
+ painter.drawText(startx, starty, 0, 0,
+ trimAdjustments ( dp, text.length() ), text.toString());
} else {
- painter.drawText(startx, starty, tls, tws, effDX, text.toString());
+ painter.drawText(startx, starty, tls, tws,
+ trimAdjustments ( dp, text.length() ), text.toString());
}
} catch (IFException e) {
handleIFException(e);
reset();
}
}
+
+ /**
+ * Trim adjustments array <code>dp</code> to be no greater length than
+ * text length, and where trailing all-zero entries are removed.
+ * @param dp a position adjustments array (or null)
+ * @param textLength the length of the associated text
+ * @return either the original value of <code>dp</code> or a copy
+ * of its first N significant adjustment entries, such that N is
+ * no greater than text length, and the last entry has a non-zero
+ * adjustment.
+ */
+ private int[][] trimAdjustments ( int[][] dp, int textLength ) {
+ if ( dp != null ) {
+ int tl = textLength;
+ int pl = dp.length;
+ int i = ( tl < pl ) ? tl : pl;
+ while ( i > 0 ) {
+ int[] pa = dp [ i - 1 ];
+ if ( !IFUtil.isPAIdentity ( pa ) ) {
+ break;
+ } else {
+ i--;
+ }
+ }
+ if ( i == 0 ) {
+ dp = null;
+ } else if ( i < pl ) {
+ dp = IFUtil.copyDP ( dp, 0, i );
+ }
+ }
+ return dp;
+ }
}
/** {@inheritDoc} */
/** {@inheritDoc} */
public void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException {
+ int[][] dp, String text) throws IFException {
try {
AttributesImpl atts = new AttributesImpl();
addAttribute(atts, "x", Integer.toString(x));
if (wordSpacing != 0) {
addAttribute(atts, "word-spacing", Integer.toString(wordSpacing));
}
- if (dx != null) {
- addAttribute(atts, "dx", IFUtil.toString(dx));
+ if (dp != null) {
+ if ( IFUtil.isDPIdentity(dp) ) {
+ // don't add dx or dp attribute
+ } else if ( IFUtil.isDPOnlyDX(dp) ) {
+ // add dx attribute only
+ int[] dx = IFUtil.convertDPToDX(dp);
+ addAttribute(atts, "dx", IFUtil.toString(dx));
+ } else {
+ // add dp attribute only
+ addAttribute(atts, "dp", XMLUtil.encodePositionAdjustments(dp));
+ }
}
addStructurePointerAttribute(atts);
handler.startElement(EL_TEXT, atts);
return documentHandler.getMimeType();
}
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @param count if <code>dp</code> is not null, then a count of dp values to convert
+ * @return if <code>dp</code> is not null, then an array of adjustments to the current
+ * x position prior to rendering individual glyphs; otherwise, null
+ */
+ public static int[] convertDPToDX ( int[][] dp, int count ) {
+ int[] dx;
+ if ( dp != null ) {
+ dx = new int [ count ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ dx [ i ] = dp [ i ] [ 0 ]; // xPlaAdjust[i]
+ }
+ } else {
+ dx = null;
+ }
+ return dx;
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return if <code>dp</code> is not null, then an array of adjustments to the current
+ * x position prior to rendering individual glyphs; otherwise, null
+ */
+ public static int[] convertDPToDX ( int[][] dp ) {
+ return convertDPToDX ( dp, ( dp != null ) ? dp.length : 0 );
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dx an array of adjustments to the current x position prior to rendering
+ * individual glyphs or null
+ * @param count if <code>dx</code> is not null, then a count of dx values to convert
+ * @return if <code>dx</code> is not null, then an array of 4-tuples, expressing [X,Y]
+ * placment adjustments and [X,Y] advancement adjustments, in that order; otherwise, null
+ */
+ public static int[][] convertDXToDP ( int[] dx, int count ) {
+ int[][] dp;
+ if ( dx != null ) {
+ dp = new int [ count ] [ 4 ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ int[] pa = dp [ i ];
+ int d = dx [ i ];
+ pa [ 0 ] = d; // xPlaAdjust[i]
+ pa [ 2 ] = d; // xAdvAdjust[i]
+ }
+ } else {
+ dp = null;
+ }
+ return dp;
+ }
+
+ /**
+ * Convert the general gpos 'dp' adjustments to the older 'dx' adjustments.
+ * This utility method is used to provide backward compatibility in implementations
+ * of IFPainter that have not yet been upgraded to the general position adjustments format.
+ * @param dx an array of adjustments to the current x position prior to rendering
+ * individual glyphs or null
+ * @return if <code>dx</code> is not null, then an array of 4-tuples, expressing [X,Y]
+ * placment adjustments and [X,Y] advancement adjustments, in that order; otherwise, null
+ */
+ public static int[][] convertDXToDP ( int[] dx ) {
+ return convertDXToDP ( dx, ( dx != null ) ? dx.length : 0 );
+ }
+
+ /**
+ * Determine if position adjustment is the identity adjustment, i.e., no non-zero adjustment.
+ * @param pa a 4-tuple, expressing [X,Y] placment and [X,Y] advance adjuustments (may be null)
+ * @return true if <code>dp</code> is null or contains no non-zero adjustment
+ */
+ public static boolean isPAIdentity ( int[] pa ) {
+ if ( pa == null ) {
+ return true;
+ } else {
+ for ( int k = 0; k < 4; k++ ) {
+ if ( pa[k] != 0 ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Determine if position adjustments is the identity adjustment, i.e., no non-zero adjustment.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return true if <code>dp</code> is null or contains no non-zero adjustment
+ */
+ public static boolean isDPIdentity ( int[][] dp ) {
+ if ( dp == null ) {
+ return true;
+ } else {
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ if ( !isPAIdentity ( dp[i] ) ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Determine if position adjustments comprises only DX adjustments as encoded by
+ * {@link #convertDPToDX}. Note that if given a set of all all zero position
+ * adjustments, both this method and {@link #isDPIdentity} will return true;
+ * however, this method may return true when {@link #isDPIdentity} returns false.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order (may be null)
+ * @return true if <code>dp</code> is not null and contains only xPlaAdjust
+ * and xAdvAdjust values consistent with the output of {@link #convertDPToDX}.
+ */
+ public static boolean isDPOnlyDX ( int[][] dp ) {
+ if ( dp == null ) {
+ return false;
+ } else {
+ for ( int i = 0, n = dp.length; i < n; i++ ) {
+ int[] pa = dp[i];
+ if ( pa[0] != pa[2] ) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Adjust a position adjustments array. If both <code>paDst</code> and <code>paSrc</code> are
+ * non-null, then <code>paSrc[i]</code> is added to <code>paDst[i]</code>.
+ * @param paDst a 4-tuple, expressing [X,Y] placment
+ * and [X,Y] advance adjuustments (may be null)
+ * @param paSrc a 4-tuple, expressing [X,Y] placment
+ * and [X,Y] advance adjuustments (may be null)
+ */
+ public static void adjustPA ( int[] paDst, int[] paSrc ) {
+ if ( ( paDst != null ) && ( paSrc != null ) ) {
+ assert paDst.length == 4; assert paSrc.length == 4;
+ for ( int i = 0; i < 4; i++ ) {
+ paDst[i] += paSrc[i];
+ }
+ }
+ }
+
+ /**
+ * Copy entries from position adjustments.
+ * @param dp an array of 4-tuples, expressing [X,Y] placment
+ * adjustments and [X,Y] advancement adjustments, in that order
+ * @param offset starting offset from which to copy
+ * @param count number of entries to copy
+ * @return a deep copy of the count position adjustment entries start at
+ * offset
+ */
+ public static int[][] copyDP ( int[][] dp, int offset, int count ) {
+ if ( ( dp == null ) || ( offset > dp.length ) || ( ( offset + count ) > dp.length ) ) {
+ throw new IllegalArgumentException();
+ } else {
+ int[][] dpNew = new int [ count ] [ 4 ];
+ for ( int i = 0, n = count; i < n; i++ ) {
+ int[] paDst = dpNew [ i ];
+ int[] paSrc = dp [ i + offset ];
+ for ( int k = 0; k < 4; k++ ) {
+ paDst [ k ] = paSrc [ k ];
+ }
+ }
+ return dpNew;
+ }
+ }
+
}
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text)
throws IFException {
g2dState.updateColor(state.getTextColor());
FontTriplet triplet = new FontTriplet(
Point2D cursor = new Point2D.Float(0, 0);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
if (dx != null && dxl > 0 && dx[0] != 0) {
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFPainter;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.render.java2d.FontMetricsMapper;
import org.apache.fop.render.java2d.Java2DPainter;
import org.apache.fop.traits.BorderProps;
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text)
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp, String text)
throws IFException {
try {
FontTriplet triplet = new FontTriplet(
? false
: HardcodedFonts.setFont(gen, fontKey, state.getFontSize(), text);
if (pclFont) {
- drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
} else {
- drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextAsBitmap(x, y, letterSpacing, wordSpacing, dp, text, triplet);
if (DEBUG) {
state.setTextColor(Color.GRAY);
HardcodedFonts.setFont(gen, "F1", state.getFontSize(), text);
- drawTextNative(x, y, letterSpacing, wordSpacing, dx, text, triplet);
+ drawTextNative(x, y, letterSpacing, wordSpacing, dp, text, triplet);
}
}
} catch (IOException ioe) {
}
}
- private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+ private void drawTextNative(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text, FontTriplet triplet) throws IOException {
Color textColor = state.getTextColor();
if (textColor != null) {
float fontSize = state.getFontSize() / 1000f;
Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
StringBuffer sb = new StringBuffer(Math.max(16, l));
private Rectangle getTextBoundingBox( // CSOK: ParameterNumber
int x, int y,
- int letterSpacing, int wordSpacing, int[] dx,
+ int letterSpacing, int wordSpacing, int[][] dp,
String text,
Font font, FontMetricsMapper metrics) {
int maxAscent = metrics.getMaxAscent(font.getFontSize()) / 1000;
0, maxAscent - descent + 2 * safetyMargin);
int l = text.length();
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
if (dx != null && dxl > 0 && dx[0] != 0) {
}
private void drawTextAsBitmap(final int x, final int y,
- final int letterSpacing, final int wordSpacing, final int[] dx,
+ final int letterSpacing, final int wordSpacing, final int[][] dp,
final String text, FontTriplet triplet) throws IFException {
//Use Java2D to paint different fonts via bitmap
final Font font = parent.getFontInfo().getFontInstance(triplet, state.getFontSize());
final int baselineOffset = maxAscent + safetyMargin;
final Rectangle boundingBox = getTextBoundingBox(x, y,
- letterSpacing, wordSpacing, dx, text, font, mapper);
+ letterSpacing, wordSpacing, dp, text, font, mapper);
final Dimension dim = boundingBox.getSize();
Graphics2DImagePainter painter = new Graphics2DImagePainter() {
Java2DPainter painter = new Java2DPainter(g2d,
getContext(), parent.getFontInfo(), state);
try {
- painter.drawText(x, y, letterSpacing, wordSpacing, dx, text);
+ painter.drawText(x, y, letterSpacing, wordSpacing, dp, text);
} catch (IFException e) {
//This should never happen with the Java2DPainter
throw new RuntimeException("Unexpected error while painting text", e);
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
}
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text)
throws IFException {
if (accessEnabled) {
FontTriplet triplet = new FontTriplet(
state.getFontFamily(), state.getFontStyle(), state.getFontWeight());
+
+ if ( ( dp == null ) || IFUtil.isDPOnlyDX ( dp ) ) {
+ drawTextWithDX ( x, y, text, triplet, letterSpacing,
+ wordSpacing, IFUtil.convertDPToDX ( dp ) );
+ } else {
+ drawTextWithDP ( x, y, text, triplet, letterSpacing,
+ wordSpacing, dp );
+ }
+ }
+
+ private void drawTextWithDX ( int x, int y, String text, FontTriplet triplet,
+ int letterSpacing, int wordSpacing, int[] dx ) {
+
//TODO Ignored: state.getFontVariant()
//TODO Opportunity for font caching if font state is more heavily used
String fontKey = getFontInfo().getInternalFontKey(triplet);
textutil.writeTJ();
}
+ private static int[] paZero = new int[4];
+
+ private void drawTextWithDP ( int x, int y, String text, FontTriplet triplet,
+ int letterSpacing, int wordSpacing, int[][] dp ) {
+ assert text != null;
+ assert triplet != null;
+ assert dp != null;
+ assert dp.length == text.length();
+ String fk = getFontInfo().getInternalFontKey(triplet);
+ Typeface tf = getTypeface(fk);
+ if ( tf.isMultiByte() ) {
+ int fs = state.getFontSize();
+ float fsPoints = fs / 1000f;
+ Font f = getFontInfo().getFontInstance(triplet, fs);
+ // String fn = f.getFontName();
+ PDFTextUtil tu = generator.getTextUtil();
+ double xc = 0f;
+ double yc = 0f;
+ double xoLast = 0f;
+ double yoLast = 0f;
+ tu.writeTextMatrix ( new AffineTransform ( 1, 0, 0, -1, x / 1000f, y / 1000f ) );
+ tu.writeTf ( fk, fsPoints );
+ for ( int i = 0, n = text.length(); i < n; i++ ) {
+ char ch = text.charAt ( i );
+ int[] pa = ( i < dp.length ) ? dp [ i ] : paZero;
+ double xo = xc + pa[0];
+ double yo = yc + pa[1];
+ double xa = f.getCharWidth(ch);
+ double ya = 0;
+ double xd = ( xo - xoLast ) / 1000f;
+ double yd = ( yo - yoLast ) / 1000f;
+ tu.writeTd ( xd, yd );
+ tu.writeTj ( f.mapChar ( ch ) );
+ xc += xa + pa[2];
+ yc += ya + pa[3];
+ xoLast = xo;
+ yoLast = yo;
+ }
+ }
+ }
+
}
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.CharUtilities;
/** {@inheritDoc} */
public void drawText(int x, int y, int letterSpacing, int wordSpacing,
- int[] dx, String text) throws IFException {
+ int[][] dp, String text) throws IFException {
try {
- //Note: dy is currently ignored
PSGenerator generator = getGenerator();
generator.useColor(state.getTextColor());
beginTextObject();
if (currentEncoding != encoding) {
if (i > 0) {
writeText(text, start, i - start,
- letterSpacing, wordSpacing, dx, font, tf);
+ letterSpacing, wordSpacing, dp, font, tf);
}
if (encoding == 0) {
useFont(fontKey, sizeMillipoints);
}
}
writeText(text, start, textLen - start,
- letterSpacing, wordSpacing, dx, font, tf);
+ letterSpacing, wordSpacing, dp, font, tf);
} else {
//Simple single-font painting
useFont(fontKey, sizeMillipoints);
writeText(text, 0, textLen,
- letterSpacing, wordSpacing, dx, font, tf);
+ letterSpacing, wordSpacing, dp, font, tf);
}
} catch (IOException ioe) {
throw new IFException("I/O error in drawText()", ioe);
private void writeText( // CSOK: ParameterNumber
String text, int start, int len,
- int letterSpacing, int wordSpacing, int[] dx,
+ int letterSpacing, int wordSpacing, int[][] dp,
Font font, Typeface tf) throws IOException {
PSGenerator generator = getGenerator();
int end = start + len;
int lineStart = 0;
StringBuffer accText = new StringBuffer(initialSize);
StringBuffer sb = new StringBuffer(initialSize);
+ int[] dx = IFUtil.convertDPToDX ( dp );
int dxl = (dx != null ? dx.length : 0);
for (int i = start; i < end; i++) {
char orgChar = text.charAt(i);
import org.apache.fop.render.XMLHandler;
import org.apache.fop.util.ColorUtil;
import org.apache.fop.util.DOM2SAX;
+import org.apache.fop.util.XMLUtil;
/**
* Renderer that renders areas to XML for debugging purposes.
}
}
maybeAddLevelAttribute(word);
+ maybeAddPositionAdjustAttribute(word);
if ( word.isReversed() ) {
addAttribute("reversed", "true");
}
}
}
+ private void maybeAddPositionAdjustAttribute ( WordArea w ) {
+ int[][] adjustments = w.getGlyphPositionAdjustments();
+ if ( adjustments != null ) {
+ addAttribute ( "position-adjust", XMLUtil.encodePositionAdjustments ( adjustments ) );
+ }
+ }
+
+
}
for ( int i = 0; i < s.length(); i++ ) {
char c = s.charAt(i);
if ( ( c >= 32 ) && ( c < 127 ) ) {
- sb.append ( c );
+ if ( c == '<' ) {
+ sb.append ( "<" );
+ } else if ( c == '>' ) {
+ sb.append ( ">" );
+ } else if ( c == '&' ) {
+ sb.append ( "&" );
+ } else {
+ sb.append ( c );
+ }
} else {
sb.append ( charToNCRef ( c ) );
}
return sb.toString();
}
+ private static String padLeft ( String s, int width, char pad ) {
+ StringBuffer sb = new StringBuffer();
+ for ( int i = s.length(); i < width; i++ ) {
+ sb.append(pad);
+ }
+ sb.append ( s );
+ return sb.toString();
+ }
+
+ /**
+ * Format character for debugging output, which it is prefixed with "0x", padded left with '0'
+ * and either 4 or 6 hex characters in width according to whether it is in the BMP or not.
+ * @param c character code
+ * @return formatted character string
+ */
+ public static String format ( int c ) {
+ if ( c < 1114112 ) {
+ return "0x" + padLeft ( Integer.toString ( c, 16 ), ( c < 65536 ) ? 4 : 6, '0' );
+ } else {
+ return "!NOT A CHARACTER!";
+ }
+ }
+
private static Map scriptTagsMap = null;
private static void putScriptTag ( Map m, int code, String tag ) {
}
}
+ /**
+ * Encode a glyph position adjustments array as a string, where the string value
+ * adheres to the following syntax:
+ *
+ * count ( 'Z' repeat | number )
+ *
+ * where each token is separated by whitespace, except that 'Z' followed by repeat
+ * are considered to be a single token with no intervening whitespace, and where
+ * 'Z' repeat encodes repeated zeroes.
+ * @param dp the adjustments array
+ * @param paCount the number of entries to encode from adjustments array
+ * @return the encoded value
+ */
+ public static String encodePositionAdjustments ( int[][] dp, int paCount ) {
+ assert dp != null;
+ StringBuffer sb = new StringBuffer();
+ int na = paCount;
+ int nz = 0;
+ sb.append ( na );
+ for ( int i = 0; i < na; i++ ) {
+ int[] pa = dp [ i ];
+ for ( int k = 0; k < 4; k++ ) {
+ int a = pa [ k ];
+ if ( a != 0 ) {
+ encodeNextAdjustment ( sb, nz, a ); nz = 0;
+ } else {
+ nz++;
+ }
+ }
+ }
+ encodeNextAdjustment ( sb, nz, 0 );
+ return sb.toString();
+ }
+
+ /**
+ * Encode a glyph position adjustments array as a string, where the string value
+ * adheres to the following syntax:
+ *
+ * count ( 'Z' repeat | number )
+ *
+ * where each token is separated by whitespace, except that 'Z' followed by repeat
+ * are considered to be a single token with no intervening whitespace.
+ * @param dp the adjustments array
+ * @return the encoded value
+ */
+ public static String encodePositionAdjustments ( int[][] dp ) {
+ assert dp != null;
+ return encodePositionAdjustments ( dp, dp.length );
+ }
+
+ private static void encodeNextAdjustment ( StringBuffer sb, int nz, int a ) {
+ encodeZeroes ( sb, nz );
+ encodeAdjustment ( sb, a );
+ }
+
+ private static void encodeZeroes ( StringBuffer sb, int nz ) {
+ if ( nz > 0 ) {
+ sb.append ( ' ' );
+ if ( nz == 1 ) {
+ sb.append ( '0' );
+ } else {
+ sb.append ( 'Z' );
+ sb.append ( nz );
+ }
+ }
+ }
+
+ private static void encodeAdjustment ( StringBuffer sb, int a ) {
+ if ( a != 0 ) {
+ sb.append ( ' ' );
+ sb.append ( a );
+ }
+ }
+
+ /**
+ * Decode a string as a glyph position adjustments array, where the string
+ * shall adhere to the syntax specified by {@link #encodePositionAdjustments}.
+ * @param value the encoded value
+ * @return the position adjustments array
+ */
+ public static int[][] decodePositionAdjustments ( String value ) {
+ int[][] dp = null;
+ if ( value != null ) {
+ String[] sa = value.split ( "\\s" );
+ if ( sa != null ) {
+ if ( sa.length > 0 ) {
+ int na = Integer.parseInt ( sa[0] );
+ dp = new int [ na ] [ 4 ];
+ for ( int i = 1, n = sa.length, k = 0; i < n; i++ ) {
+ String s = sa [ i ];
+ if ( s.charAt(0) == 'Z' ) {
+ int nz = Integer.parseInt ( s.substring ( 1 ) );
+ k += nz;
+ } else {
+ dp [ k / 4 ] [ k % 4 ] = Integer.parseInt ( s );
+ k += 1;
+ }
+ }
+ }
+ }
+ }
+ return dp;
+ }
+
+ /**
+ * Returns an attribute value as a glyph position adjustments array. The string value
+ * is expected to be a non-empty sequence of either Z<repeat> or <number>, where the
+ * former encodes a repeat count (of zeroes) and the latter encodes a integer number,
+ * and where each item is separated by whitespace.
+ * @param attributes the Attributes object
+ * @param name the name of the attribute
+ * @return the position adjustments array
+ */
+ public static int[][] getAttributeAsPositionAdjustments(Attributes attributes, String name) {
+ String s = attributes.getValue(name);
+ if (s == null) {
+ return null;
+ } else {
+ return decodePositionAdjustments(s.trim());
+ }
+ }
+
}
import org.apache.fop.render.intermediate.IFContext;
import org.apache.fop.render.intermediate.IFException;
import org.apache.fop.render.intermediate.IFState;
+import org.apache.fop.render.intermediate.IFUtil;
import org.apache.fop.traits.BorderProps;
import org.apache.fop.traits.RuleStyle;
import org.apache.fop.util.ColorUtil;
/** {@inheritDoc} */
- public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx,
+ public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[][] dp,
String text) throws IFException {
try {
establish(MODE_TEXT);
if (wordSpacing != 0) {
XMLUtil.addAttribute(atts, "word-spacing", SVGUtil.formatMptToPt(wordSpacing));
}
- if (dx != null) {
+ if (dp != null) {
+ int[] dx = IFUtil.convertDPToDX(dp);
XMLUtil.addAttribute(atts, "dx", SVGUtil.formatMptArrayToPt(dx));
}
handler.startElement("text", atts);