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 [5/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/bidi/DelimitedTextRange.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/DelimitedTextRange.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,228 @@
+/*
+ * 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.bidi;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.fo.CharIterator;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FObj;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.traits.WritingModeTraits;
+import org.apache.fop.traits.WritingModeTraitsGetter;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+
+/**
+ * The <code>DelimitedTextRange</code> class implements the "delimited text range" as described
+ * by XML-FO 1.1 §5.8, which contains a flattened sequence of characters. Any FO that generates
+ * block areas serves as a delimiter.
+ *
+ * @author Glenn Adams
+ */
+public class DelimitedTextRange {
+    private FONode fn;                              // node that generates this text range
+    private StringBuffer buffer;                    // flattened character sequence of generating FO nodes
+    private List intervals;                         // list of intervals over buffer of generating FO nodes
+    /**
+     * Primary constructor.
+     * @param fn node that generates this text range
+     */
+    public DelimitedTextRange ( FONode fn ) {
+        this.fn = fn;
+        this.buffer = new StringBuffer();
+        this.intervals = new Vector();
+    }
+    /**
+     * Obtain node that generated this text range.
+     * @return node that generated this text range
+     */
+    public FONode getNode() {
+        return fn;
+    }
+    /**
+     * Append interval using characters from character iterator IT.
+     * @param it character iterator
+     * @param fn node that generates interval being appended
+     */
+    public void append ( CharIterator it, FONode fn ) {
+        if ( it != null ) {
+            int s = buffer.length();
+            int e = s;
+            while ( it.hasNext() ) {
+                char c = it.nextChar();
+                buffer.append ( c );
+                e++;
+            }
+            intervals.add ( new TextInterval ( fn, s, e ) );
+        }
+    }
+    /**
+     * Append interval using character C.
+     * @param c character
+     * @param fn node that generates interval being appended
+     */
+    public void append ( char c, FONode fn ) {
+        if ( c != 0 ) {
+            int s = buffer.length();
+            int e = s + 1;
+            buffer.append ( c );
+            intervals.add ( new TextInterval ( fn, s, e ) );
+        }
+    }
+    /**
+     * Determine if range is empty.
+     * @return true if range is empty
+     */
+    public boolean isEmpty() {
+        return buffer.length() == 0;
+    }
+    /**
+     * Resolve bidirectional levels for this range.
+     */
+    public void resolve() {
+        WritingModeTraitsGetter tg;
+        if ( ( tg = WritingModeTraits.getWritingModeTraitsGetter ( getNode() ) ) != null ) {
+            resolve ( tg.getInlineProgressionDirection() );
+        }
+    }
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer ( "DR: " + fn.getLocalName() + " { <" + CharUtilities.toNCRefs ( buffer.toString() ) + ">" );
+        sb.append ( ", intervals <" );
+        boolean first = true;
+        for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+            TextInterval ti = (TextInterval) it.next();
+            if ( first ) {
+                first = false;
+            } else {
+                sb.append(',');
+            }
+            sb.append ( ti.toString() );
+        }
+        sb.append("> }");
+        return sb.toString();
+    }
+    private void resolve ( Direction paragraphEmbeddingLevel ) {
+        int [] levels;
+        if ( ( levels = UnicodeBidiAlgorithm.resolveLevels ( buffer, paragraphEmbeddingLevel ) ) != null ) {
+            assignLevels ( levels );
+            assignBlockLevel ( paragraphEmbeddingLevel );
+            assignTextLevels();
+        }
+    }
+    /**
+     * <p>Assign resolved levels to all text intervals of this delimited text range.</p>
+     * <p>Has a possible side effect of replacing the intervals array with a new array
+     * containing new text intervals, such that each text interval is associated with
+     * a single level run.</p>
+     * @param levels array of levels each corresponding to each index of the delimited
+     * text range
+     */
+    private void assignLevels ( int[] levels ) {
+        Vector intervalsNew = new Vector ( intervals.size() );
+        for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+            TextInterval ti = (TextInterval) it.next();
+            intervalsNew.addAll ( assignLevels ( ti, levels ) );
+        }
+        if ( ! intervalsNew.equals ( intervals ) ) {
+            intervals = intervalsNew;
+        }
+    }
+    /**
+     * <p>Assign resolved levels to a specified text interval over this delimited text
+     * range.</p>
+     * <p>Returns a list of text intervals containing either (1) the single, input text
+     * interval or (2) two or more new text intervals obtained from sub-dividing the input
+     * text range into level runs, i.e., runs of text assigned to a single level.</p>
+     * @param ti a text interval to which levels are to be assigned
+     * @param levels array of levels each corresponding to each index of the delimited
+     * text range
+     * @return a list of text intervals as described above
+     */
+    private static final Log log = LogFactory.getLog(BidiResolver.class); // CSOK: ConstantNameCheck
+    private List assignLevels ( TextInterval ti, int[] levels ) {
+        Vector tiv = new Vector();
+        FONode fn = ti.getNode();
+        int fnStart = ti.getStart();                                     // start of node's text in delimited text range
+        for ( int i = fnStart, n = ti.getEnd(); i < n; ) {
+            int s = i;                                              // inclusive start index of interval in delimited text range
+            int e = s;                                              // exclusive end index of interval in delimited text range
+            int l = levels [ s ];                                   // current run level
+            while ( e < n ) {                                       // skip to end of run level or end of interval
+                if ( levels [ e ] != l ) {
+                    break;
+                } else {
+                    e++;
+                }
+            }
+            if ( ( ti.getStart() == s ) && ( ti.getEnd() == e ) ) {
+                ti.setLevel ( l );                                       // reuse interval, assigning it single level
+            } else {
+                ti = new TextInterval ( fn, fnStart, s, e, l );     // subdivide interval
+            }
+            if (log.isDebugEnabled()) {
+                log.debug ( "AL(" + l + "): " + ti );
+            }
+            tiv.add ( ti );
+            i = e;
+        }
+        return tiv;
+    }
+    /**
+     * <p>Assign resolved levels for each interval to source #PCDATA in the associated FOText.</p>
+     */
+    private void assignTextLevels() {
+        for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+            TextInterval ti = (TextInterval) it.next();
+            ti.assignTextLevels();
+        }
+    }
+    private void assignBlockLevel ( Direction paragraphEmbeddingLevel ) {
+        int defaultLevel = ( paragraphEmbeddingLevel == Direction.RL ) ? 1 : 0;
+        for ( Iterator it = intervals.iterator(); it.hasNext(); ) {
+            TextInterval ti = (TextInterval) it.next();
+            assignBlockLevel ( ti.getNode(), defaultLevel );
+        }
+    }
+    private void assignBlockLevel ( FONode node, int defaultLevel ) {
+        for ( FONode fn = node; fn != null; fn = fn.getParent() ) {
+            if ( fn instanceof FObj ) {
+                FObj fo = (FObj) fn;
+                if ( fo.isBidiRangeBlockItem() ) {
+                    if ( fo.getBidiLevel() < 0 ) {
+                        fo.setBidiLevel ( defaultLevel );
+                    }
+                    break;
+                }
+            }
+        }
+    }
+}
+

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/InlineRun.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.bidi;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Vector;
+
+import org.apache.fop.area.inline.Anchor;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.inline.InlineBlockParent;
+import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.InlineViewport;
+import org.apache.fop.area.inline.Leader;
+import org.apache.fop.area.inline.Space;
+import org.apache.fop.area.inline.SpaceArea;
+import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.area.inline.UnresolvedPageNumber;
+import org.apache.fop.area.inline.WordArea;
+import org.apache.fop.traits.Direction;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>InlineRun</code> class is a utility class, the instances of which are used
+ * to capture a sequence of reordering levels associated with an inline area.
+ *
+ * @author Glenn Adams
+ */
+public class InlineRun {
+    private InlineArea inline;
+    private int[] levels;
+    private int minLevel;
+    private int maxLevel;
+    private int reversals;
+    /**
+     * Primary constructor.
+     * @param inline which generated this inline run
+     * @param levels levels array
+     */
+    public InlineRun ( InlineArea inline, int[] levels ) {
+        assert inline != null;
+        assert levels != null;
+        this.inline = inline;
+        this.levels = levels;
+        setMinMax ( levels );
+    }
+    /**
+     * Alternate constructor.
+     * @param inline which generated this inline run
+     * @param level for each index
+     * @param count of indices
+     */
+    public InlineRun ( InlineArea inline, int level, int count ) {
+        this ( inline, makeLevels ( level, count ) );
+    }
+    /**
+     * Obtain inline area that generated this inline run.
+     * @return inline area that generated this inline run.
+     */
+    public InlineArea getInline() {
+        return inline;
+    }
+    /**
+     * Obtain minimum bidi level for this run.
+     * @return minimum bidi level
+     */
+    public int getMinLevel() {
+        return minLevel;
+    }
+    /**
+     * Obtain maximum bidi level for this run.
+     * @return maximum bidi level
+     */
+    public int getMaxLevel() {
+        return maxLevel;
+    }
+    private void setMinMax ( int[] levels ) {
+        int mn = Integer.MAX_VALUE;
+        int mx = Integer.MIN_VALUE;
+        if ( ( levels != null ) && ( levels.length > 0 ) ) {
+            for ( int i = 0, n = levels.length; i < n; i++ ) {
+                int l = levels [ i ];
+                if ( l < mn ) {
+                    mn = l;
+                }
+                if ( l > mx ) {
+                    mx = l;
+                }
+            }
+        } else {
+            mn = mx = -1;
+        }
+        this.minLevel = mn;
+        this.maxLevel = mx;
+    }
+    /**
+     * Determine if this run has homogenous (same valued) bidi levels.
+     * @return true if homogenous
+     */
+    public boolean isHomogenous() {
+        return minLevel == maxLevel;
+    }
+    /**
+     * Split this inline run into homogenous runs.
+     * @return list of new runs
+     */
+    public List split() {
+        List runs = new Vector();
+        for ( int i = 0, n = levels.length; i < n; ) {
+            int l = levels [ i ];
+            int s = i;
+            int e = s;
+            while ( e < n ) {
+                if ( levels [ e ] != l ) {
+                    break;
+                } else {
+                    e++;
+                }
+            }
+            if ( s < e ) {
+                runs.add ( new InlineRun ( inline, l, e - s ) );
+            }
+            i = e;
+        }
+        assert runs.size() < 2 : "heterogeneous inlines not yet supported!!";
+        return runs;
+    }
+    /**
+     * Update a min/max array to correspond with this run's min/max values.
+     * @param mm reference to min/max array
+     */
+    public void updateMinMax ( int[] mm ) {
+        if ( minLevel < mm[0] ) {
+            mm[0] = minLevel;
+        }
+        if ( maxLevel > mm[1] ) {
+            mm[1] = maxLevel;
+        }
+    }
+    /**
+     * Determine if run needs mirroring.
+     * @return true if run is homogenous and odd (i.e., right to left)
+     */
+    public boolean maybeNeedsMirroring() {
+        return ( minLevel == maxLevel ) && ( ( minLevel & 1 ) != 0 );
+    }
+    /**
+     * Reverse run (by incrementing reversal count, not actually reversing).
+     */
+    public void reverse() {
+        reversals++;
+    }
+    /**
+     * Reverse inline area if it is a word area and it requires
+     * reversal.
+     * @param mirror if true then also mirror characters
+     */
+    public void maybeReverseWord ( boolean mirror ) {
+        if ( inline instanceof WordArea ) {
+            WordArea w = (WordArea) inline;
+            // if not already reversed, then reverse now
+            if ( ! w.isReversed() ) {
+                if ( ( reversals & 1 ) != 0 ) {
+                    w.reverse ( mirror );
+                } else if ( mirror && maybeNeedsMirroring() ) {
+                    w.mirror();
+                }
+            }
+        }
+    }
+    @Override
+    public boolean equals ( Object o ) {
+        if ( o instanceof InlineRun ) {
+            InlineRun ir = (InlineRun) o;
+            if ( ir.inline != inline ) {
+                return false;
+            } else if ( ir.minLevel != minLevel ) {
+                return false;
+            } else if ( ir.maxLevel != maxLevel ) {
+                return false;
+            } else if ( ( ir.levels != null ) && ( levels != null ) ) {
+                if ( ir.levels.length != levels.length ) {
+                    return false;
+                } else {
+                    for ( int i = 0, n = levels.length; i < n; i++ ) {
+                        if ( ir.levels[i] != levels[i] ) {
+                            return false;
+                        }
+                    }
+                    return true;
+                }
+            } else if ( ( ir.levels == null ) && ( levels == null ) ) {
+                return true;
+            } else {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+    @Override
+    public int hashCode() {
+        int l = ( inline != null ) ? inline.hashCode() : 0;
+        l = ( l ^ minLevel ) + ( l << 19 );
+        l = ( l ^ maxLevel )   + ( l << 11 );
+        return l;
+    }
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer( "RR: { type = \'" );
+        char c;
+        String content = null;
+        if ( inline instanceof WordArea ) {
+            c = 'W';
+            content = ( (WordArea) inline ) .getWord();
+        } else if ( inline instanceof SpaceArea ) {
+            c = 'S';
+            content = ( (SpaceArea) inline ) .getSpace();
+        } else if ( inline instanceof Anchor ) {
+            c = 'A';
+        } else if ( inline instanceof Leader ) {
+            c = 'L';
+        } else if ( inline instanceof Space ) {
+            c = 'S';
+        } else if ( inline instanceof UnresolvedPageNumber ) {
+            c = '#';
+            content = ( (UnresolvedPageNumber) inline ) .getText();
+        } else if ( inline instanceof InlineBlockParent ) {
+            c = 'B';
+        } else if ( inline instanceof InlineViewport ) {
+            c = 'V';
+        } else if ( inline instanceof InlineParent ) {
+            c = 'I';
+        } else {
+            c = '?';
+        }
+        sb.append ( c );
+        sb.append ( "\', levels = \'" );
+        sb.append ( generateLevels ( levels ) );
+        sb.append ( "\', min = " );
+        sb.append ( minLevel );
+        sb.append ( ", max = " );
+        sb.append ( maxLevel );
+        sb.append ( ", reversals = " );
+        sb.append ( reversals );
+        sb.append ( ", content = <" );
+        sb.append ( CharUtilities.toNCRefs ( content ) );
+        sb.append ( "> }" );
+        return sb.toString();
+    }
+    private String generateLevels ( int[] levels ) {
+        StringBuffer lb = new StringBuffer();
+        int maxLevel = -1;
+        int numLevels = levels.length;
+        for ( int i = 0; i < numLevels; i++ ) {
+            int l = levels [ i ];
+            if ( l > maxLevel ) {
+                maxLevel = l;
+            }
+        }
+        if ( maxLevel < 0 ) {
+            // leave level buffer empty
+        } else if ( maxLevel < 10 ) {
+            // use string of decimal digits
+            for ( int i = 0; i < numLevels; i++ ) {
+                lb.append ( (char) ( '0' + levels [ i ] ) );
+            }
+        } else {
+            // use comma separated list
+            boolean first = true;
+            for ( int i = 0; i < numLevels; i++ ) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    lb.append(',');
+                }
+                lb.append ( levels [ i ] );
+            }
+        }
+        return lb.toString();
+    }
+    private static int[] makeLevels ( int level, int count ) {
+        int[] levels = new int [ count ];
+        Arrays.fill ( levels, level );
+        return levels;
+    }
+}
+

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/TextInterval.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,143 @@
+/*
+ * 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.bidi;
+
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.flow.AbstractPageNumberCitation;
+import org.apache.fop.fo.flow.AbstractGraphics;
+import org.apache.fop.fo.flow.BidiOverride;
+import org.apache.fop.fo.flow.Character;
+import org.apache.fop.fo.flow.Leader;
+
+// CSOFF: LineLengthCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>TextInterval</code> class is a utility class, the instances of which are used
+ * to record backpointers to associated nodes over sub-intervals of a delimited text range.
+ *
+ * @author Glenn Adams
+ */
+class TextInterval {
+    private FONode fn;              // associated node
+    private int textStart;          // starting index within delimited text range of associated node's text
+    private int start;              // starting index within delimited text range
+    private int end;                // ending index within delimited text range
+    private int level;              // resolved level or default (-1)
+    TextInterval ( FONode fn, int start, int end ) {
+        this ( fn, start, start, end, -1 );
+    }
+    TextInterval ( FONode fn, int textStart, int start, int end, int level ) {
+        this.fn = fn;
+        this.textStart = textStart;
+        this.start = start;
+        this.end = end;
+        this.level = level;
+    }
+    FONode getNode() {
+        return fn;
+    }
+    int getTextStart() {
+        return textStart;
+    }
+    int getStart() {
+        return start;
+    }
+    int getEnd() {
+        return end;
+    }
+    int getLevel() {
+        return level;
+    }
+    void setLevel ( int level ) {
+        this.level = level;
+    }
+    public int length() {
+        return end - start;
+    }
+    public String getText() {
+        if ( fn instanceof FOText ) {
+            return ( (FOText) fn ) .getCharSequence() .toString();
+        } else if ( fn instanceof Character ) {
+            return new String ( new char[] {( (Character) fn ) .getCharacter()} );
+        } else {
+            return null;
+        }
+    }
+    public void assignTextLevels() {
+        if ( fn instanceof FOText ) {
+            ( (FOText) fn ) .setBidiLevel ( level, start - textStart, end - textStart );
+        } else if ( fn instanceof Character ) {
+            ( (Character) fn ) .setBidiLevel ( level );
+        } else if ( fn instanceof AbstractPageNumberCitation ) {
+            ( (AbstractPageNumberCitation) fn ) .setBidiLevel ( level );
+        } else if ( fn instanceof AbstractGraphics ) {
+            ( (AbstractGraphics) fn ) .setBidiLevel ( level );
+        } else if ( fn instanceof Leader ) {
+            ( (Leader) fn ) .setBidiLevel ( level );
+        }
+    }
+    public boolean equals ( Object o ) {
+        if ( o instanceof TextInterval ) {
+            TextInterval ti = (TextInterval) o;
+            if ( ti.getNode() != fn ) {
+                return false;
+            } else if ( ti.getStart() != start ) {
+                return false;
+            } else if ( ti.getEnd() != end ) {
+                return false;
+            } else {
+                return true;
+            }
+        } else {
+            return false;
+        }
+    }
+    public int hashCode() {
+        int l = ( fn != null ) ? fn.hashCode() : 0;
+        l = ( l ^ start ) + ( l << 19 );
+        l = ( l ^ end )   + ( l << 11 );
+        return l;
+    }
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        char c;
+        if ( fn instanceof FOText ) {
+            c = 'T';
+        } else if ( fn instanceof Character ) {
+            c = 'C';
+        } else if ( fn instanceof BidiOverride ) {
+            c = 'B';
+        } else if ( fn instanceof AbstractPageNumberCitation ) {
+            c = '#';
+        } else if ( fn instanceof AbstractGraphics ) {
+            c = 'G';
+        } else if ( fn instanceof Leader ) {
+            c = 'L';
+        } else {
+            c = '?';
+        }
+        sb.append ( c );
+        sb.append ( "[" + start + "," + end + "][" + textStart + "](" + level + ")" );
+        return sb.toString();
+    }
+}
+

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnflattenProcessor.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,361 @@
+/*
+ * 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.bidi;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.fop.area.Area;
+import org.apache.fop.area.LinkResolver;
+import org.apache.fop.area.inline.BasicLinkArea;
+import org.apache.fop.area.inline.FilledArea;
+import org.apache.fop.area.inline.InlineArea;
+import org.apache.fop.area.inline.InlineParent;
+import org.apache.fop.area.inline.SpaceArea;
+import org.apache.fop.area.inline.TextArea;
+import org.apache.fop.area.inline.UnresolvedPageNumber;
+
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: SimplifyBooleanReturnCheck
+
+/**
+ * The <code>UnflattenProcessor</code> class is used to reconstruct (by unflattening) a line
+ * area's internal area hierarachy after leaf inline area reordering is completed.
+ *
+ * @author Glenn Adams
+ */
+class UnflattenProcessor {
+    private List<InlineArea>        il;             // list of flattened inline areas being unflattened
+    private List<InlineArea>        ilNew;          // list of unflattened inline areas being constructed
+    private int                     iaLevelLast;    // last (previous) level of current inline area (if applicable) or -1
+    private TextArea                tcOrig;         // original text area container
+    private TextArea                tcNew;          // new text area container being constructed
+    private Stack<InlineParent>     icOrig;         // stack of original inline parent containers
+    private Stack<InlineParent>     icNew;          // stack of new inline parent containers being constructed
+    UnflattenProcessor ( List<InlineArea> inlines ) {
+        this.il = inlines;
+        this.ilNew = new ArrayList<InlineArea>();
+        this.iaLevelLast = -1;
+        this.icOrig = new Stack<InlineParent>();
+        this.icNew = new Stack<InlineParent>();
+    }
+    List unflatten() {
+        if ( il != null ) {
+            for ( Iterator<InlineArea> it = il.iterator(); it.hasNext(); ) {
+                process ( it.next() );
+            }
+        }
+        finishAll();
+        return ilNew;
+    }
+    private void process ( InlineArea ia ) {
+        process ( findInlineContainers ( ia ), findTextContainer ( ia ), ia );
+    }
+    private void process ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+        if ( ( tcNew == null ) || ( tc != tcNew ) ) {
+            maybeFinishTextContainer ( tc, ia );
+            maybeFinishInlineContainers ( ich, tc, ia );
+            update ( ich, tc, ia );
+        } else {
+            // skip inline area whose text container is the current new text container,
+            // which occurs in the context of the inline runs produced by a filled area
+        }
+    }
+    private boolean shouldFinishTextContainer ( TextArea tc, InlineArea ia ) {
+        if ( ( tcOrig != null ) && ( tc != tcOrig ) ) {
+            return true;
+        } else if ( ( iaLevelLast != -1 ) && ( ia.getBidiLevel() != iaLevelLast ) ) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+    private void finishTextContainer() {
+        finishTextContainer ( null, null );
+    }
+    private void finishTextContainer ( TextArea tc, InlineArea ia ) {
+        if ( tcNew != null ) {
+            updateIPD ( tcNew );
+            if ( ! icNew.empty() ) {
+                icNew.peek().addChildArea ( tcNew );
+            } else {
+                ilNew.add ( tcNew );
+            }
+        }
+        tcNew = null;
+    }
+    private void maybeFinishTextContainer ( TextArea tc, InlineArea ia ) {
+        if ( shouldFinishTextContainer ( tc, ia ) ) {
+            finishTextContainer ( tc, ia );
+        }
+    }
+    private boolean shouldFinishInlineContainer ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+        if ( ( ich == null ) || ich.isEmpty() ) {
+            return ! icOrig.empty();
+        } else {
+            if ( ! icOrig.empty() ) {
+                InlineParent ic  = ich.get(0);
+                InlineParent ic0 = icOrig.peek();
+                return ( ic != ic0 ) && ! isInlineParentOf ( ic, ic0 );
+            } else {
+                return false;
+            }
+        }
+    }
+    private void finishInlineContainer() {
+        finishInlineContainer ( null, null, null );
+    }
+    private void finishInlineContainer ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+        if ( ( ich != null ) && ! ich.isEmpty() ) {     // finish non-matching inner inline container(s)
+            for ( Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) {
+                InlineParent ic  = it.next();
+                InlineParent ic0 = icOrig.empty() ? null : icOrig.peek();
+                if ( ic0 == null ) {
+                    assert icNew.empty();
+                } else if ( ic != ic0 ) {
+                    assert ! icNew.empty();
+                    InlineParent icO0 = icOrig.pop();
+                    InlineParent icN0 = icNew.pop();
+                    assert icO0 != null;
+                    assert icN0 != null;
+                    if ( icNew.empty() ) {
+                        ilNew.add ( icN0 );
+                    } else {
+                        icNew.peek().addChildArea ( icN0 );
+                    }
+                    if ( ! icOrig.empty() && ( icOrig.peek() == ic ) ) {
+                        break;
+                    }
+                } else {
+                    break;
+                }
+            }
+        } else {                                        // finish all inline containers
+            while ( ! icNew.empty() ) {
+                InlineParent icO0 = icOrig.pop();
+                InlineParent icN0 = icNew.pop();
+                assert icO0 != null;
+                assert icN0 != null;
+                if ( icNew.empty() ) {
+                    ilNew.add ( icN0 );
+                } else {
+                    icNew.peek().addChildArea ( icN0 );
+                }
+            }
+        }
+    }
+    private void maybeFinishInlineContainers ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+        if ( shouldFinishInlineContainer ( ich, tc, ia ) ) {
+            finishInlineContainer ( ich, tc, ia );
+        }
+    }
+    private void finishAll() {
+        finishTextContainer();
+        finishInlineContainer();
+    }
+    private void update ( List<InlineParent> ich, TextArea tc, InlineArea ia ) {
+        if ( ! alreadyUnflattened ( ia ) ) {
+            if ( ( ich != null ) && ! ich.isEmpty() ) {
+                pushInlineContainers ( ich );
+            }
+            if ( tc != null ) {
+                pushTextContainer ( tc, ia );
+            } else {
+                pushNonTextInline ( ia );
+            }
+            iaLevelLast = ia.getBidiLevel();
+            tcOrig = tc;
+        } else  if ( tcNew != null ) {
+            finishTextContainer();
+            tcOrig = null;
+        } else {
+            tcOrig = null;
+        }
+    }
+    private boolean alreadyUnflattened ( InlineArea ia ) {
+        for ( Iterator<InlineArea> it = ilNew.iterator(); it.hasNext(); ) {
+            if ( ia.isAncestorOrSelf ( it.next() ) ) {
+                return true;
+            }
+        }
+        return false;
+    }
+    private void pushInlineContainers ( List<InlineParent> ich ) {
+        LinkedList<InlineParent> icl = new LinkedList<InlineParent>();
+        for ( Iterator<InlineParent> it = ich.iterator(); it.hasNext(); ) {
+            InlineParent ic = it.next();
+            if ( icOrig.search ( ic ) >= 0 ) {
+                break;
+            } else {
+                icl.addFirst ( ic );
+            }
+        }
+        for ( Iterator<InlineParent> it = icl.iterator(); it.hasNext(); ) {
+            InlineParent ic = it.next();
+            icOrig.push ( ic );
+            icNew.push ( generateInlineContainer ( ic ) );
+        }
+    }
+    private void pushTextContainer ( TextArea tc, InlineArea ia ) {
+        if ( tc instanceof UnresolvedPageNumber ) {
+            tcNew = tc;
+        } else {
+            if ( tcNew == null ) {
+                tcNew = generateTextContainer ( tc );
+            }
+            tcNew.addChildArea ( ia );
+        }
+    }
+    private void pushNonTextInline ( InlineArea ia ) {
+        if ( icNew.empty() ) {
+            ilNew.add ( ia );
+        } else {
+            icNew.peek().addChildArea ( ia );
+        }
+    }
+    private InlineParent generateInlineContainer ( InlineParent i ) {
+        if ( i instanceof BasicLinkArea ) {
+            return generateBasicLinkArea ( (BasicLinkArea) i );
+        } else if ( i instanceof FilledArea ) {
+            return generateFilledArea ( (FilledArea) i );
+        } else {
+            return generateInlineContainer0 ( i );
+        }
+    }
+    private InlineParent generateBasicLinkArea ( BasicLinkArea l ) {
+        BasicLinkArea lc = new BasicLinkArea();
+        if ( l != null ) {
+            initializeInlineContainer ( lc, l );
+            initializeLinkArea ( lc, l );
+        }
+        return lc;
+    }
+    private void initializeLinkArea ( BasicLinkArea lc, BasicLinkArea l ) {
+        assert lc != null;
+        assert l != null;
+        LinkResolver r = l.getResolver();
+        if ( r != null ) {
+            String[] idrefs = r.getIDRefs();
+            if ( idrefs.length > 0 ) {
+                String idref = idrefs[0];
+                LinkResolver lr = new LinkResolver ( idref, lc );
+                lc.setResolver ( lr );
+                r.addDependent ( lr );
+            }
+        }
+    }
+    private InlineParent generateFilledArea ( FilledArea f ) {
+        FilledArea fc = new FilledArea();
+        if ( f != null ) {
+            initializeInlineContainer ( fc, f );
+            initializeFilledArea ( fc, f );
+        }
+        return fc;
+    }
+    private void initializeFilledArea ( FilledArea fc, FilledArea f ) {
+        assert fc != null;
+        assert f != null;
+        fc.setIPD ( f.getIPD() );
+        fc.setUnitWidth ( f.getUnitWidth() );
+    }
+    private InlineParent generateInlineContainer0 ( InlineParent i ) {
+        InlineParent ic = new InlineParent();
+        if ( i != null ) {
+            initializeInlineContainer ( ic, i );
+        }
+        return ic;
+    }
+    private void initializeInlineContainer ( InlineParent ic, InlineParent i ) {
+        assert ic != null;
+        assert i != null;
+        ic.setTraits ( i.getTraits() );
+        ic.setBPD ( i.getBPD() );
+        ic.setBlockProgressionOffset ( i.getBlockProgressionOffset() );
+    }
+    private TextArea generateTextContainer ( TextArea t ) {
+        TextArea tc = new TextArea();
+        if ( t != null ) {
+            tc.setTraits ( t.getTraits() );
+            tc.setBPD ( t.getBPD() );
+            tc.setBlockProgressionOffset ( t.getBlockProgressionOffset() );
+            tc.setBaselineOffset ( t.getBaselineOffset() );
+            tc.setTextWordSpaceAdjust ( t.getTextWordSpaceAdjust() );
+            tc.setTextLetterSpaceAdjust ( t.getTextLetterSpaceAdjust() );
+        }
+        return tc;
+    }
+    private void updateIPD ( TextArea tc ) {
+        int numAdjustable = 0;
+        for ( Iterator it = tc.getChildAreas().iterator(); it.hasNext(); ) {
+            InlineArea ia = (InlineArea) it.next();
+            if ( ia instanceof SpaceArea ) {
+                SpaceArea sa = (SpaceArea) ia;
+                if ( sa.isAdjustable() ) {
+                    numAdjustable++;
+                }
+            }
+        }
+        if ( numAdjustable > 0 ) {
+            tc.setIPD ( tc.getIPD() + ( numAdjustable * tc.getTextWordSpaceAdjust() ) );
+        }
+    }
+    private TextArea findTextContainer ( InlineArea ia ) {
+        assert ia != null;
+        TextArea t = null;
+        while ( t == null ) {
+            if ( ia instanceof TextArea ) {
+                t = (TextArea) ia;
+            } else {
+                Area p = ia.getParentArea();
+                if ( p instanceof InlineArea ) {
+                    ia = (InlineArea) p;
+                } else {
+                    break;
+                }
+            }
+        }
+        return t;
+    }
+    private List<InlineParent> findInlineContainers ( InlineArea ia ) {
+        assert ia != null;
+        List<InlineParent> ich = new ArrayList<InlineParent>();
+        Area a = ia.getParentArea();
+        while ( a != null ) {
+            if ( a instanceof InlineArea ) {
+                if ( ( a instanceof InlineParent ) && ! ( a instanceof TextArea ) ) {
+                    ich.add ( (InlineParent) a );
+                }
+                a = ( (InlineArea) a ) .getParentArea();
+            } else {
+                a = null;
+            }
+        }
+        return ich;
+    }
+    private boolean isInlineParentOf ( InlineParent ic0, InlineParent ic1 ) {
+        assert ic0 != null;
+        return ic0.getParentArea() == ic1;
+    }
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/bidi/UnicodeBidiAlgorithm.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,839 @@
+/*
+ * 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.bidi;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.fop.traits.Direction;
+import org.apache.fop.util.CharUtilities;
+
+// CSOFF: AvoidNestedBlocksCheck
+// CSOFF: EmptyForIteratorPadCheck
+// CSOFF: InnerAssignmentCheck
+// CSOFF: LineLengthCheck
+// CSOFF: NoWhitespaceAfterCheck
+// CSOFF: ParameterNumberCheck
+
+/**
+ * The <code>UnicodeBidiAlgorithm</code> class implements functionality prescribed by
+ * the Unicode Bidirectional Algorithm, Unicode Standard Annex #9.
+ *
+ * @author Glenn Adams
+ */
+public final class UnicodeBidiAlgorithm implements BidiConstants {
+
+    /**
+     * logging instance
+     */
+    private static final Log log = LogFactory.getLog(BidiResolver.class);                                                   // CSOK: ConstantNameCheck
+
+    private UnicodeBidiAlgorithm() {
+    }
+
+    /**
+     * Resolve the directionality levels of each character in a character seqeunce.
+     * If some character is encoded in the character sequence as a Unicode Surrogate Pair,
+     * then the directionality level of each of the two members of the  pair will be identical.
+     * @return null if bidirectional processing is not required; otherwise, returns an array
+     * of integers, where each integer corresponds to exactly one UTF-16
+     * encoding element present in the input character sequence, and where each integer denotes
+     * the directionality level of the corresponding encoding element
+     * @param cs input character sequence representing a UTF-16 encoded string
+     * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+     */
+    public static int[] resolveLevels ( CharSequence cs, Direction defaultLevel ) {
+        int[] chars = new int [ cs.length() ];
+        if ( convertToScalar ( cs, chars ) || ( defaultLevel == Direction.RL ) ) {
+            return resolveLevels ( chars, ( defaultLevel == Direction.RL ) ? 1 : 0, new int [ chars.length ] );
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Resolve the directionality levels of each character in a character seqeunce.
+     * @return null if bidirectional processing is not required; otherwise, returns an array
+     * of integers, where each integer corresponds to exactly one UTF-16
+     * encoding element present in the input character sequence, and where each integer denotes
+     * the directionality level of the corresponding encoding element
+     * @param chars array of input characters represented as unicode scalar values
+     * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+     * @param levels array to receive levels, one for each character in chars array
+     */
+    public static int[] resolveLevels ( int[] chars, int defaultLevel, int[] levels ) {
+        return resolveLevels ( chars, getClasses ( chars ), defaultLevel, levels, false );
+    }
+
+    /**
+     * Resolve the directionality levels of each character in a character seqeunce.
+     * @return null if bidirectional processing is not required; otherwise, returns an array
+     * of integers, where each integer corresponds to exactly one UTF-16
+     * encoding element present in the input character sequence, and where each integer denotes
+     * the directionality level of the corresponding encoding element
+     * @param chars array of input characters represented as unicode scalar values
+     * @param classes array containing one bidi class per character in chars array
+     * @param defaultLevel the default paragraph level, which must be zero (LR) or one (RL)
+     * @param levels array to receive levels, one for each character in chars array
+     * @param useRuleL1 true if rule L1 should be used
+     */
+    public static int[] resolveLevels ( int[] chars, int[] classes, int defaultLevel, int[] levels, boolean useRuleL1 ) {
+        int[] ica = classes;
+        int[] wca = copySequence ( ica );
+        int[] ea  = new int [ levels.length ];
+        resolveExplicit ( wca, defaultLevel, ea );
+        resolveRuns ( wca, defaultLevel, ea, levelsFromEmbeddings ( ea, levels ) );
+        if ( useRuleL1 ) {
+            resolveSeparators ( ica, wca, defaultLevel, levels );
+        }
+        dump ( "RL: CC(" + ( ( chars != null ) ? chars.length : -1 ) + ")", chars, classes, defaultLevel, levels );
+        return levels;
+    }
+
+    private static int[] copySequence ( int[] ta ) {
+        int[] na = new int [ ta.length ];
+        System.arraycopy ( ta, 0, na, 0, na.length );
+        return na;
+    }
+
+    private static void resolveExplicit ( int[] wca, int defaultLevel, int[] ea ) {
+        int[] es = new int [ MAX_LEVELS ];          /* embeddings stack */
+        int ei = 0;                                 /* embeddings stack index */
+        int ec = defaultLevel;                      /* current embedding level */
+        for ( int i = 0, n = wca.length; i < n; i++ ) {
+            int bc = wca [ i ];                     /* bidi class of current char */
+            int el;                                 /* embedding level to assign to current char */
+            switch ( bc ) {
+            case LRE:                               // start left-to-right embedding
+            case RLE:                               // start right-to-left embedding
+            case LRO:                               // start left-to-right override
+            case RLO:                               // start right-to-left override
+                {
+                    int en;                         /* new embedding level */
+                    if ( ( bc == RLE ) || ( bc == RLO ) ) {
+                        en = ( ( ec & ~OVERRIDE ) + 1 ) | 1;
+                    } else {
+                        en = ( ( ec & ~OVERRIDE ) + 2 ) & ~1;
+                    }
+                    if ( en < ( MAX_LEVELS + 1 ) ) {
+                        es [ ei++ ] = ec;
+                        if ( ( bc == LRO ) || ( bc == RLO ) ) {
+                            ec = en | OVERRIDE;
+                        } else {
+                            ec = en & ~OVERRIDE;
+                        }
+                    } else {
+                        // max levels exceeded, so don't change level or override
+                    }
+                    el = ec;
+                    break;
+                }
+            case PDF:                               // pop directional formatting
+                {
+                    el = ec;
+                    if ( ei > 0 ) {
+                        ec = es [ --ei ];
+                    } else {
+                        // ignore isolated PDF
+                    }
+                    break;
+                }
+            case B:                                 // paragraph separator
+                {
+                    el = ec = defaultLevel;
+                    ei = 0;
+                    break;
+                }
+            default:
+                {
+                    el = ec;
+                    break;
+                }
+            }
+            switch ( bc ) {
+            case BN:
+                break;
+            case LRE: case RLE: case LRO: case RLO: case PDF:
+                wca [ i ] = BN;
+                break;
+            default:
+                if ( ( el & OVERRIDE ) != 0 ) {
+                    wca [ i ] = directionOfLevel ( el );
+                }
+                break;
+            }
+            ea [ i ] = el;
+        }
+    }
+
+    private static int directionOfLevel ( int level ) {
+        return ( ( level & 1 ) != 0 ) ? R : L;
+    }
+
+    private static int levelOfEmbedding ( int embedding ) {
+        return embedding & ~OVERRIDE;
+    }
+
+    private static int[] levelsFromEmbeddings ( int[] ea, int[] la ) {
+        assert ea != null;
+        assert la != null;
+        assert la.length == ea.length;
+        for ( int i = 0, n = la.length; i < n; i++ ) {
+            la [ i ] = levelOfEmbedding ( ea [ i ] );
+        }
+        return la;
+    }
+
+    private static void resolveRuns ( int[] wca, int defaultLevel, int[] ea, int[] la ) {
+        if ( la.length != wca.length ) {
+            throw new IllegalArgumentException ( "levels sequence length must match classes sequence length" );
+        } else if ( la.length != ea.length ) {
+            throw new IllegalArgumentException ( "levels sequence length must match embeddings sequence length" );
+        } else {
+            for ( int i = 0, n = ea.length, lPrev = defaultLevel; i < n; ) {
+                int s = i;
+                int e = s;
+                int l = findNextNonRetainedFormattingLevel ( wca, ea, s, lPrev );
+                while ( e < n ) {
+                    if ( la [ e ] != l ) {
+                        if ( startsWithRetainedFormattingRun ( wca, ea, e ) ) {
+                            e += getLevelRunLength ( ea, e );
+                        } else {
+                            break;
+                        }
+                    } else {
+                        e++;
+                    }
+                }
+                lPrev = resolveRun ( wca, defaultLevel, ea, la, s, e, l, lPrev );
+                i = e;
+            }
+        }
+    }
+
+    private static int findNextNonRetainedFormattingLevel ( int[] wca, int[] ea, int start, int lPrev ) {
+        int s = start;
+        int e = wca.length;
+        while ( s < e ) {
+            if ( startsWithRetainedFormattingRun ( wca, ea, s ) ) {
+                s += getLevelRunLength ( ea, s );
+            } else {
+                break;
+            }
+        }
+        if ( s < e ) {
+            return levelOfEmbedding ( ea [ s ] );
+        } else {
+            return lPrev;
+        }
+    }
+
+    private static int getLevelRunLength ( int[] ea, int start ) {
+        assert start < ea.length;
+        int nl = 0;
+        for ( int s = start, e = ea.length, l0 = levelOfEmbedding ( ea [ start ] ); s < e; s++ ) {
+            if ( levelOfEmbedding ( ea [ s ] ) == l0 ) {
+                nl++;
+            } else {
+                break;
+            }
+        }
+        return nl;
+    }
+
+    private static boolean startsWithRetainedFormattingRun ( int[] wca, int[] ea, int start ) {
+        int nl = getLevelRunLength ( ea, start );
+        if ( nl > 0 ) {
+            int nc = getRetainedFormattingRunLength ( wca, start );
+            return ( nc >= nl );
+        } else {
+            return false;
+        }
+    }
+
+    private static int getRetainedFormattingRunLength ( int[] wca, int start ) {
+        assert start < wca.length;
+        int nc = 0;
+        for ( int s = start, e = wca.length; s < e; s++ ) {
+            if ( wca [ s ] == BidiConstants.BN ) {
+                nc++;
+            } else {
+                break;
+            }
+        }
+        return nc;
+    }
+
+    private static int resolveRun ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int levelPrev ) {
+
+        // determine start of run direction
+        int sor = directionOfLevel ( max ( levelPrev, level ) );
+
+        // determine end of run direction
+        int le = -1;
+        if ( end == wca.length ) {
+            le = max ( level, defaultLevel );
+        } else {
+            for ( int i = end; i < wca.length; i++ ) {
+                if ( wca [ i ] != BidiConstants.BN ) {
+                    le = max ( level, la [ i ] );
+                    break;
+                }
+            }
+            if ( le < 0 ) {
+                le = max ( level, defaultLevel );
+            }
+        }
+        int eor = directionOfLevel ( le );
+
+        if (log.isDebugEnabled()) {
+            log.debug ( "BR[" + padLeft ( start, 3 ) + "," + padLeft ( end, 3 ) + "] :" + padLeft ( level, 2 ) + ": SOR(" + getClassName(sor) + "), EOR(" + getClassName(eor) + ")" );
+        }
+
+        resolveWeak ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+        resolveNeutrals ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+        resolveImplicit ( wca, defaultLevel, ea, la, start, end, level, sor, eor );
+
+        // if this run is all retained formatting, then return prior level, otherwise this run's level
+        return isRetainedFormatting ( wca, start, end ) ? levelPrev : level;
+    }
+
+    private static void resolveWeak ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+
+        // W1 - X BN* NSM -> X BN* X
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == NSM ) {
+                wca [ i ] = bcPrev;
+            } else if ( bc != BN ) {
+                bcPrev = bc;
+            }
+        }
+
+        // W2 - AL ... EN -> AL ... AN
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == EN ) {
+                if ( bcPrev == AL ) {
+                    wca [ i ] = AN;
+                }
+            } else if ( isStrong ( bc ) ) {
+                bcPrev = bc;
+            }
+        }
+
+        // W3 - AL -> R
+        for ( int i = start, n = end; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == AL ) {
+                wca [ i ] = R;
+            }
+        }
+
+        // W4 - EN BN* ES BN* EN -> EN BN* EN BN* EN; XN BN* CS BN* XN -> XN BN* XN BN* XN
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == ES ) {
+                int bcNext = eor;
+                for ( int j = i + 1; j < n; j++ ) {
+                    if ( ( bc = wca [ j ] ) != BN ) {
+                        bcNext = bc;
+                        break;
+                    }
+                }
+                if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+                    wca [ i ] = EN;
+                }
+            } else if ( bc == CS ) {
+                int bcNext = eor;
+                for ( int j = i + 1; j < n; j++ ) {
+                    if ( ( bc = wca [ j ] ) != BN ) {
+                        bcNext = bc;
+                        break;
+                    }
+                }
+                if ( ( bcPrev == EN ) && ( bcNext == EN ) ) {
+                    wca [ i ] = EN;
+                } else if ( ( bcPrev == AN ) && ( bcNext == AN ) ) {
+                    wca [ i ] = AN;
+                }
+            }
+            if ( bc != BN ) {
+                bcPrev = bc;
+            }
+        }
+
+        // W5 - EN (ET|BN)* -> EN (EN|BN)*; (ET|BN)* EN -> (EN|BN)* EN
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == ET ) {
+                int bcNext = eor;
+                for ( int j = i + 1; j < n; j++ ) {
+                    bc = wca [ j ];
+                    if ( ( bc != BN ) && ( bc != ET ) ) {
+                        bcNext = bc;
+                        break;
+                    }
+                }
+                if ( ( bcPrev == EN ) || ( bcNext == EN ) ) {
+                    wca [ i ] = EN;
+                }
+            } else if ( ( bc != BN ) && ( bc != ET ) ) {
+                bcPrev = bc;
+            }
+        }
+
+        // W6 - BN* (ET|ES|CS) BN* -> ON* ON ON*
+        for ( int i = start, n = end; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( ( bc == ET ) || ( bc == ES ) || ( bc == CS ) ) {
+                wca [ i ] = ON;
+                resolveAdjacentBoundaryNeutrals ( wca, start, end, i, ON );
+            }
+        }
+
+        // W7 - L ... EN -> L ... L
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( bc == EN ) {
+                if ( bcPrev == L ) {
+                    wca [ i ] = L;
+                }
+            } else if ( ( bc == L ) || ( bc == R ) ) {
+                bcPrev = bc;
+            }
+        }
+
+    }
+
+    private static void resolveNeutrals ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+
+        // N1 - (L|R) N+ (L|R) -> L L+ L | R R+ R; (AN|EN) N+ R -> (AN|EN) R+ R; R N+ (AN|EN) -> R R+ (AN|EN)
+        for ( int i = start, n = end, bcPrev = sor; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( isNeutral ( bc ) ) {
+                int bcNext = eor;
+                for ( int j = i + 1; j < n; j++ ) {
+                    bc = wca [ j ];
+                    if ( ( bc == L ) || ( bc == R ) ) {
+                        bcNext = bc;
+                        break;
+                    } else if ( ( bc == AN ) || ( bc == EN ) ) {
+                        bcNext = R;
+                        break;
+                    } else if ( isNeutral ( bc ) ) {
+                        continue;
+                    } else if ( isRetainedFormatting ( bc ) ) {
+                        continue;
+                    } else {
+                        break;
+                    }
+                }
+                if ( bcPrev == bcNext ) {
+                    wca [ i ] = bcPrev;
+                    resolveAdjacentBoundaryNeutrals ( wca, start, end, i, bcPrev );
+                }
+            } else if ( ( bc == L ) || ( bc == R ) ) {
+                bcPrev = bc;
+            } else if ( ( bc == AN ) || ( bc == EN ) ) {
+                bcPrev = R;
+            }
+        }
+
+        // N2 - N -> embedding level
+        for ( int i = start, n = end; i < n; i++ ) {
+            int bc = wca [ i ];
+            if ( isNeutral ( bc ) ) {
+                int bcEmbedding = directionOfLevel ( levelOfEmbedding ( ea [ i ] ) );
+                wca [ i ] = bcEmbedding;
+                resolveAdjacentBoundaryNeutrals ( wca, start, end, i, bcEmbedding );
+            }
+        }
+
+    }
+
+    private static void resolveAdjacentBoundaryNeutrals ( int[] wca, int start, int end, int index, int bcNew ) {
+        if ( ( index < start ) || ( index >= end ) ) {
+            throw new IllegalArgumentException();
+        } else {
+            for ( int i = index - 1; i >= start; i-- ) {
+                int bc = wca [ i ];
+                if ( bc == BN ) {
+                    wca [ i ] = bcNew;
+                } else {
+                    break;
+                }
+            }
+            for ( int i = index + 1; i < end; i++ ) {
+                int bc = wca [ i ];
+                if ( bc == BN ) {
+                    wca [ i ] = bcNew;
+                } else {
+                    break;
+                }
+            }
+        }
+    }
+
+    private static void resolveImplicit ( int[] wca, int defaultLevel, int[] ea, int[] la, int start, int end, int level, int sor, int eor ) {
+        for ( int i = start, n = end; i < n; i++ ) {
+            int bc = wca [ i ];                     // bidi class
+            int el = la [ i ];                      // embedding level
+            int ed = 0;                             // embedding level delta
+            if ( ( el & 1 ) == 0 ) {                // even
+                if ( bc == R ) {
+                    ed = 1;
+                } else if ( bc == AN ) {
+                    ed = 2;
+                } else if ( bc == EN ) {
+                    ed = 2;
+                }
+            } else {                                // odd
+                if ( bc == L ) {
+                    ed = 1;
+                } else if ( bc == EN ) {
+                    ed = 1;
+                } else if ( bc == AN ) {
+                    ed = 1;
+                }
+            }
+            la [ i ] = el + ed;
+        }
+    }
+
+    /**
+     * Resolve separators and boundary neutral levels to account for UAX#9 3.4 L1 while taking into
+     * account retention of formatting codes (5.2).
+     * @param ica original input class array (sequence)
+     * @param wca working copy of original intput class array (sequence), as modified by prior steps
+     * @param dl default paragraph level
+     * @param la array of output levels to be adjusted, as produced by bidi algorithm
+     */
+    private static void resolveSeparators ( int[] ica, int[] wca, int dl, int[] la ) {
+        // steps (1) through (3)
+        for ( int i = 0, n = ica.length; i < n; i++ ) {
+            int ic = ica[i];
+            if ( ( ic == BidiConstants.S ) || ( ic == BidiConstants.B ) ) {
+                la[i] = dl;
+                for ( int k = i - 1; k >= 0; k-- ) {
+                    int pc = ica[k];
+                    if ( isRetainedFormatting ( pc ) ) {
+                        continue;
+                    } if ( pc == BidiConstants.WS ) {
+                        la[k] = dl;
+                    } else {
+                        break;
+                    }
+                }
+            }
+        }
+        // step (4) - consider end of input sequence to be end of line, but skip any trailing boundary neutrals and retained formatting codes
+        for ( int i = ica.length; i > 0; i-- ) {
+            int k = i - 1;
+            int ic = ica[k];
+            if ( isRetainedFormatting ( ic ) ) {
+                continue;
+            } else if ( ic == BidiConstants.WS ) {
+                la[k] = dl;
+            } else {
+                break;
+            }
+        }
+        // step (5) - per section 5.2
+        for ( int i = 0, n = ica.length; i < n; i++ ) {
+            int ic = ica[i];
+            if ( isRetainedFormatting ( ic ) ) {
+                if ( i == 0 ) {
+                    la[i] = dl;
+                } else {
+                    la[i] = la [ i - 1 ];
+                }
+            }
+        }
+    }
+
+    private static boolean isStrong ( int bc ) {
+        switch ( bc ) {
+        case L:
+        case R:
+        case AL:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private static boolean isNeutral ( int bc ) {
+        switch ( bc ) {
+        case WS:
+        case ON:
+        case S:
+        case B:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private static boolean isRetainedFormatting ( int bc ) {
+        switch ( bc ) {
+        case LRE:
+        case LRO:
+        case RLE:
+        case RLO:
+        case PDF:
+        case BN:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private static boolean isRetainedFormatting ( int[] ca, int s, int e ) {
+        for ( int i = s; i < e; i++ ) {
+            if ( ! isRetainedFormatting ( ca[i] ) ) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static int max ( int x, int y ) {
+        if ( x > y ) {
+            return x;
+        } else {
+            return y;
+        }
+    }
+
+    private static int[] getClasses ( int[] chars ) {
+        int[] classes = new int [ chars.length ];
+        int bc;
+        for ( int i = 0, n = chars.length; i < n; i++ ) {
+            int ch = chars [ i ];
+            if ( ch >= 0 ) {
+                bc = BidiClass.getBidiClass ( chars [ i ] );
+            } else {
+                bc = SURROGATE;
+            }
+            classes [ i ] = bc;
+        }
+        return classes;
+    }
+
+    /**
+     * Convert character sequence (a UTF-16 encoded string) to an array of unicode scalar values
+     * expressed as integers. If a valid UTF-16 surrogate pair is encountered, it is converted to
+     * two integers, the first being the equivalent unicode scalar  value, and the second being
+     * negative one (-1). This special mechanism is used to track the use of surrogate pairs while
+     * working with unicode scalar values, and permits maintaining indices that apply both to the
+     * input UTF-16 and out scalar value sequences.
+     * @return a boolean indicating that content is present that triggers bidirectional processing
+     * @param cs a UTF-16 encoded character sequence
+     * @param chars an integer array to accept the converted scalar values, where the length of the
+     * array must be the same as the length of the input character sequence
+     * @throws IllegalArgumentException if the input sequence is not a valid UTF-16 string, e.g.,
+     * if it contains an isolated UTF-16 surrogate
+     */
+    private static boolean convertToScalar ( CharSequence cs, int[] chars ) throws IllegalArgumentException {
+        boolean triggered = false;
+        if ( chars.length != cs.length() ) {
+            throw new IllegalArgumentException ( "characters array length must match input sequence length" );
+        }
+        for ( int i = 0, n = chars.length; i < n; ) {
+            int chIn = cs.charAt ( i );
+            int chOut;
+            if ( chIn < 0xD800 ) {
+                chOut = chIn;
+            } else if ( chIn < 0xDC00 ) {
+                int chHi = chIn;
+                int chLo;
+                if ( ( i + 1 ) < n ) {
+                    chLo = cs.charAt ( i + 1 );
+                    if ( ( chLo >= 0xDC00 ) && ( chLo <= 0xDFFF ) ) {
+                        chOut = convertToScalar ( chHi, chLo );
+                    } else {
+                        throw new IllegalArgumentException ( "isolated high surrogate" );
+                    }
+                } else {
+                    throw new IllegalArgumentException ( "truncated surrogate pair" );
+                }
+            } else if ( chIn < 0xE000 ) {
+                throw new IllegalArgumentException ( "isolated low surrogate" );
+            } else {
+                chOut = chIn;
+            }
+            if ( ! triggered && triggersBidi ( chOut ) ) {
+                triggered = true;
+            }
+            if ( ( chOut & 0xFF0000 ) == 0 ) {
+                chars [ i++ ] = chOut;
+            } else {
+                chars [ i++ ] = chOut;
+                chars [ i++ ] = -1;
+            }
+        }
+        return triggered;
+    }
+
+    /**
+     * Convert UTF-16 surrogate pair to unicode scalar valuee.
+     * @return a unicode scalar value
+     * @param chHi high (most significant or first) surrogate
+     * @param chLo low (least significant or second) surrogate
+     * @throws IllegalArgumentException if one of the input surrogates is not valid
+     */
+    private static int convertToScalar ( int chHi, int chLo ) {
+        if ( ( chHi < 0xD800 ) || ( chHi > 0xDBFF ) ) {
+            throw new IllegalArgumentException ( "bad high surrogate" );
+        } else if ( ( chLo < 0xDC00 ) || ( chLo > 0xDFFF ) ) {
+            throw new IllegalArgumentException ( "bad low surrogate" );
+        } else {
+            return ( ( ( chHi & 0x03FF ) << 10 ) | ( chLo & 0x03FF ) ) + 0x10000;
+        }
+    }
+
+    /**
+     * Determine of character CH triggers bidirectional processing. Bidirectional
+     * processing is deemed triggerable if CH is a strong right-to-left character,
+     * an arabic letter or number, or is a right-to-left embedding or override
+     * character.
+     * @return true if character triggers bidirectional processing
+     * @param ch a unicode scalar value
+     */
+    private static boolean triggersBidi ( int ch ) {
+        switch ( BidiClass.getBidiClass ( ch ) ) {
+        case R:
+        case AL:
+        case AN:
+        case RLE:
+        case RLO:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private static void dump ( String header, int[] chars, int[] classes, int defaultLevel, int[] levels ) {
+        log.debug ( header );
+        log.debug ( "BD: default level(" + defaultLevel + ")" );
+        StringBuffer sb = new StringBuffer();
+        if ( chars != null ) {
+            for ( int i = 0, n = chars.length; i < n; i++ ) {
+                int ch = chars [ i ];
+                sb.setLength(0);
+                if ( ( ch > 0x20 ) && ( ch < 0x7F ) ) {
+                    sb.append ( (char) ch );
+                } else {
+                    sb.append ( CharUtilities.charToNCRef ( ch ) );
+                }
+                for ( int k = sb.length(); k < 12; k++ ) {
+                    sb.append ( ' ' );
+                }
+                sb.append ( ": " + padRight ( getClassName ( classes[i] ), 4 ) + " " + levels[i] );
+                log.debug ( sb );
+            }
+        } else {
+            for ( int i = 0, n = classes.length; i < n; i++ ) {
+                sb.setLength(0);
+                for ( int k = sb.length(); k < 12; k++ ) {
+                    sb.append ( ' ' );
+                }
+                sb.append ( ": " + padRight ( getClassName ( classes[i] ), 4 ) + " " + levels[i] );
+                log.debug ( sb );
+            }
+        }
+    }
+
+    private static String getClassName ( int bc ) {
+        switch ( bc ) {
+        case L:                                     // left-to-right
+            return "L";
+        case LRE:                                   // left-to-right embedding
+            return "LRE";
+        case LRO:                                   // left-to-right override
+            return "LRO";
+        case R:                                     // right-to-left
+            return "R";
+        case AL:                                    // right-to-left arabic
+            return "AL";
+        case RLE:                                   // right-to-left embedding
+            return "RLE";
+        case RLO:                                   // right-to-left override
+            return "RLO";
+        case PDF:                                   // pop directional formatting
+            return "PDF";
+        case EN:                                    // european number
+            return "EN";
+        case ES:                                    // european number separator
+            return "ES";
+        case ET:                                    // european number terminator
+            return "ET";
+        case AN:                                    // arabic number
+            return "AN";
+        case CS:                                    // common number separator
+            return "CS";
+        case NSM:                                   // non-spacing mark
+            return "NSM";
+        case BN:                                    // boundary neutral
+            return "BN";
+        case B:                                     // paragraph separator
+            return "B";
+        case S:                                     // segment separator
+            return "S";
+        case WS:                                    // whitespace
+            return "WS";
+        case ON:                                    // other neutrals
+            return "ON";
+        case SURROGATE:                             // placeholder for low surrogate
+            return "SUR";
+        default:
+            return "?";
+        }
+    }
+
+    private static String padLeft ( int n, int width ) {
+        return padLeft ( Integer.toString ( n ), width );
+    }
+
+    private static String padLeft ( String s, int width ) {
+        StringBuffer sb = new StringBuffer();
+        for ( int i = s.length(); i < width; i++ ) {
+            sb.append(' ');
+        }
+        sb.append ( s );
+        return sb.toString();
+    }
+
+    /* not used yet
+    private static String padRight ( int n, int width ) {
+        return padRight ( Integer.toString ( n ), width );
+    }
+    */
+
+    private static String padRight ( String s, int width ) {
+        StringBuffer sb = new StringBuffer ( s );
+        for ( int i = sb.length(); i < width; i++ ) {
+            sb.append(' ');
+        }
+        return sb.toString();
+    }
+
+}

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/AdvancedTypographicTableFormatException.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+/**
+ * Exception thrown when attempting to decode a truetype font file and a format
+ * constraint is violated.
+ * @author Glenn Adams
+ */
+public class AdvancedTypographicTableFormatException extends RuntimeException {
+    /**
+     * Instantiate ATT format exception.
+     */
+    public AdvancedTypographicTableFormatException() {
+        super();
+    }
+    /**
+     * Instantiate ATT format exception.
+     * @param message a message string
+     */
+    public AdvancedTypographicTableFormatException(String message) {
+        super(message);
+    }
+    /**
+     * Instantiate ATT format exception.
+     * @param message a message string
+     * @param cause a <code>Throwable</code> that caused this exception
+     */
+    public AdvancedTypographicTableFormatException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

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

Added: xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java
URL: http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java?rev=1293736&view=auto
==============================================================================
--- xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java (added)
+++ xmlgraphics/fop/trunk/src/java/org/apache/fop/complexscripts/fonts/GlyphClassTable.java Sun Feb 26 02:29:01 2012
@@ -0,0 +1,277 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.complexscripts.fonts;
+
+import java.util.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 AdvancedTypographicTableFormatException ( "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 AdvancedTypographicTableFormatException ( "illegal mapping entry, must be Integer: " + o );
+                }
+            }
+            assert i == n;
+            assert this.gca == null;
+            this.firstGlyph = firstGlyph;
+            this.gca = gca;
+            this.gcMax = gcMax;
+        }
+        /** {@inheritDoc} */
+        public String toString() {
+            StringBuffer sb = new StringBuffer();
+            sb.append("{ firstGlyph = " + firstGlyph + ", classes = {");
+            for ( int i = 0, n = gca.length; i < n; i++ ) {
+                if ( i > 0 ) {
+                    sb.append(',');
+                }
+                sb.append ( Integer.toString ( gca [ i ] ) );
+            }
+            sb.append("} }");
+            return sb.toString();
+        }
+    }
+
+    private static class RangeClassTable extends GlyphMappingTable.RangeMappingTable implements GlyphClassMapping {
+        public RangeClassTable ( List entries ) {
+            super ( entries );
+        }
+        /** {@inheritDoc} */
+        public int getMappedIndex ( int gid, int s, int m ) {
+            return m;
+        }
+        /** {@inheritDoc} */
+        public int getClassSize ( int set ) {
+            return getMappingSize();
+        }
+        /** {@inheritDoc} */
+        public int getClassIndex ( int gid, int set ) {
+            return getMappedIndex ( gid );
+        }
+    }
+
+    private static class CoverageSetClassTable extends GlyphMappingTable.EmptyMappingTable implements GlyphClassMapping {
+        public CoverageSetClassTable ( List entries ) {
+            throw new UnsupportedOperationException ( "coverage set class table not yet supported" );
+        }
+        /** {@inheritDoc} */
+        public int getType() {
+            return GLYPH_CLASS_TYPE_COVERAGE_SET;
+        }
+        /** {@inheritDoc} */
+        public int getClassSize ( int set ) {
+            return 0;
+        }
+        /** {@inheritDoc} */
+        public int getClassIndex ( int gid, int set ) {
+            return -1;
+        }
+    }
+
+}



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