You are viewing a plain text version of this content. The canonical link for it is here.
Posted to fop-commits@xmlgraphics.apache.org by ga...@apache.org on 2012/02/26 03:29:29 UTC

svn commit: r1293736 [8/38] - in /xmlgraphics/fop/trunk: ./ src/codegen/java/org/apache/fop/tools/ src/codegen/unicode/java/org/apache/fop/complexscripts/ src/codegen/unicode/java/org/apache/fop/complexscripts/bidi/ src/documentation/content/xdocs/trun...

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphProcessingState.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,1135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.GlyphContextTester;
+import org.apache.fop.complexscripts.util.GlyphTester;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// 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 lookupFlags;
+    /** 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, int flags) { return isIgnoredBase(gi, flags); } };
+        this.ignoreLigature = new GlyphTester() { public boolean test(int gi, int flags) { return isIgnoredLigature(gi, flags); } };
+        this.ignoreMark = new GlyphTester() { public boolean test(int gi, int flags) { return isIgnoredMark(gi, flags); } };
+    }
+
+    /**
+     * 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 setLookupFlags ( int flags ) {
+        if ( this.lookupFlags == 0 ) {
+            this.lookupFlags = flags;
+        } else if ( flags == 0 ) {
+            this.lookupFlags = 0;
+        }
+    }
+
+    /**
+     * Obtain governing lookup  flags.
+     * @return lookup flags (zero may indicate unset or no flags)
+     */
+    public int getLookupFlags() {
+        return lookupFlags;
+    }
+
+    /**
+     * 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() );
+        setLookupFlags ( st.getFlags() );
+        setIgnoreDefault ( getIgnoreTester ( getLookupFlags() ) );
+    }
+
+    /**
+     * Reset glyph subtable specific state.
+     */
+    public void resetSubtableState() {
+        setGDEF ( null );
+        setLookupFlags ( 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, getLookupFlags() ) ) {
+                    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, getLookupFlags() ) ) {
+                    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, getLookupFlags() ) ) {
+                        ignored++;
+                    } else {
+                        counted++;
+                    }
+                }
+            }
+        }
+        return new int[] { counted, ignored };
+    }
+
+    private int[] getGlyphsAvailableReverse ( int start, GlyphTester ignoreTester ) throws IndexOutOfBoundsException {
+        int counted = 0;
+        int ignored = 0;
+        if ( ignoreTester == null ) {
+            counted = start + 1;
+        } else {
+            for ( int i = start; i >= 0; i-- ) {
+                int gi = getGlyph ( i - index );
+                if ( gi == 65535 ) {
+                    ignored++;
+                } else {
+                    if ( ignoreTester.test ( gi, getLookupFlags() ) ) {
+                        ignored++;
+                    } else {
+                        counted++;
+                    }
+                }
+            }
+        }
+        return new int[] { counted, ignored };
+    }
+
+    /**
+     * Determine number of glyphs available starting at specified offset from current position. If
+     * <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, getLookupFlags() ) ) {
+                    if ( k < count ) {
+                        associations [ k++ ] = getAssociation ( i - index ); counted++;
+                    } else {
+                        break;
+                    }
+                } else {
+                    ignored++;
+                }
+            }
+        }
+        if ( ( counts != null ) && ( counts.length > 1 ) ) {
+            counts[0] = counted;
+            counts[1] = ignored;
+        }
+        return associations;
+    }
+
+    private 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, getLookupFlags() ) ) {
+                    if ( k < count ) {
+                        associations [ k++ ] = getAssociation ( i - index ); counted++;
+                    } else {
+                        break;
+                    }
+                } else {
+                    ignored++;
+                }
+            }
+        }
+        if ( ( counts != null ) && ( counts.length > 1 ) ) {
+            counts[0] = counted;
+            counts[1] = ignored;
+        }
+        return associations;
+    }
+
+    /**
+     * Obtain <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 ( script, language, feature, igs, index, getLookupFlags() );
+        }
+    }
+
+    /**
+     * Apply default application semantices; namely, consume one glyph.
+     */
+    public void applyDefault() {
+        consumed += 1;
+    }
+
+    /**
+     * Determine if specified glyph is a base glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+     */
+    public boolean isBase ( int gi ) {
+        if ( gdef != null ) {
+            return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_BASE );
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if specified glyph is an ignored base glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @param flags that apply to lookup in scope
+     * @return true if glyph definition table records glyph as a base glyph; otherwise, false
+     */
+    public boolean isIgnoredBase ( int gi, int flags ) {
+        return ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) && isBase ( gi );
+    }
+
+    /**
+     * Determine if specified glyph is an ligature glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+     */
+    public boolean isLigature ( int gi ) {
+        if ( gdef != null ) {
+            return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_LIGATURE );
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if specified glyph is an ignored ligature glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @param flags that apply to lookup in scope
+     * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+     */
+    public boolean isIgnoredLigature ( int gi, int flags ) {
+        return ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) && isLigature ( gi );
+    }
+
+    /**
+     * Determine if specified glyph is a mark glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @return true if glyph definition table records glyph as a mark glyph; otherwise, false
+     */
+    public boolean isMark ( int gi ) {
+        if ( gdef != null ) {
+            return gdef.isGlyphClass ( gi, GlyphDefinitionTable.GLYPH_CLASS_MARK );
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Determine if specified glyph is an ignored ligature glyph according to the governing
+     * glyph definition table.
+     * @param gi glyph index to test
+     * @param flags that apply to lookup in scope
+     * @return true if glyph definition table records glyph as a ligature glyph; otherwise, false
+     */
+    public boolean isIgnoredMark ( int gi, int flags ) {
+        if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+            return isMark ( gi );
+        } else if ( ( flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE ) != 0 ) {
+            int lac = ( flags & GlyphSubtable.LF_MARK_ATTACHMENT_TYPE ) >> 8;
+            int gac = gdef.getMarkAttachClass ( gi );
+            return ( gac != lac );
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Obtain an ignored glyph tester that corresponds to the specified lookup flags.
+     * @param flags lookup flags
+     * @return a glyph tester
+     */
+    public GlyphTester getIgnoreTester ( int flags ) {
+        if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+            if ( ( flags & (GlyphSubtable.LF_IGNORE_LIGATURE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+                return ignoreBase;
+            } else {
+                return getCombinedIgnoreTester ( flags );
+            }
+        }
+        if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+            if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_MARK) ) == 0 ) {
+                return ignoreLigature;
+            } else {
+                return getCombinedIgnoreTester ( flags );
+            }
+        }
+        if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+            if ( ( flags & (GlyphSubtable.LF_IGNORE_BASE | GlyphSubtable.LF_IGNORE_LIGATURE) ) == 0 ) {
+                return ignoreMark;
+            } else {
+                return getCombinedIgnoreTester ( flags );
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Obtain an ignored glyph tester that corresponds to the specified multiple (combined) lookup flags.
+     * @param flags lookup flags
+     * @return a glyph tester
+     */
+    public GlyphTester getCombinedIgnoreTester ( int flags ) {
+        GlyphTester[] gta = new GlyphTester [ 3 ];
+        int ngt = 0;
+        if ( ( flags & GlyphSubtable.LF_IGNORE_BASE ) != 0 ) {
+            gta [ ngt++ ] = ignoreBase;
+        }
+        if ( ( flags & GlyphSubtable.LF_IGNORE_LIGATURE ) != 0 ) {
+            gta [ ngt++ ] = ignoreLigature;
+        }
+        if ( ( flags & GlyphSubtable.LF_IGNORE_MARK ) != 0 ) {
+            gta [ ngt++ ] = ignoreMark;
+        }
+        return getCombinedOrTester ( gta, ngt );
+    }
+
+    /**
+     * Obtain an combined OR glyph tester.
+     * @param gta an array of glyph testers
+     * @param ngt number of glyph testers present in specified array
+     * @return a combined OR glyph tester
+     */
+    public GlyphTester getCombinedOrTester ( GlyphTester[] gta, int ngt ) {
+        if ( ngt > 0 ) {
+            return new CombinedOrGlyphTester ( gta, ngt );
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Obtain an combined AND glyph tester.
+     * @param gta an array of glyph testers
+     * @param ngt number of glyph testers present in specified array
+     * @return a combined AND glyph tester
+     */
+    public GlyphTester getCombinedAndTester ( GlyphTester[] gta, int ngt ) {
+        if ( ngt > 0 ) {
+            return new CombinedAndGlyphTester ( gta, ngt );
+        } else {
+            return null;
+        }
+    }
+
+    /** combined OR glyph tester */
+    private static class CombinedOrGlyphTester implements GlyphTester {
+        private GlyphTester[] gta;
+        private int ngt;
+        CombinedOrGlyphTester ( GlyphTester[] gta, int ngt ) {
+            this.gta = gta;
+            this.ngt = ngt;
+        }
+        /** {@inheritDoc} */
+        public boolean test ( int gi, int flags ) {
+            for ( int i = 0, n = ngt; i < n; i++ ) {
+                GlyphTester gt = gta [ i ];
+                if ( gt != null ) {
+                    if ( gt.test ( gi, flags ) ) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    /** combined AND glyph tester */
+    private static class CombinedAndGlyphTester implements GlyphTester {
+        private GlyphTester[] gta;
+        private int ngt;
+        CombinedAndGlyphTester ( GlyphTester[] gta, int ngt ) {
+            this.gta = gta;
+            this.ngt = ngt;
+        }
+        /** {@inheritDoc} */
+        public boolean test ( int gi, int flags ) {
+            for ( int i = 0, n = ngt; i < n; i++ ) {
+                GlyphTester gt = gta [ i ];
+                if ( gt != null ) {
+                    if ( ! gt.test ( gi, flags ) ) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    /** NOT glyph tester */
+    private static class NotGlyphTester implements GlyphTester {
+        private GlyphTester gt;
+        NotGlyphTester ( GlyphTester gt ) {
+            this.gt = gt;
+        }
+        /** {@inheritDoc} */
+        public boolean test ( int gi, int flags ) {
+            if ( gt != null ) {
+                if ( gt.test ( gi, flags ) ) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitution.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+// CSOFF: LineLengthCheck
+
+/**
+ * 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 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
+     */
+    boolean substitute ( GlyphSubstitutionState ss );
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionState.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,230 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// 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;
+    /** character association predications */
+    private boolean predications;
+
+    /**
+     * 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() );
+        this.predications = gs.getPredications();
+    }
+
+    /**
+     * 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
+     * @param predication a predication value to add to association A if predications enabled
+     */
+    public void putGlyph ( int glyph, GlyphSequence.CharAssociation a, Object predication ) {
+        if ( ! ogb.hasRemaining() ) {
+            ogb = growBuffer ( ogb ); 
+        }
+        ogb.put ( glyph );
+        if ( predications && ( predication != null ) ) {
+            a.setPredication ( feature, predication );
+        }
+        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
+     * @param predication optional predicaion object to be associated with glyphs' associations
+     */
+    public void putGlyphs ( int[] glyphs, GlyphSequence.CharAssociation[] associations, Object predication ) {
+        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 ], predication );
+        }
+    }
+
+    /**
+     * 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 ), null );
+            // consume replaced input glyphs
+            consume ( nog );
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Apply default application semantices; namely, consume one input glyph,
+     * writing that glyph (and its association) to the output glyphs (and associations).
+     */
+    public void applyDefault() {
+        super.applyDefault();
+        int gi = getGlyph();
+        if ( gi != 65535 ) {
+            putGlyph ( gi, getAssociation(), null );
+        }
+    }
+
+    private static IntBuffer growBuffer ( IntBuffer ib ) {
+        int capacity = ib.capacity();
+        int capacityNew = capacity * 2;
+        IntBuffer ibNew = IntBuffer.allocate ( capacityNew );
+        ib.rewind();
+        return ibNew.put ( ib );
+    }
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphSubstitutionSubtable.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import org.apache.fop.complexscripts.util.GlyphSequence;
+import org.apache.fop.complexscripts.util.ScriptContextTester;
+
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>GlyphSubstitutionSubtable</code> implements an abstract base of a glyph substitution subtable,
+ * providing a default implementation of the <code>GlyphSubstitution</code> interface.
+ * @author Glenn Adams
+ */
+public abstract class GlyphSubstitutionSubtable extends GlyphSubtable implements GlyphSubstitution {
+
+    /**
+     * Instantiate a <code>GlyphSubstitutionSubtable</code>.
+     * @param id subtable identifier
+     * @param sequence subtable sequence
+     * @param flags subtable flags
+     * @param format subtable format
+     * @param coverage subtable coverage table
+     */
+    protected GlyphSubstitutionSubtable ( String id, int sequence, int flags, int format, GlyphCoverageTable coverage ) {
+        super ( id, sequence, flags, format, coverage );
+    }
+
+    /** {@inheritDoc} */
+    public int getTableType() {
+        return GlyphTable.GLYPH_TABLE_TYPE_SUBSTITUTION;
+    }
+
+    /** {@inheritDoc} */
+    public String getTypeName() {
+        return GlyphSubstitutionTable.getLookupTypeName ( getType() );
+    }
+
+    /** {@inheritDoc} */
+    public boolean isCompatible ( GlyphSubtable subtable ) {
+        return subtable instanceof GlyphSubstitutionSubtable;
+    }
+
+    /** {@inheritDoc} */
+    public boolean usesReverseScan() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public boolean substitute ( GlyphSubstitutionState ss ) {
+        return false;
+    }
+
+    /**
+     * Apply substitutions using specified state and subtable array. For each position in input sequence,
+     * apply subtables in order until some subtable applies or none remain. If no subtable applied or no
+     * input was consumed for a given position, then apply default action (copy input glyph and advance).
+     * If <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();
+    }
+
+    /**
+     * Apply substitutions.
+     * @param gs input glyph sequence
+     * @param script tag
+     * @param language tag
+     * @param feature tag
+     * @param sta subtable array
+     * @param sct script context tester
+     * @return output glyph sequence
+     */
+    public static final GlyphSequence substitute ( GlyphSequence gs, String script, String language, String feature, GlyphSubstitutionSubtable[] sta, ScriptContextTester sct ) {
+        return substitute ( new GlyphSubstitutionState ( gs, script, language, feature, sct ), sta, -1 );
+    }
+
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: fop-commits-unsubscribe@xmlgraphics.apache.org
For additional commands, e-mail: fop-commits-help@xmlgraphics.apache.org