Added: 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/GlyphLayout.java
URL: 
http://svn.apache.org/viewvc/xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/GlyphLayout.java?rev=1647590&view=auto
==============================================================================
--- 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/GlyphLayout.java
 (added)
+++ 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/GlyphLayout.java
 Tue Dec 23 15:10:45 2014
@@ -0,0 +1,2046 @@
+/*
+
+   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.
+
+ */
+package org.apache.batik.bridge;
+
+import java.awt.BasicStroke;
+import java.awt.Graphics2D;
+import java.awt.Shape;
+import java.awt.Stroke;
+import java.awt.font.FontRenderContext;
+import java.awt.font.TextAttribute;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.geom.Line2D;
+import java.text.AttributedCharacterIterator;
+import java.text.CharacterIterator;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.batik.gvt.font.AWTGVTFont;
+import org.apache.batik.gvt.font.AltGlyphHandler;
+import org.apache.batik.gvt.font.GVTFont;
+import org.apache.batik.gvt.font.GVTGlyphMetrics;
+import org.apache.batik.gvt.font.GVTGlyphVector;
+import org.apache.batik.gvt.font.GVTLineMetrics;
+import org.apache.batik.gvt.text.ArabicTextHandler;
+import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
+import org.apache.batik.gvt.text.TextPath;
+
+/**
+ * Implementation of TextSpanLayout which uses java.awt.font.GlyphVector.
+ * @see org.apache.batik.bridge.TextSpanLayout
+ *
+ * @author <a href="mailto:bill.hane...@ireland.sun.com";>Bill Haneman</a>
+ * @version $Id$
+ */
+public class GlyphLayout implements TextSpanLayout {
+
+    protected GVTGlyphVector gv;
+    private GVTFont font;
+    private GVTLineMetrics metrics;
+    private AttributedCharacterIterator aci;
+    protected Point2D advance;
+    private Point2D offset;
+    private float   xScale=1;
+    private float   yScale=1;
+    private TextPath textPath;
+    private Point2D textPathAdvance;
+    private int []  charMap;
+    private boolean vertical, adjSpacing=true;
+    private float [] glyphAdvances;
+    private boolean isAltGlyph; //false
+
+    // When layoutApplied is false it means that the glyph positions
+    // are different from where they would be if you did
+    // doExplicitGlyphLayout().
+    protected boolean layoutApplied = false;
+    // When spacingApplied is false it means that xScale, yScale and
+    // kerning/wordspacing stuff haven't been applied. This can
+    // be rectified by calling adjustTextSpacing().  Note that when
+    // spacing is actually used layoutApplied will be cleared it
+    // is not garunteed that applying text spacing will cause it to
+    // be cleared (it will only be cleared if the glyphs move).
+    private boolean spacingApplied = false;
+    // When pathApplied is false it means that the text has not been
+    // layed out on the associated text path (if any).  If there is an
+    // associated text path then this will clear both layoutApplied
+    // and spacing applied but neither will be touched if no text path
+    // is present.
+    private boolean pathApplied    = false;
+
+
+    public static final AttributedCharacterIterator.Attribute FLOW_LINE_BREAK
+        = GVTAttributedCharacterIterator.TextAttribute.FLOW_LINE_BREAK;
+
+    public static final AttributedCharacterIterator.Attribute FLOW_PARAGRAPH
+        = GVTAttributedCharacterIterator.TextAttribute.FLOW_PARAGRAPH;
+
+    public static final AttributedCharacterIterator.Attribute
+        FLOW_EMPTY_PARAGRAPH
+        = GVTAttributedCharacterIterator.TextAttribute.FLOW_EMPTY_PARAGRAPH;
+
+    public static final AttributedCharacterIterator.Attribute LINE_HEIGHT
+        = GVTAttributedCharacterIterator.TextAttribute.LINE_HEIGHT;
+
+    public static final AttributedCharacterIterator.Attribute
+        VERTICAL_ORIENTATION
+        = GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION;
+
+    public static final
+        AttributedCharacterIterator.Attribute VERTICAL_ORIENTATION_ANGLE =
+       GVTAttributedCharacterIterator.TextAttribute.VERTICAL_ORIENTATION_ANGLE;
+
+    public static final
+        AttributedCharacterIterator.Attribute HORIZONTAL_ORIENTATION_ANGLE =
+     GVTAttributedCharacterIterator.TextAttribute.HORIZONTAL_ORIENTATION_ANGLE;
+
+    private static final AttributedCharacterIterator.Attribute X
+        = GVTAttributedCharacterIterator.TextAttribute.X;
+
+    private static final AttributedCharacterIterator.Attribute Y
+        = GVTAttributedCharacterIterator.TextAttribute.Y;
+
+    private static final AttributedCharacterIterator.Attribute DX
+        = GVTAttributedCharacterIterator.TextAttribute.DX;
+
+    private static final AttributedCharacterIterator.Attribute DY
+        = GVTAttributedCharacterIterator.TextAttribute.DY;
+
+    private static final AttributedCharacterIterator.Attribute ROTATION
+        = GVTAttributedCharacterIterator.TextAttribute.ROTATION;
+
+    private static final AttributedCharacterIterator.Attribute BASELINE_SHIFT
+        = GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT;
+
+    private static final AttributedCharacterIterator.Attribute WRITING_MODE
+        = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE;
+
+    private static final Integer WRITING_MODE_TTB
+        = GVTAttributedCharacterIterator.TextAttribute.WRITING_MODE_TTB;
+
+    private static final Integer ORIENTATION_AUTO
+        = GVTAttributedCharacterIterator.TextAttribute.ORIENTATION_AUTO;
+
+    public static final AttributedCharacterIterator.Attribute GVT_FONT
+        = GVTAttributedCharacterIterator.TextAttribute.GVT_FONT;
+
+    protected static Set runAtts = new HashSet();
+
+    static {
+        runAtts.add(X);
+        runAtts.add(Y);
+        runAtts.add(DX);
+        runAtts.add(DY);
+        runAtts.add(ROTATION);
+        runAtts.add(BASELINE_SHIFT);
+    }
+
+    protected static Set szAtts = new HashSet();
+
+    static {
+        szAtts.add(TextAttribute.SIZE);
+        szAtts.add(GVT_FONT);
+        szAtts.add(LINE_HEIGHT);
+    }
+
+
+    /**
+     * Creates the specified text layout using the
+     * specified AttributedCharacterIterator and rendering context.
+     *
+     * @param aci the AttributedCharacterIterator whose text is to
+     *  be laid out
+     * @param charMap Indicates how chars in aci map to original
+     *                text char array.
+     * @param offset The offset position of this text layout
+     * @param frc the FontRenderContext to use for generating glyphs.
+     */
+    public GlyphLayout(AttributedCharacterIterator aci,
+                       int [] charMap,
+                       Point2D offset,
+                       FontRenderContext frc) {
+
+        this.aci = aci;
+        this.offset = offset;
+        this.font = getFont();
+        this.charMap = charMap;
+
+        this.metrics = font.getLineMetrics
+            (aci, aci.getBeginIndex(), aci.getEndIndex(), frc);
+
+        // create the glyph vector
+        this.gv = null;
+        this.aci.first();
+        this.vertical = (aci.getAttribute(WRITING_MODE) == WRITING_MODE_TTB);
+        this.textPath =  (TextPath) aci.getAttribute
+            (GVTAttributedCharacterIterator.TextAttribute.TEXTPATH);
+
+        AltGlyphHandler altGlyphHandler
+            = (AltGlyphHandler)this.aci.getAttribute
+            (GVTAttributedCharacterIterator.TextAttribute.ALT_GLYPH_HANDLER);
+        if (altGlyphHandler != null) {
+            // this must be an altGlyph text element, try and create
+            // the alternate glyphs
+            this.gv = altGlyphHandler.createGlyphVector
+                (frc, this.font.getSize(), this.aci);
+            if ( this.gv != null ){
+                this.isAltGlyph = true;
+            }
+        }
+        if (this.gv == null) {
+            // either not an altGlyph or the altGlyphHandler failed to
+            // create a glyph vector
+            this.gv = font.createGlyphVector(frc, this.aci);
+        }
+    }
+
+
+    public GVTGlyphVector getGlyphVector() {
+        return this.gv;
+    }
+
+
+    /**
+     * Returns the current text position at the beginning
+     * of glyph layout, before the application of explicit
+     * glyph positioning attributes.
+     */
+    public Point2D getOffset() {
+        return offset;
+    }
+
+    /**
+     * Sets the scaling factor to use for string.  if ajdSpacing is
+     * true then only the spacing between glyphs will be adjusted
+     * otherwise the glyphs and the spaces between them will be
+     * adjusted.  Only the scale factor in the progression direction
+     * is used (x for horizontal text, y for vertical text
+     * ).
+     * @param xScale Scale factor to apply in X direction.
+     * @param yScale Scale factor to apply in Y direction.
+     * @param adjSpacing True if only spaces should be adjusted.
+     */
+    public void setScale(float xScale, float yScale, boolean adjSpacing) {
+        // Fix the off axis scale factor.
+        if (vertical) xScale = 1;
+        else          yScale = 1;
+
+        if ((xScale != this.xScale) ||
+            (yScale != this.yScale) ||
+            (adjSpacing != this.adjSpacing)) {
+            this.xScale = xScale;
+            this.yScale = yScale;
+            this.adjSpacing = adjSpacing;
+
+            // We don't affect layoutApplied directly...
+
+            // However if we did path layout or spacing it's all junk now...
+            spacingApplied = false;
+            glyphAdvances  = null;
+            pathApplied    = false;
+        }
+    }
+
+    /**
+     * Sets the text position used for the implicit origin
+     * of glyph layout. Ignored if multiple explicit glyph
+     * positioning attributes are present in ACI
+     * (e.g. if the aci has multiple X or Y values).
+     */
+    public void setOffset(Point2D offset) {
+        if ((offset.getX() != this.offset.getX()) ||
+            (offset.getY() != this.offset.getY())) {
+            if ((layoutApplied)||(spacingApplied)) {
+                // Already layed out need to shift glyph positions to
+                // account for new offset.
+                float dx = (float)(offset.getX()-this.offset.getX());
+                float dy = (float)(offset.getY()-this.offset.getY());
+                int numGlyphs = gv.getNumGlyphs();
+
+                float [] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
+                Point2D.Float pos = new Point2D.Float();
+                for (int i=0; i<=numGlyphs; i++) {
+                    pos.x = gp[2*i  ]+dx;
+                    pos.y = gp[2*i+1]+dy;
+                    gv.setGlyphPosition(i, pos);
+                }
+            }
+
+            // When not layed out (or after updating) just set the new
+            // offset this will be factored in for any future layout
+            // operations.
+            this.offset = offset;
+
+            // We don't affect layoutApplied or spacingApplied since
+            // they both work off the offset value.
+
+            // However if we did path layout it's all junk now...
+            pathApplied = false;
+        }
+    }
+
+    public GVTGlyphMetrics getGlyphMetrics(int glyphIndex) {
+        return gv.getGlyphMetrics(glyphIndex);
+    }
+
+    public GVTLineMetrics getLineMetrics() {
+        return metrics;
+    }
+
+    /**
+     * Returns true if the advance direction of this text is vertical.
+     */
+    public boolean isVertical() {
+        return vertical;
+    }
+
+    /**
+     * Returns true if this layout in on a text path.
+     */
+    public boolean isOnATextPath() {
+        return (textPath != null);
+    }
+
+
+    /**
+     * Returns the number of glyphs in this layout.
+     */
+    public int getGlyphCount() {
+        return gv.getNumGlyphs();
+    }
+
+
+    /**
+     * Returns the number of chars represented by the glyphs within the
+     * specified range.
+     *
+     * @param startGlyphIndex The index of the first glyph in the range.
+     * @param endGlyphIndex The index of the last glyph in the range.
+     *
+     * @return The number of chars.
+     */
+    public int getCharacterCount(int startGlyphIndex, int endGlyphIndex) {
+        return gv.getCharacterCount(startGlyphIndex, endGlyphIndex);
+    }
+
+    /**
+     * Returns true if the text direction in this layout is from left to right.
+     */
+    public boolean isLeftToRight() {
+        aci.first();
+        int bidiLevel =
+            ((Integer)aci.getAttribute
+             (GVTAttributedCharacterIterator.TextAttribute.BIDI_LEVEL))
+            .intValue();
+
+        // Check if low bit is set if not then we are left to right
+        // (even bidi level).
+        return ((bidiLevel&0x01) == 0);
+    }
+
+
+    /**
+     * This method makes certain that the layout has been
+     * completed at this point (much of the layout is done lazily).
+     */
+    private final void syncLayout() {
+        if (!pathApplied) {
+            doPathLayout();
+        }
+    }
+
+    /**
+     * Paints the text layout using the
+     * specified Graphics2D and rendering context.
+     * @param g2d the Graphics2D to use
+     */
+    public void draw(Graphics2D g2d) {
+        syncLayout();
+        gv.draw(g2d, aci);
+    }
+
+    /**
+     * Returns the current text position at the completion
+     * of glyph layout.
+     */
+    public Point2D getAdvance2D() {
+        adjustTextSpacing();
+        return advance;
+    }
+
+
+    /**
+     * Returns the outline of the completed glyph layout.
+     */
+    public Shape getOutline() {
+        syncLayout();
+
+        return gv.getOutline();
+    }
+
+    public float [] getGlyphAdvances() {
+        if (glyphAdvances != null)
+            return glyphAdvances;
+
+        if (!spacingApplied)
+            // This will layout the text if needed.
+            adjustTextSpacing();
+
+        int numGlyphs = gv.getNumGlyphs();
+        float [] glyphPos = gv.getGlyphPositions(0, numGlyphs+1, null);
+        glyphAdvances = new float[numGlyphs+1];
+        int off = 0;
+        if (isVertical())
+            off = 1;
+
+        float start = glyphPos[off];
+        for (int i=0; i<numGlyphs+1; i++) {
+            glyphAdvances[i] = glyphPos[2*i+off]-start;
+        }
+        return glyphAdvances;
+    }
+
+    /**
+     * Returns the outline of the specified decorations on the glyphs,
+     * @param decorationType an integer indicating the type(s) of decorations
+     *     included in this shape.  May be the result of "OR-ing" several
+     *     values together:
+     * e.g. <code>DECORATION_UNDERLINE | DECORATION_STRIKETHROUGH</code>
+     */
+    public Shape getDecorationOutline(int decorationType) {
+        syncLayout();
+
+        Shape g = new GeneralPath();
+        if ((decorationType & DECORATION_UNDERLINE) != 0) {
+             ((GeneralPath) g).append(getUnderlineShape(), false);
+        }
+        if ((decorationType & DECORATION_STRIKETHROUGH) != 0) {
+             ((GeneralPath) g).append(getStrikethroughShape(), false);
+        }
+        if ((decorationType & DECORATION_OVERLINE) != 0) {
+             ((GeneralPath) g).append(getOverlineShape(), false);
+        }
+        return g;
+    }
+
+    /**
+     * Returns the rectangular bounds of the completed glyph layout.
+     */
+    public Rectangle2D getBounds2D() {
+        syncLayout();
+        return gv.getBounds2D(aci);
+    }
+
+    /**
+     * Returns the rectangular bounds of the completed glyph layout,
+     * inclusive of "decoration" (underline, overline, etc.)
+     */
+    public Rectangle2D getGeometricBounds() {
+        syncLayout();
+        Rectangle2D gvB, decB;
+        gvB = gv.getGeometricBounds();
+        decB = getDecorationOutline(DECORATION_ALL).getBounds2D();
+        return gvB.createUnion(decB);
+    }
+
+    /**
+     * Returns the position to used when drawing a text run after this one.
+     * It takes into account the text path layout if there is one.
+     */
+    public Point2D getTextPathAdvance() {
+        syncLayout();
+        if (textPath != null) {
+            return textPathAdvance;
+        } else {
+            return getAdvance2D();
+        }
+    }
+
+
+    /**
+     * Returns the index of the first glyph that has the specified char index.
+     *
+     * @param charIndex The original index of the character in the text node's
+     * text string.
+     * @return The index of the matching glyph in this layout's glyph vector,
+     *         or -1 if a matching glyph could not be found.
+     */
+    public int getGlyphIndex(int charIndex) {
+        int numGlyphs = getGlyphCount();
+        int j=0;
+        for (int i = 0; i < numGlyphs; i++) {
+            int count = getCharacterCount(i, i);
+            for (int n=0; n<count; n++) {
+                int glyphCharIndex = charMap[j++];
+                if (charIndex == glyphCharIndex)
+                    return i;
+                if (j >= charMap.length)
+                    return -1;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the index of the last glyph that has the specified char index.
+     *
+     * @param charIndex The original index of the character in the text node's
+     * text string.
+     * @return The index of the matching glyph in this layout's glyph vector,
+     *         or -1 if a matching glyph could not be found.
+     */
+    public int getLastGlyphIndex(int charIndex) {
+        int numGlyphs = getGlyphCount();
+        int j=charMap.length-1;
+        for (int i = numGlyphs-1; i >= 0; --i) {
+            int count = getCharacterCount(i, i);
+            for (int n=0; n<count; n++) {
+                int glyphCharIndex = charMap[j--];
+                if (charIndex == glyphCharIndex) return i;
+                if (j < 0)                       return -1;
+            }
+        }
+        return -1;
+    }
+
+
+    /**
+     * Return the angle value according to the orientation
+     * of the character.
+     */
+    public double getComputedOrientationAngle(int index){
+
+        if ( isGlyphOrientationAuto() ){
+            if (isVertical()) {
+                char ch = aci.setIndex(index);
+                if (isLatinChar(ch))
+                    return 90.0;
+                else
+                    return 0.0;
+            }
+            return 0.0;
+        }
+        else{
+            return getGlyphOrientationAngle();
+        }
+    }
+
+   /**
+     * Returns a Shape which encloses the currently selected glyphs
+     * as specified by the character indices.
+     *
+     * @param beginCharIndex the index of the first char in the
+     * contiguous selection.
+     * @param endCharIndex the index of the last char in the
+     * contiguous selection.
+     * @return The highlight shape or null if the spacified char range
+     * does not overlap with the chars in this layout.  */
+    public Shape getHighlightShape(int beginCharIndex, int endCharIndex) {
+        syncLayout();
+
+        if (beginCharIndex > endCharIndex) {
+            int temp = beginCharIndex;
+            beginCharIndex = endCharIndex;
+            endCharIndex = temp;
+        }
+        GeneralPath shape = null;
+        int numGlyphs = getGlyphCount();
+
+        Point2D.Float [] topPts = new Point2D.Float[2*numGlyphs];
+        Point2D.Float [] botPts = new Point2D.Float[2*numGlyphs];
+
+        int ptIdx = 0;
+
+        int currentChar = 0;
+        for (int i = 0; i < numGlyphs; i++) {
+            int glyphCharIndex = charMap[currentChar];
+            if ((glyphCharIndex >= beginCharIndex) &&
+                (glyphCharIndex <= endCharIndex) &&
+                gv.isGlyphVisible(i)) {
+
+                Shape gbounds = gv.getGlyphLogicalBounds(i);
+                if (gbounds != null) {
+                    // We got something...
+                    if (shape == null)
+                        shape = new GeneralPath();
+
+                    // We are pretty dumb here we assume that we always
+                    // get back polygons with four sides to them if
+                    // isn't met we are SOL.
+                    float [] pts = new float[6];
+                    int count = 0;
+                    int type = -1;
+
+                    PathIterator pi = gbounds.getPathIterator(null);
+                    Point2D.Float firstPt = null;
+
+                    while (!pi.isDone()) {
+                        type = pi.currentSegment(pts);
+                        if ((type == PathIterator.SEG_MOVETO) ||
+                            (type == PathIterator.SEG_LINETO)) {
+                            // LINETO or MOVETO
+                            if (count > 4) break; // too many lines...
+                            if (count == 4) {
+                                // make sure we are just closing it..
+                                if ((firstPt == null)     ||
+                                    (firstPt.x != pts[0]) ||
+                                    (firstPt.y != pts[1]))
+                                    break;
+                            } else {
+                                Point2D.Float pt;
+                                pt = new Point2D.Float(pts[0], pts[1]);
+                                if (count == 0) firstPt = pt;
+                                // Use sides of  rectangle...
+                                switch (count) {
+                                case 0: botPts[ptIdx]   = pt; break;
+                                case 1: topPts[ptIdx]   = pt; break;
+                                case 2: topPts[ptIdx+1] = pt; break;
+                                case 3: botPts[ptIdx+1] = pt; break;
+                                }
+                            }
+                        } else if (type == PathIterator.SEG_CLOSE) {
+                                // Close in the wrong spot?
+                            if ((count < 4) || (count > 5)) break;
+                        } else {
+                            // QUADTO or CUBETO
+                            break;
+                        }
+
+                        count++;
+                        pi.next();
+                    }
+                    if (pi.isDone()) {
+                        // Sucessfully Expressed as a quadralateral...
+                        if ((botPts[ptIdx]!=null) &&
+                            ((topPts[ptIdx].x != topPts[ptIdx+1].x) ||
+                             (topPts[ptIdx].y != topPts[ptIdx+1].y)))
+                            // box isn't empty so use it's points...
+                            ptIdx += 2;
+                    } else {
+                        // Wasn't a quadralateral so just add it don't try
+                        // and merge it...
+                        addPtsToPath(shape, topPts, botPts, ptIdx);
+                        ptIdx = 0;
+                        shape.append(gbounds, false);
+                    }
+                }
+            }
+            currentChar += getCharacterCount(i, i);
+            if (currentChar >= charMap.length)
+                currentChar = charMap.length-1;
+        }
+        addPtsToPath(shape, topPts, botPts, ptIdx);
+
+        return shape;
+    }
+
+    public static final double eps = 0.00001;
+
+    public static boolean epsEQ(double a, double b) {
+        return ((a+eps > b) && (a-eps < b));
+    }
+
+    public static int makeConvexHull(Point2D.Float [] pts, int numPts) {
+        // Sort the Pts in X...
+        Point2D.Float tmp;
+        for (int i=1; i<numPts; i++) {
+            // Simple bubble sort (numPts should be small so shouldn't
+            // be too bad.).
+            if ((pts[i].x < pts[i-1].x) ||
+                ((pts[i].x == pts[i-1].x) && (pts[i].y < pts[i-1].y))) {
+                tmp = pts[i];
+                pts[i] = pts[i-1];
+                pts[i-1] = tmp;
+                i=0;
+                continue;
+            }
+        }
+
+        Point2D.Float pt0 = pts[0];
+        Point2D.Float pt1 = pts[numPts-1];
+        Point2D.Float dxdy = new Point2D.Float(pt1.x-pt0.x, pt1.y-pt0.y);
+        float soln, c = dxdy.y*pt0.x-dxdy.x*pt0.y;
+
+        Point2D.Float [] topList = new Point2D.Float[numPts];
+        Point2D.Float [] botList = new Point2D.Float[numPts];
+        botList[0] = topList[0] = pts[0];
+        int nTopPts=1;
+        int nBotPts=1;
+        for (int i=1; i<numPts-1; i++) {
+            Point2D.Float pt = pts[i];
+            soln = dxdy.x*pt.y-dxdy.y*pt.x+c;
+            if (soln < 0) {
+                // Below line goes into bot pt list...
+                while (nBotPts >= 2) {
+                    pt0 = botList[nBotPts-2];
+                    pt1 = botList[nBotPts-1];
+                    float dx = pt1.x-pt0.x;
+                    float dy = pt1.y-pt0.y;
+                    float c0 = dy*pt0.x-dx*pt0.y;
+                    soln = dx*pt.y-dy*pt.x+c0;
+                    if (soln > eps) // Left turn add and we are done..
+                        break;
+                    if (soln > -eps) {
+                        // On line take lowest Y of two and keep going
+                        if (pt1.y < pt.y) pt = pt1;
+                        nBotPts--;
+                        break;
+                    }
+                    // right turn drop prev pt;
+                    nBotPts--;
+                }
+                botList[nBotPts++] = pt;
+            } else {
+                // Above line goes into top pt list...
+                while (nTopPts >= 2) {
+                    pt0 = topList[nTopPts-2];
+                    pt1 = topList[nTopPts-1];
+                    float dx = pt1.x-pt0.x;
+                    float dy = pt1.y-pt0.y;
+                    float c0 = dy*pt0.x-dx*pt0.y;
+                    soln = dx*pt.y-dy*pt.x+c0;
+                    if (soln < -eps) // Right turn add and check next point.
+                        break;
+                    if (soln < eps) {
+                        // On line take greatest Y of two and keep going
+                        if (pt1.y > pt.y) pt = pt1;
+                        nTopPts--;
+                        break;
+                    }
+                    // left turn drop prev pt;
+                    nTopPts--;
+                }
+                topList[nTopPts++] = pt;
+            }
+        }
+
+        // Check last point in both sets...
+        Point2D.Float pt = pts[numPts-1];
+        while (nBotPts >= 2) {
+            pt0 = botList[nBotPts-2];
+            pt1 = botList[nBotPts-1];
+            float dx = pt1.x-pt0.x;
+            float dy = pt1.y-pt0.y;
+            float c0 = dy*pt0.x-dx*pt0.y;
+            soln = dx*pt.y-dy*pt.x+c0;
+            if (soln > eps)
+                // Left turn add and we are done..
+                break;
+            if (soln > -eps) {
+                // On line take lowest Y of two and keep going
+                if (pt1.y >= pt.y) nBotPts--;
+                break;
+            }
+            // right turn drop prev pt;
+            nBotPts--;
+        }
+
+        while (nTopPts >= 2) {
+            pt0 = topList[nTopPts-2];
+            pt1 = topList[nTopPts-1];
+            float dx = pt1.x-pt0.x;
+            float dy = pt1.y-pt0.y;
+            float c0 = dy*pt0.x-dx*pt0.y;
+            soln = dx*pt.y-dy*pt.x+c0;
+            if (soln < -eps)
+                // Right turn done...
+                break;
+            if (soln < eps) {
+                // On line take lowest Y of two and keep going
+                if (pt1.y <= pt.y) nTopPts--;
+                break;
+            }
+            // left turn drop prev pt;
+            nTopPts--;
+        }
+
+        System.arraycopy( topList, 0, pts, 0, nTopPts );
+        int i= nTopPts;
+
+        // We always include the 'last' point as it is always on convex hull.
+        pts[i++] = pts[numPts-1];
+
+        // don't include botList[0] since it is the same as topList[0].
+        for (int n=nBotPts-1; n>0; n--, i++)
+            pts[i] = botList[n];
+
+        return i;
+    }
+
+    public static void addPtsToPath(GeneralPath shape,
+                                     Point2D.Float [] topPts,
+                                     Point2D.Float [] botPts,
+                                     int numPts) {
+        if (numPts < 2) return;
+        if (numPts == 2) {
+            shape.moveTo(topPts[0].x, topPts[0].y);
+            shape.lineTo(topPts[1].x, topPts[1].y);
+            shape.lineTo(botPts[1].x, botPts[1].y);
+            shape.lineTo(botPts[0].x, botPts[0].y);
+            shape.lineTo(topPts[0].x, topPts[0].y);
+            return;
+        }
+
+        // Here we 'connect the dots' the best way we know how...
+        // What I do is construct a convex hull between adjacent
+        // character boxes, then I union that into the shape.  this
+        // does a good job of bridging between adjacent characters,
+        // but still closely tracking to text boxes.  The use of the
+        // Area class is fairly heavy weight but it seems to keep up
+        // in this instanace (probably because all the shapes are very
+        // simple polygons).
+        Point2D.Float [] boxes = new Point2D.Float[8];
+        Point2D.Float [] chull = new Point2D.Float[8];
+        boxes[4] = topPts[0];
+        boxes[5] = topPts[1];
+        boxes[6] = botPts[1];
+        boxes[7] = botPts[0];
+        Area []areas = new Area[numPts/2];
+        int nAreas =0;
+        for (int i=2; i<numPts; i+=2) {
+            boxes[0] = boxes[4];
+            boxes[1] = boxes[5];
+            boxes[2] = boxes[6];
+            boxes[3] = boxes[7];
+            boxes[4] = topPts[i];
+            boxes[5] = topPts[i+1];
+            boxes[6] = botPts[i+1];
+            boxes[7] = botPts[i];
+
+            float delta,sz,dist;
+            delta  = boxes[2].x-boxes[0].x;
+            dist   = delta*delta;
+            delta  = boxes[2].y-boxes[0].y;
+            dist  += delta*delta;
+            sz     = (float)Math.sqrt(dist);
+
+            delta  = boxes[6].x-boxes[4].x;
+            dist   = delta*delta;
+            delta  = boxes[6].y-boxes[4].y;
+            dist  += delta*delta;
+            sz    += (float)Math.sqrt(dist);
+
+            delta = ((boxes[0].x+boxes[1].x+boxes[2].x+boxes[3].x)-
+                     (boxes[4].x+boxes[5].x+boxes[6].x+boxes[7].x))/4;
+            dist = delta*delta;
+            delta = ((boxes[0].y+boxes[1].y+boxes[2].y+boxes[3].y)-
+                     (boxes[4].y+boxes[5].y+boxes[6].y+boxes[7].y))/4;
+            dist += delta*delta;
+            dist  = (float)Math.sqrt(dist);
+            // Note here that dist is the distance between center
+            // points, and sz is the sum of the length of the
+            // diagonals of the letter boxes.  In normal cases one
+            // would expect dist to be approximately equal to sz/2.
+            // So here we merge if the two characters are within four
+            // character widths of each other. If they are farther
+            // apart than that chances are it's a 'line break' or
+            // something similar where we will get better results
+            // merging seperately, and anyways with this much space
+            // between them the extra outline shouldn't hurt..
+            GeneralPath gp = new GeneralPath();
+            if (dist < sz) {
+                // Close enough to merge with previous char...
+                System.arraycopy(boxes, 0, chull, 0, 8);
+                int npts = makeConvexHull(chull, 8);
+                gp.moveTo(chull[0].x, chull[0].y);
+                for(int n=1; n<npts; n++)
+                    gp.lineTo(chull[n].x, chull[n].y);
+                gp.closePath();
+            } else {
+                // Merge all previous areas
+                mergeAreas(shape, areas, nAreas);
+                nAreas = 0; // Start fresh...
+
+                // Then just add box (add the previous char box if first pts)
+                if (i==2) {
+                    gp.moveTo(boxes[0].x, boxes[0].y);
+                    gp.lineTo(boxes[1].x, boxes[1].y);
+                    gp.lineTo(boxes[2].x, boxes[2].y);
+                    gp.lineTo(boxes[3].x, boxes[3].y);
+                    gp.closePath();
+                    shape.append(gp, false);
+                    gp.reset();
+                }
+                gp.moveTo(boxes[4].x, boxes[4].y);
+                gp.lineTo(boxes[5].x, boxes[5].y);
+                gp.lineTo(boxes[6].x, boxes[6].y);
+                gp.lineTo(boxes[7].x, boxes[7].y);
+                gp.closePath();
+            }
+            areas[nAreas++] = new Area(gp);
+        }
+
+        mergeAreas(shape, areas, nAreas);
+    }
+
+    public static void mergeAreas(GeneralPath shape,
+                                  Area []shapes, int nShapes) {
+        // Merge areas hierarchically, this means that while there are
+        // the same number of Area.add calls (n-1) the great majority
+        // of them are very simple combinations.  This helps to speed
+        // things up a tad...
+        while (nShapes > 1) {
+            int n=0;
+            for (int i=1; i<nShapes;i+=2) {
+                shapes[i-1].add(shapes[i]);
+                shapes[n++] = shapes[i-1];
+                shapes[i] = null;
+            }
+
+            // make sure we include the last one if odd.
+            if ((nShapes&0x1) == 1)
+                shapes[n-1].add(shapes[nShapes-1]);
+            nShapes = nShapes/2;
+        }
+        if (nShapes == 1)
+            shape.append(shapes[0], false);
+    }
+
+    /**
+     * Perform hit testing for coordinate at x, y.
+     *
+     * @param x the x coordinate of the point to be tested.
+     * @param y the y coordinate of the point to be tested.
+     *
+     * @return a TextHit object encapsulating the character index for
+     *     successful hits and whether the hit is on the character
+     *     leading edge.
+     */
+    public TextHit hitTestChar(float x, float y) {
+        syncLayout();
+
+        TextHit textHit = null;
+
+        int currentChar = 0;
+        for (int i = 0; i < gv.getNumGlyphs(); i++) {
+            Shape gbounds = gv.getGlyphLogicalBounds(i);
+            if (gbounds != null) {
+                Rectangle2D gbounds2d = gbounds.getBounds2D();
+                if (gbounds.contains(x, y)) {
+                    boolean isRightHalf =
+                        (x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d)));
+                    boolean isLeadingEdge = !isRightHalf;
+                    int charIndex = charMap[currentChar];
+                    textHit = new TextHit(charIndex, isLeadingEdge);
+                    return textHit;
+                }
+            }
+            currentChar += getCharacterCount(i, i);
+            if (currentChar >= charMap.length)
+                currentChar = charMap.length-1;
+        }
+        return textHit;
+    }
+
+//protected
+
+    /**
+     * Returns the GVTFont to use when rendering the specified
+     * character iterator.  This should already be set as an attribute
+     * on the aci.
+     *
+     * @return The GVTFont to use.
+     */
+    protected GVTFont getFont() {
+        aci.first();
+        GVTFont gvtFont = (GVTFont)aci.getAttribute(GVT_FONT);
+
+        if (gvtFont != null)
+            return gvtFont;
+
+        // shouldn't get here
+        return new AWTGVTFont(aci.getAttributes());
+    }
+
+    /**
+     * Returns a shape describing the overline decoration for a given ACI.
+     */
+    protected Shape getOverlineShape() {
+        double y = metrics.getOverlineOffset();
+        float overlineThickness = metrics.getOverlineThickness();
+
+        // need to move the overline a bit lower,
+        // not sure if this is correct behaviour or not
+        y += overlineThickness;
+
+        // Not certain what should be done here...
+        aci.first();
+        Float dy = (Float) aci.getAttribute(DY);
+        if (dy != null)
+            y += dy.floatValue();
+
+        Stroke overlineStroke =
+            new BasicStroke(overlineThickness);
+        Rectangle2D logicalBounds = gv.getLogicalBounds();
+
+        return overlineStroke.createStrokedShape(
+                           new Line2D.Double(
+                           logicalBounds.getMinX() + overlineThickness/2.0, 
offset.getY()+y,
+                           logicalBounds.getMaxX() - overlineThickness/2.0, 
offset.getY()+y));
+    }
+
+    /**
+     * Returns a shape describing the undeline decoration for a given ACI.
+     */
+    protected Shape getUnderlineShape() {
+
+        double y = metrics.getUnderlineOffset();
+        float underlineThickness = metrics.getUnderlineThickness();
+
+        // need to move the underline a bit lower,
+        // not sure if this is correct behaviour or not
+        y += underlineThickness*1.5;
+
+        BasicStroke underlineStroke =
+            new BasicStroke(underlineThickness);
+
+        // Not certain what should be done here...
+        aci.first();
+        Float dy = (Float) aci.getAttribute(DY);
+        if (dy != null)
+            y += dy.floatValue();
+
+        Rectangle2D logicalBounds = gv.getLogicalBounds();
+
+        return underlineStroke.createStrokedShape(
+                           new Line2D.Double(
+                           logicalBounds.getMinX() + underlineThickness/2.0, 
offset.getY()+y,
+                           logicalBounds.getMaxX() - underlineThickness/2.0, 
offset.getY()+y));
+    }
+
+    /**
+     * Returns a shape describing the strikethrough line for a given ACI.
+     */
+    protected Shape getStrikethroughShape() {
+        double y = metrics.getStrikethroughOffset();
+        float strikethroughThickness = metrics.getStrikethroughThickness();
+
+        Stroke strikethroughStroke =
+            new BasicStroke(strikethroughThickness);
+
+        // Not certain what should be done here...
+        aci.first();
+        Float dy = (Float) aci.getAttribute(DY);
+        if (dy != null)
+            y += dy.floatValue();
+
+        Rectangle2D logicalBounds = gv.getLogicalBounds();
+        return strikethroughStroke.createStrokedShape(
+                           new Line2D.Double(
+                           logicalBounds.getMinX() + 
strikethroughThickness/2.0, offset.getY()+y,
+                           logicalBounds.getMaxX() - 
strikethroughThickness/2.0, offset.getY()+y));
+    }
+
+    /**
+     * Explicitly lays out each of the glyphs in the glyph
+     * vector. This will handle any glyph position adjustments such as
+     * dx, dy and baseline offsets.  It will also handle vertical
+     * layouts.
+     */
+    protected void doExplicitGlyphLayout() {
+
+        this.gv.performDefaultLayout();
+
+        float baselineAscent
+            = vertical ?
+            (float) gv.getLogicalBounds().getWidth() :
+            (metrics.getAscent() + Math.abs(metrics.getDescent()));
+
+        int numGlyphs = gv.getNumGlyphs();
+
+        float[] gp = gv.getGlyphPositions(0, numGlyphs+1, null);
+        float verticalFirstOffset = 0f;
+        float horizontalFirstOffset = 0f;
+
+        boolean glyphOrientationAuto = isGlyphOrientationAuto();
+        int glyphOrientationAngle = 0;
+        if (!glyphOrientationAuto) {
+            glyphOrientationAngle = getGlyphOrientationAngle();
+        }
+        int i=0;
+        int aciStart = aci.getBeginIndex();
+        int aciIndex = 0;
+        char ch = aci.first();
+        int runLimit = aciIndex+aciStart;
+
+        Float x=null, y=null, dx=null, dy=null, rotation=null;
+        Object baseline=null;
+
+        float shift_x_pos = 0;
+        float shift_y_pos = 0;
+        float curr_x_pos = (float)offset.getX();
+        float curr_y_pos = (float)offset.getY();
+
+        Point2D.Float pos = new Point2D.Float();
+        boolean hasArabicTransparent = false;
+
+        while (i < numGlyphs) {
+            if (aciIndex+aciStart >= runLimit) {
+                runLimit = aci.getRunLimit(runAtts);
+                x        = (Float) aci.getAttribute(X);
+                y        = (Float) aci.getAttribute(Y);
+                dx       = (Float) aci.getAttribute(DX);
+                dy       = (Float) aci.getAttribute(DY);
+                rotation = (Float) aci.getAttribute(ROTATION);
+                baseline = aci.getAttribute(BASELINE_SHIFT);
+            }
+
+            GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
+
+            if (i==0) {
+                if (isVertical()) {
+                    if (glyphOrientationAuto) {
+                        if (isLatinChar(ch)) {
+                            // it will be rotated 90
+                            verticalFirstOffset = 0f;
+                        } else {
+                            // it won't be rotated
+                            float advY = gm.getVerticalAdvance();
+                            float asc  = metrics.getAscent();
+                            float dsc  = metrics.getDescent();
+                            verticalFirstOffset =  asc+(advY-(asc+dsc))/2;
+                        }
+                    } else {
+                        if (glyphOrientationAngle == 0) {
+                            float advY = gm.getVerticalAdvance();
+                            float asc  = metrics.getAscent();
+                            float dsc  = metrics.getDescent();
+                            verticalFirstOffset =  asc+(advY-(asc+dsc))/2;
+                        } else {
+                            // 90, 180, 270
+                            verticalFirstOffset = 0f;
+                        }
+                    }
+                } else {  // not vertical
+                    if ((glyphOrientationAngle == 270)) {
+                        horizontalFirstOffset =
+                            (float)gm.getBounds2D().getHeight();
+                    } else {
+                        // 0, 90, 180
+                        horizontalFirstOffset = 0;
+                    }
+                }
+            } else {  // not the first char
+                if (glyphOrientationAuto        &&
+                    (verticalFirstOffset == 0f) && !isLatinChar(ch)) {
+                    float advY = gm.getVerticalAdvance();
+                    float asc  = metrics.getAscent();
+                    float dsc  = metrics.getDescent();
+                    verticalFirstOffset =  asc + (advY - (asc+dsc))/2;
+                }
+            }
+
+            // ox and oy are origin adjustments for each glyph,
+            // computed on the basis of baseline-shifts, etc.
+            float ox = 0f;
+            float oy = 0f;
+            float glyphOrientationRotation = 0f;
+            float glyphRotation = 0f;
+
+            if (ch != CharacterIterator.DONE) {
+                if (vertical) {
+                    if (glyphOrientationAuto) {
+                        if (isLatinChar(ch)) {
+                            // If character is Latin, then rotate by
+                            // 90 degrees
+                            glyphOrientationRotation = (float) (Math.PI / 2f);
+                        } else {
+                            glyphOrientationRotation = 0f;
+                        }
+                    } else {
+                        glyphOrientationRotation = 
(float)Math.toRadians(glyphOrientationAngle);
+                    }
+                    if (textPath != null) {
+                        // if vertical and on a path, any x's are ignored
+                        x = null;
+                    }
+                } else {
+                    glyphOrientationRotation = 
(float)Math.toRadians(glyphOrientationAngle);
+                    if (textPath != null) {
+                        // if horizontal and on a path, any y's are ignored
+                        y = null;
+                    }
+                }
+
+                // calculate the total rotation for this glyph
+                if (rotation == null || rotation.isNaN()) {
+                    glyphRotation = glyphOrientationRotation;
+                } else {
+                    glyphRotation = (rotation.floatValue() +
+                                     glyphOrientationRotation);
+                }
+
+                if ((x != null) && !x.isNaN()) {
+                    if (i == 0)
+                        shift_x_pos = (float)(x.floatValue()-offset.getX());
+                    curr_x_pos = x.floatValue()-shift_x_pos;
+                }
+                if (dx != null && !dx.isNaN()) {
+                    curr_x_pos += dx.floatValue();
+                }
+
+                if ((y != null) && !y.isNaN()) {
+                    if (i == 0)
+                        shift_y_pos = (float)(y.floatValue()-offset.getY());
+                    curr_y_pos = y.floatValue()-shift_y_pos;
+                }
+                if (dy != null && !dy.isNaN()) {
+                    curr_y_pos += dy.floatValue();
+                } else if (i > 0) {
+                    curr_y_pos += gp[i*2 + 1]-gp[i*2 - 1];
+                }
+
+                float baselineAdjust = 0f;
+                if (baseline != null) {
+                    if (baseline instanceof Integer) {
+                        if (baseline==TextAttribute.SUPERSCRIPT_SUPER) {
+                            baselineAdjust = baselineAscent*0.5f;
+                        } else if (baseline==TextAttribute.SUPERSCRIPT_SUB) {
+                            baselineAdjust = -baselineAscent*0.5f;
+                        }
+                    } else if (baseline instanceof Float) {
+                        baselineAdjust = ((Float) baseline).floatValue();
+                    }
+                    if (vertical) {
+                        ox = baselineAdjust;
+                    } else {
+                        oy = -baselineAdjust;
+                    }
+                }
+
+                if (vertical) {
+                    // offset due to rotation of first character
+                    oy += verticalFirstOffset;
+
+                    if (glyphOrientationAuto) {
+                        if (isLatinChar(ch)) {
+                            ox += metrics.getStrikethroughOffset();
+                        } else {
+                            Rectangle2D glyphBounds
+                                = gv.getGlyphVisualBounds(i).getBounds2D();
+                            ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) -
+                                          glyphBounds.getWidth()/2);
+                        }
+                    } else {
+                        // center the character if it's not auto orient
+                        Rectangle2D glyphBounds
+                            = gv.getGlyphVisualBounds(i).getBounds2D();
+                        if (glyphOrientationAngle == 0) {
+                            ox -= (float)((glyphBounds.getMaxX() - gp[2*i]) -
+                                          glyphBounds.getWidth()/2);
+                        } else if (glyphOrientationAngle == 180) {
+                            ox += (float)((glyphBounds.getMaxX() - gp[2*i]) -
+                                          glyphBounds.getWidth()/2);
+                        } else if (glyphOrientationAngle == 90) {
+                            ox += metrics.getStrikethroughOffset();
+                        } else { // 270
+                            ox -= metrics.getStrikethroughOffset();
+                        }
+                    }
+                } else {
+                    ox += horizontalFirstOffset;
+                    if (glyphOrientationAngle == 90) {
+                        oy -= gm.getHorizontalAdvance();
+                    } else if (glyphOrientationAngle == 180) {
+                        oy -= metrics.getAscent();
+                    }
+                }
+            }
+
+            // set the new glyph position
+            pos.x = curr_x_pos+ox;
+            pos.y = curr_y_pos+oy;
+            gv.setGlyphPosition(i, pos);
+
+            // calculate the position of the next glyph
+            if (ArabicTextHandler.arabicCharTransparent(ch)) {
+                hasArabicTransparent = true;
+            } else {
+                // Apply the advance if the current char is not transparent
+                if (vertical) {
+                    float advanceY = 0;
+                    if (glyphOrientationAuto) {
+                        if (isLatinChar(ch)) {
+                            advanceY = gm.getHorizontalAdvance();
+                        } else {
+                            advanceY = gm.getVerticalAdvance();
+                        }
+                    } else {
+                        if ((glyphOrientationAngle ==   0) ||
+                            (glyphOrientationAngle == 180)) {
+                            advanceY = gm.getVerticalAdvance();
+                        } else if (glyphOrientationAngle == 90) {
+                            advanceY = gm.getHorizontalAdvance();
+                        } else { // 270
+                            advanceY = gm.getHorizontalAdvance();
+                            // need to translate so that the spacing
+                            // between chars is correct
+                            gv.setGlyphTransform
+                                (i, AffineTransform.getTranslateInstance
+                                 (0, advanceY));
+                        }
+                    }
+                    curr_y_pos += advanceY;
+                } else {
+                    float advanceX = 0;
+                    if (glyphOrientationAngle ==   0) {
+                        advanceX = gm.getHorizontalAdvance();
+                    } else if (glyphOrientationAngle == 180) {
+                        advanceX = gm.getHorizontalAdvance();
+                        // need to translate so that the spacing
+                        // between chars is correct
+                        gv.setGlyphTransform
+                            (i, AffineTransform.getTranslateInstance
+                             (advanceX, 0));
+                    } else {
+                        // 90, 270
+                        advanceX = gm.getVerticalAdvance();
+                    }
+                    curr_x_pos += advanceX;
+                }
+            }
+
+            // rotate the glyph
+            if (!epsEQ(glyphRotation,0)) {
+                AffineTransform glyphTransform = gv.getGlyphTransform(i);
+                if (glyphTransform == null) {
+                    glyphTransform = new AffineTransform();
+                }
+                AffineTransform rotAt;
+                // Make the 90Deg rotations slightly 'snap to'.
+                // Also use explicit matrix to avoid round-off.
+                if (epsEQ(glyphRotation, Math.PI/2)) {
+                    rotAt = new AffineTransform(0, 1, -1, 0, 0, 0);
+                } else if (epsEQ(glyphRotation, Math.PI)) {
+                    rotAt = new AffineTransform(-1, 0, 0, -1, 0, 0);
+                } else if (epsEQ(glyphRotation, 3*Math.PI/2)) {
+                    rotAt = new AffineTransform(0, -1, 1, 0, 0, 0);
+                } else {
+                    rotAt = AffineTransform.getRotateInstance(glyphRotation);
+                }
+                glyphTransform.concatenate(rotAt);
+                gv.setGlyphTransform(i, glyphTransform);
+            }
+
+            aciIndex += gv.getCharacterCount(i,i);
+            if (aciIndex >= charMap.length)
+                aciIndex = charMap.length-1;
+            ch = aci.setIndex(aciIndex+aciStart);
+            i++;
+        }
+        // Update last glyph pos
+        pos.x = curr_x_pos;
+        pos.y = curr_y_pos;
+        gv.setGlyphPosition(i, pos);
+
+        advance = new Point2D.Float((float)(curr_x_pos - offset.getX()),
+                                    (float)(curr_y_pos - offset.getY()));
+
+
+        // Do a last pass positioning the transparent/mark glyphs on the
+        // base glyphs.
+        if (hasArabicTransparent) {
+            ch = aci.first();
+            aciIndex = 0;
+            i=0;
+            int transparentStart = -1;
+            while (i < numGlyphs) {
+                if (ArabicTextHandler.arabicCharTransparent(ch)) {
+                    if (transparentStart == -1) transparentStart = i;
+                } else {
+                    if (transparentStart != -1) {
+                        Point2D         loc   = gv.getGlyphPosition(i);
+                        GVTGlyphMetrics gm    = gv.getGlyphMetrics(i);
+                        int tyS=0, txS=0;      // these never changed ??     
todo
+                        float advX=0, advY=0;
+                        if (vertical) {
+                            if (glyphOrientationAuto ||
+                                (glyphOrientationAngle == 90))
+                                advY = gm.getHorizontalAdvance();
+                            else if (glyphOrientationAngle == 270)
+                                advY = 0;
+                            else if (glyphOrientationAngle == 0)
+                                advX = gm.getHorizontalAdvance();
+                            else // 180
+                                advX = -gm.getHorizontalAdvance();
+                        } else {
+                            if (glyphOrientationAngle ==   0)
+                                advX = gm.getHorizontalAdvance();
+                            else if (glyphOrientationAngle == 90)
+                                advY = gm.getHorizontalAdvance();
+                            else if (glyphOrientationAngle == 180)
+                                advX = 0;
+                            else // 270
+                                advY = -gm.getHorizontalAdvance();
+                        }
+                        float baseX = (float)(loc.getX()+advX);
+                        float baseY = (float)(loc.getY()+advY);
+                        for (int j=transparentStart; j<i; j++) {
+                            Point2D         locT = gv.getGlyphPosition(j);
+                            GVTGlyphMetrics gmT  = gv.getGlyphMetrics(j);
+                            float           locX = (float)locT.getX();
+                            float           locY = (float)locT.getY();
+                            float           tx=0, ty=0;
+                            float           advT = gmT.getHorizontalAdvance();
+                            if (vertical) {
+                                if (glyphOrientationAuto ||
+                                    (glyphOrientationAngle == 90))
+                                    locY = baseY-advT;
+                                else if (glyphOrientationAngle == 270)
+                                    locY = baseY+advT;
+                                else if (glyphOrientationAngle == 0)
+                                    locX = baseX-advT;
+                                else // 180deg
+                                    locX = baseX+advT;
+                            } else {
+                                if (glyphOrientationAngle ==   0)
+                                    locX = baseX-advT;
+                                else if (glyphOrientationAngle == 90)
+                                    locY = baseY-advT;
+                                else if (glyphOrientationAngle == 180)
+                                    locX = baseX+advT;
+                                else // 270
+                                    locY = baseY+advT;
+                            }
+
+                            locT = new Point2D.Double(locX, locY);
+                            gv.setGlyphPosition(j, locT);
+                            if ((txS != 0) || (tyS != 0)) {        // because 
txS, tyS are set to 0 and not
+                                AffineTransform at;                // changed, 
this path is never used  todo
+                                at = AffineTransform.getTranslateInstance
+                                    (tx,ty);
+                                at.concatenate(gv.getGlyphTransform(i));
+                                gv.setGlyphTransform(i, at);
+                            }
+                        }
+                        transparentStart = -1;
+                    }
+                }
+                aciIndex += gv.getCharacterCount(i,i);
+                if (aciIndex >= charMap.length)
+                    aciIndex = charMap.length-1;
+                ch = aci.setIndex(aciIndex+aciStart);
+                i++;
+            }
+
+        }
+
+
+        layoutApplied  = true;
+        spacingApplied = false;
+        glyphAdvances  = null;
+        pathApplied    = false;
+    }
+
+    /**
+     * Does any spacing adjustments that may have been specified.
+     */
+    protected void adjustTextSpacing() {
+
+        if (spacingApplied)
+            // Nothing to do...
+            return;
+
+        if (!layoutApplied)
+            // Must have clean layout to do spacing...
+            doExplicitGlyphLayout();
+
+        aci.first();
+        Boolean customSpacing =  (Boolean) aci.getAttribute(
+               GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
+        if ((customSpacing != null) && customSpacing.booleanValue()) {
+            advance = doSpacing
+                ((Float) aci.getAttribute
+                 (GVTAttributedCharacterIterator.TextAttribute.KERNING),
+                 (Float) aci.getAttribute
+                 (GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING),
+                 (Float) aci.getAttribute
+                 (GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING));
+            // Basic layout is now messed up...
+            layoutApplied  = false;
+        }
+
+        // This will clear layoutApplied if it mucks with the current
+        // character positions.
+        applyStretchTransform(!adjSpacing);
+
+        spacingApplied = true;
+        pathApplied    = false;
+    }
+
+    /**
+     * Performs any spacing adjustments required and returns the new advance
+     * value.
+     *
+     * @param kern The kerning adjustment to apply to the space
+     * between each char.
+     * @param letterSpacing The amount of spacing required between each char.
+     * @param wordSpacing The amount of spacing required between each word.  */
+    protected Point2D doSpacing(Float kern,
+                                Float letterSpacing,
+                                Float wordSpacing) {
+        boolean autoKern = true;
+        boolean doWordSpacing = false;
+        boolean doLetterSpacing = false;
+        float kernVal = 0f;
+        float letterSpacingVal = 0f;
+
+        if ((kern != null) && (!kern.isNaN())) {
+            kernVal = kern.floatValue();
+            autoKern = false;
+        }
+        if ((letterSpacing != null) && (!letterSpacing.isNaN())) {
+            letterSpacingVal = letterSpacing.floatValue();
+            doLetterSpacing = true;
+        }
+        if ((wordSpacing != null) && (!wordSpacing.isNaN())) {
+            doWordSpacing = true;
+        }
+
+        int numGlyphs = gv.getNumGlyphs();
+
+        float dx = 0f;
+        float dy = 0f;
+        Point2D[] newPositions = new Point2D[numGlyphs+1];
+        Point2D prevPos = gv.getGlyphPosition(0);
+        int prevCode    = gv.getGlyphCode(0);
+        float x = (float) prevPos.getX();
+        float y = (float) prevPos.getY();
+
+        Point2D lastCharAdvance
+            = new Point2D.Double(advance.getX() - 
(gv.getGlyphPosition(numGlyphs-1).getX() - x),
+                                 advance.getY() - 
(gv.getGlyphPosition(numGlyphs-1).getY() - y));
+
+        try {
+            GVTFont font = gv.getFont();
+            // do letter spacing first
+            if ((numGlyphs > 1) && (doLetterSpacing || !autoKern)) {
+                for (int i=1; i<=numGlyphs; ++i) {
+                    Point2D gpos = gv.getGlyphPosition(i);
+                    int     currCode;
+                    currCode = (i == numGlyphs)?-1:gv.getGlyphCode(i);
+                    dx = (float)gpos.getX()-(float)prevPos.getX();
+                    dy = (float)gpos.getY()-(float)prevPos.getY();
+                    if (autoKern) {
+                        if (vertical) dy += letterSpacingVal;
+                        else          dx += letterSpacingVal;
+                    } else {
+                        // apply explicit kerning adjustments,
+                        // removing any auto-kern values
+                        if (vertical) {
+                            float vKern = 0;
+                            if (currCode != -1)
+                                vKern = font.getVKern(prevCode, currCode);
+                            dy += kernVal - vKern + letterSpacingVal;
+                        } else {
+                            float hKern = 0;
+                            if (currCode != -1)
+                                hKern = font.getHKern(prevCode, currCode);
+                            dx += kernVal - hKern + letterSpacingVal;
+                        }
+                    }
+                    x += dx;
+                    y += dy;
+                    newPositions[i] = new Point2D.Float(x, y);
+                    prevPos = gpos;
+                    prevCode = currCode;
+                }
+
+                for (int i=1; i<=numGlyphs; ++i) { // assign the new positions
+                    if (newPositions[i] != null) {
+                        gv.setGlyphPosition(i, newPositions[i]);
+                    }
+                }
+            }
+
+             // adjust the advance of the last character
+            if (vertical) {
+                lastCharAdvance.setLocation
+                    (lastCharAdvance.getX(),
+                     lastCharAdvance.getY() + kernVal + letterSpacingVal);
+            } else {
+                lastCharAdvance.setLocation
+                    (lastCharAdvance.getX() + kernVal + letterSpacingVal,
+                     lastCharAdvance.getY());
+            }
+
+            // now do word spacing
+            dx = 0f;
+            dy = 0f;
+            prevPos = gv.getGlyphPosition(0);
+            x = (float) prevPos.getX();
+            y = (float) prevPos.getY();
+
+            if ((numGlyphs > 1) && (doWordSpacing)) {
+                for (int i = 1; i < numGlyphs; i++) {
+                    Point2D gpos = gv.getGlyphPosition(i);
+                    dx = (float)gpos.getX()-(float)prevPos.getX();
+                    dy = (float)gpos.getY()-(float)prevPos.getY();
+                    boolean inWS = false;
+                    // while this is whitespace, increment
+                    int beginWS = i;
+                    int endWS = i;
+                    GVTGlyphMetrics gm = gv.getGlyphMetrics(i);
+
+                    // BUG: gm.isWhitespace() fails for latin SPACE glyph!
+                    while ((gm.getBounds2D().getWidth()<0.01d) || 
gm.isWhitespace()) {
+                        if (!inWS) inWS = true;
+                        if (i == numGlyphs-1) {
+                            // white space at the end
+                            break;
+                        }
+                        ++i;
+                        ++endWS;
+                        gpos = gv.getGlyphPosition(i);
+                        gm = gv.getGlyphMetrics(i);
+                    }
+
+                    if ( inWS ) {  // apply wordSpacing
+                        int nWS = endWS-beginWS;
+                        float px = (float) prevPos.getX();
+                        float py = (float) prevPos.getY();
+                        dx = (float) (gpos.getX() - px)/(nWS+1);
+                        dy = (float) (gpos.getY() - py)/(nWS+1);
+                        if (vertical) {
+                            dy += wordSpacing.floatValue()/(nWS+1);
+                        } else {
+                            dx += wordSpacing.floatValue()/(nWS+1);
+                        }
+                        for (int j=beginWS; j<=endWS; ++j) {
+                            x += dx;
+                            y += dy;
+                            newPositions[j] = new Point2D.Float(x, y);
+                        }
+                    } else {
+                        dx = (float) (gpos.getX()-prevPos.getX());
+                        dy = (float) (gpos.getY()-prevPos.getY());
+                        x += dx;
+                        y += dy;
+                        newPositions[i] = new Point2D.Float(x, y);
+                    }
+                    prevPos = gpos;
+                }
+                Point2D gPos = gv.getGlyphPosition(numGlyphs);
+                x += (float) (gPos.getX()-prevPos.getX());
+                y += (float) (gPos.getY()-prevPos.getY());
+                newPositions[numGlyphs] = new Point2D.Float(x, y);
+
+                for (int i=1; i<=numGlyphs; ++i) { // assign the new positions
+                    if (newPositions[i] != null) {
+                        gv.setGlyphPosition(i, newPositions[i]);
+                    }
+                }
+            }
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        // calculate the new advance
+        double advX = gv.getGlyphPosition(numGlyphs-1).getX()
+                     - gv.getGlyphPosition(0).getX();
+        double advY = gv.getGlyphPosition(numGlyphs-1).getY()
+                     - gv.getGlyphPosition(0).getY();
+        Point2D newAdvance = new Point2D.Double(advX + lastCharAdvance.getX(),
+                                                advY + lastCharAdvance.getY());
+        return newAdvance;
+    }
+
+    /**
+     * Stretches the text so that it becomes the specified length.
+     *
+     * @param stretchGlyphs if true xScale, yScale will be applied to
+     *                      each glyphs transform.
+     */
+    protected void applyStretchTransform(boolean stretchGlyphs) {
+        if ((xScale == 1) && (yScale==1))
+            return;
+
+        AffineTransform scaleAT =
+            AffineTransform.getScaleInstance(xScale, yScale);
+
+        int numGlyphs = gv.getNumGlyphs();
+        float [] gp   = gv.getGlyphPositions(0, numGlyphs+1, null);
+
+        float initX   = gp[0];
+        float initY   = gp[1];
+        Point2D.Float pos = new Point2D.Float();
+        for (int i = 0; i <= numGlyphs; i++) {
+            float dx = gp[2*i]  -initX;
+            float dy = gp[2*i+1]-initY;
+            pos.x = initX+dx*xScale;
+            pos.y = initY+dy*yScale;
+            gv.setGlyphPosition(i, pos);
+
+            if ((stretchGlyphs) && (i != numGlyphs)) {
+                // stretch the glyph
+                AffineTransform glyphTransform = gv.getGlyphTransform(i);
+                if (glyphTransform != null) {
+                    glyphTransform.preConcatenate(scaleAT);
+                    gv.setGlyphTransform(i, glyphTransform);
+                } else {
+                    gv.setGlyphTransform (i, scaleAT);
+                }
+            }
+        }
+
+        advance = new Point2D.Float((float)(advance.getX()*xScale),
+                                    (float)(advance.getY()*yScale));
+        // Basic layout is now messed up...
+        layoutApplied  = false;
+    }
+
+    /**
+     * If this layout is on a text path, positions the characters
+     * along the path.
+     */
+    protected void doPathLayout() {
+        if (pathApplied)
+            return;
+
+        if (!spacingApplied)
+            // This will layout the text if needed.
+            adjustTextSpacing();
+
+        getGlyphAdvances();
+
+        // if doesn't have an attached text path, just return
+        if (textPath == null) {
+            // We applied the empty path (i.e. do nothing).
+            pathApplied = true;
+            return;
+        }
+
+
+        boolean horizontal = !isVertical();
+
+        boolean glyphOrientationAuto = isGlyphOrientationAuto();
+        int glyphOrientationAngle = 0;
+        if (!glyphOrientationAuto) {
+            glyphOrientationAngle = getGlyphOrientationAngle();
+        }
+
+        float pathLength  = textPath.lengthOfPath();
+        float startOffset = textPath.getStartOffset();
+        int   numGlyphs   = gv.getNumGlyphs();
+
+        // make sure all glyphs visible again, this maybe just a change in
+        // offset so they may have been made invisible in a previous
+        // pathLayout call
+        for (int i = 0; i < numGlyphs; i++) {
+            gv.setGlyphVisible(i, true);
+        }
+
+        // calculate the total length of the glyphs, this will become be
+        // the length along the path that is used by the text
+        float glyphsLength;
+        if (horizontal) {
+            glyphsLength = (float) gv.getLogicalBounds().getWidth();
+        } else {
+            glyphsLength = (float) gv.getLogicalBounds().getHeight();
+        }
+
+        // check that pathLength and glyphsLength are not 0
+        if (pathLength == 0f || glyphsLength == 0f) {
+            // We applied the empty path.
+            pathApplied = true;
+            textPathAdvance = advance;
+            return;
+        }
+
+        // the current start point of the character on the path
+        // calculate the offset of the first glyph the offset will be
+        // 0 if the glyph is on the path (ie. not adjusted by a dy or
+        // dx)
+        Point2D firstGlyphPosition = gv.getGlyphPosition(0);
+        float glyphOffset = 0;   // offset perpendicular to path
+        float currentPosition;
+        if (horizontal) {
+            glyphOffset     = (float)(firstGlyphPosition.getY());
+            currentPosition = (float)(firstGlyphPosition.getX() + startOffset);
+        } else {
+            glyphOffset     = (float)(firstGlyphPosition.getX());
+            currentPosition = (float)(firstGlyphPosition.getY() + startOffset);
+        }
+
+        char ch = aci.first();
+        int start       = aci.getBeginIndex();
+        int currentChar = 0;
+        int lastGlyphDrawn = -1;
+        float lastGlyphAdvance = 0;
+        // iterate through the GlyphVector placing each glyph
+        for (int i = 0; i < numGlyphs; i++) {
+
+            Point2D currentGlyphPos = gv.getGlyphPosition(i);
+
+            // calculate the advance and offset for the next glyph, do it
+            // now before we modify the current glyph position
+
+            float glyphAdvance = 0;  // along path
+            float nextGlyphOffset = 0;  // perpendicular to path eg dy or dx
+            Point2D nextGlyphPosition = gv.getGlyphPosition(i+1);
+            if (horizontal) {
+                glyphAdvance    = (float)(nextGlyphPosition.getX() -
+                                          currentGlyphPos.getX());
+                nextGlyphOffset = (float)(nextGlyphPosition.getY() -
+                                          currentGlyphPos.getY());
+            } else {
+                glyphAdvance    = (float)(nextGlyphPosition.getY() -
+                                          currentGlyphPos.getY());
+                nextGlyphOffset = (float)(nextGlyphPosition.getX() -
+                                          currentGlyphPos.getX());
+            }
+
+            // calculate the center line position for the glyph
+            Rectangle2D glyphBounds = gv.getGlyphOutline(i).getBounds2D();
+            float glyphWidth = (float) glyphBounds.getWidth();
+            float glyphHeight = (float) glyphBounds.getHeight();
+            float glyphMidX = 0;
+            if (glyphWidth > 0) {
+                glyphMidX  = (float)(glyphBounds.getX()+glyphWidth/2f);
+                glyphMidX -= (float)currentGlyphPos.getX();
+            }
+
+            float glyphMidY=0;
+            if (glyphHeight > 0) {
+                glyphMidY  = (float)(glyphBounds.getY()+glyphHeight/2f);
+                glyphMidY -= (float)currentGlyphPos.getY();
+            }
+
+            float charMidPos;
+            if (horizontal) {
+                charMidPos = currentPosition + glyphMidX;
+            } else {
+                charMidPos = currentPosition + glyphMidY;
+            }
+
+            // Calculate the actual point to place the glyph around
+            Point2D charMidPoint = textPath.pointAtLength(charMidPos);
+
+            // Check if the glyph is actually on the path
+            if (charMidPoint != null) {
+
+                // Calculate the normal to the path (midline of glyph)
+                float angle = textPath.angleAtLength(charMidPos);
+
+                // Define the transform of the glyph
+                AffineTransform glyphPathTransform = new AffineTransform();
+
+                // rotate midline of glyph to be normal to path
+                if (horizontal) {
+                    glyphPathTransform.rotate(angle);
+                } else {
+                    glyphPathTransform.rotate(angle-(Math.PI/2));
+                }
+
+                // re-apply any offset eg from tspan, or spacing adjust
+                if (horizontal) {
+                    glyphPathTransform.translate(0, glyphOffset);
+                } else {
+                    glyphPathTransform.translate(glyphOffset, 0);
+                }
+
+                // translate glyph backwards so we rotate about the
+                // center of the glyph
+                if (horizontal) {
+                    glyphPathTransform.translate(-glyphMidX, 0f);
+                } else {
+                    glyphPathTransform.translate(0f, -glyphMidY);
+                }
+
+                // set the new glyph position and transform
+                AffineTransform glyphTransform = gv.getGlyphTransform(i);
+                if (glyphTransform != null) {
+                    glyphPathTransform.concatenate(glyphTransform);
+                }
+
+                gv.setGlyphTransform(i, glyphPathTransform);
+                gv.setGlyphPosition (i, charMidPoint);
+                // keep track of the last glyph drawn to make calculating the
+                // textPathAdvance value easier later
+                lastGlyphDrawn = i;
+                lastGlyphAdvance = glyphAdvance;
+
+            } else {
+                // not on path so don't render
+                gv.setGlyphVisible(i, false);
+            }
+            currentPosition += glyphAdvance;
+            glyphOffset += nextGlyphOffset;
+            currentChar += gv.getCharacterCount(i,i);
+            if (currentChar >= charMap.length)
+                currentChar = charMap.length-1;
+            ch = aci.setIndex(currentChar+start);
+        }
+
+        // store the position where a following glyph should be drawn,
+        // note: this will only be used if the following text layout is not
+        //       on a text path
+        if (lastGlyphDrawn > -1) {
+            Point2D lastGlyphPos = gv.getGlyphPosition(lastGlyphDrawn);
+            if (horizontal) {
+                textPathAdvance = new Point2D.Double
+                    (lastGlyphPos.getX()+lastGlyphAdvance,
+                     lastGlyphPos.getY());
+            } else {
+                textPathAdvance = new Point2D.Double
+                    (lastGlyphPos.getX(),
+                     lastGlyphPos.getY()+lastGlyphAdvance);
+            }
+        } else {
+            textPathAdvance = new Point2D.Double(0,0);
+        }
+
+        // The default layout is junk now...
+        layoutApplied  = false;
+        // The spacing stuff is junk now.
+        spacingApplied = false;
+        pathApplied    = true;
+    }
+
+    /**
+     * Returns true if the specified character is within one of the Latin
+     * unicode character blocks.
+     *
+     * @param c The char to test.
+     *
+     * @return True if c is latin.
+     */
+    protected boolean isLatinChar(char c) {
+
+        if ( c < 255 && Character.isLetterOrDigit( c )){
+            // cheap quick check, should catch most lation-chars
+            return true;
+        }
+
+        Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
+
+        if (block == Character.UnicodeBlock.BASIC_LATIN ||
+            block == Character.UnicodeBlock.LATIN_1_SUPPLEMENT ||
+            block == Character.UnicodeBlock.LATIN_EXTENDED_ADDITIONAL ||
+            block == Character.UnicodeBlock.LATIN_EXTENDED_A ||
+            block == Character.UnicodeBlock.LATIN_EXTENDED_B ||
+            block == Character.UnicodeBlock.ARABIC ||
+            block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_A ||
+            block == Character.UnicodeBlock.ARABIC_PRESENTATION_FORMS_B) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns whether or not the vertical glyph orientation value is "auto".
+     */
+    protected boolean isGlyphOrientationAuto() {
+        if (!isVertical()) return false;
+        aci.first();
+        Integer vOrient = (Integer)aci.getAttribute(VERTICAL_ORIENTATION);
+        if (vOrient != null) {
+            return (vOrient == ORIENTATION_AUTO);
+        }
+        return true;
+    }
+
+    /**
+     * Returns the value of the vertical glyph orientation angle. This will be
+     * one of 0, 90, 180 or 270.
+     */
+    protected int getGlyphOrientationAngle() {
+
+        int glyphOrientationAngle = 0;
+
+        aci.first();
+        Float angle;
+
+        if (isVertical()) {
+            angle = (Float)aci.getAttribute(VERTICAL_ORIENTATION_ANGLE);
+        } else {
+            angle = (Float)aci.getAttribute(HORIZONTAL_ORIENTATION_ANGLE);
+        }
+
+        if (angle != null) {
+            glyphOrientationAngle = (int)angle.floatValue();
+        }
+
+        // if not one of 0, 90, 180 or 270, round to nearest value
+        if ((glyphOrientationAngle !=   0) || (glyphOrientationAngle !=  90) 
||       // todo - this logic expression
+            (glyphOrientationAngle != 180) || (glyphOrientationAngle != 270)) 
{       // is true for all values.....
+
+            while (glyphOrientationAngle < 0) {
+                glyphOrientationAngle += 360;
+            }
+
+            while (glyphOrientationAngle >= 360) {
+                glyphOrientationAngle -= 360;
+            }
+
+            if ((glyphOrientationAngle <= 45) ||
+                (glyphOrientationAngle > 315)) {
+                glyphOrientationAngle = 0;
+            } else if ((glyphOrientationAngle > 45) &&
+                       (glyphOrientationAngle <= 135)) {
+                glyphOrientationAngle = 90;
+            } else if ((glyphOrientationAngle > 135) &&
+                       (glyphOrientationAngle <= 225)) {
+                glyphOrientationAngle = 180;
+            } else {
+                glyphOrientationAngle = 270;
+            }
+        }
+        return glyphOrientationAngle;
+    }
+
+    /**
+     * Return true is the character index is represented by glyphs
+     * in this layout.
+     *
+     * @param index index of the character in the ACI.
+     * @return true if the layout represents that character.
+     */
+    public boolean hasCharacterIndex(int index){
+
+        for (int n=0; n<charMap.length; n++) {
+            if (index == charMap[n])
+                return true;
+        }
+        return false;
+    }
+
+    /**
+     * Return true if this text run represents
+     * an alt glyph.
+     */
+    public boolean isAltGlyph(){
+        return this.isAltGlyph;
+    }
+
+    @Override
+    public boolean isReversed(){
+        return gv.isReversed();
+    }
+
+    @Override
+    public void maybeReverse(boolean mirror){
+        gv.maybeReverse(mirror);
+    }
+}

Modified: 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Location.java
URL: 
http://svn.apache.org/viewvc/xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Location.java?rev=1647590&r1=1647589&r2=1647590&view=diff
==============================================================================
--- 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Location.java
 (original)
+++ 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Location.java
 Tue Dec 23 15:10:45 2014
@@ -19,8 +19,9 @@
 package org.apache.batik.bridge;
 
 import java.net.URL;
+
+import org.apache.batik.anim.dom.SVGOMDocument;
 import org.apache.batik.bridge.BridgeContext;
-import org.apache.batik.dom.svg.SVGOMDocument;
 import org.apache.batik.dom.AbstractDocument;
 
 /**

Added: 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Mark.java
URL: 
http://svn.apache.org/viewvc/xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Mark.java?rev=1647590&view=auto
==============================================================================
--- 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Mark.java
 (added)
+++ 
xmlgraphics/batik/branches/submodules_cyclic_deps/sources/org/apache/batik/bridge/Mark.java
 Tue Dec 23 15:10:45 2014
@@ -0,0 +1,40 @@
+/*
+
+   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.
+
+ */
+package org.apache.batik.bridge;
+
+/**
+ * Marker interface, mostly, that encapsulates information about a
+ * selection gesture.
+ *
+ * @author <a href="mailto:bill.hane...@ireland.sun.com";>Bill Haneman</a>
+ * @version $Id$
+ */
+public interface Mark {
+    /*
+     * Return the TextNode this Mark is associated with
+     */
+    TextNode getTextNode();
+
+    /**
+     * Returns the index of the character that has been hit.
+     *
+     * @return The character index.
+     */
+    int getCharIndex();
+}


Reply via email to