Package org.apache.batik.gvt.text

Source Code of org.apache.batik.gvt.text.GlyphLayout$ReorderedCharacterIterator

/*****************************************************************************
* Copyright (C) The Apache Software Foundation. All rights reserved.        *
* ------------------------------------------------------------------------- *
* This software is published under the terms of the Apache Software License *
* version 1.1, a copy of which has been included with this distribution in  *
* the LICENSE file.                                                         *
*****************************************************************************/

package org.apache.batik.gvt.text;

import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.font.TextAttribute;
import java.awt.font.GlyphVector;
import java.awt.font.GlyphMetrics;
import java.awt.font.LineMetrics;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.text.AttributedString;

import org.apache.batik.gvt.GraphicsNodeRenderContext;
import org.apache.batik.gvt.text.TextSpanLayout;
import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
import org.apache.batik.gvt.text.TextHit;
import org.apache.batik.gvt.font.GVTGlyphVector;
import org.apache.batik.gvt.font.GVTLineMetrics;
import org.apache.batik.gvt.font.GVTFont;
import org.apache.batik.gvt.font.AWTGVTFont;

import java.util.Map;
import java.util.Vector;

/**
* Implementation of TextSpanLayout which uses java.awt.font.GlyphVector.
* @see org.apache.batik.gvt.TextSpanLayout.
*
* @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
* @version $Id: GlyphLayout.java,v 1.9 2001/04/29 08:22:56 dino Exp $
*/
public class GlyphLayout implements TextSpanLayout {

   // private GlyphVector gv;
  //  private Font font;
    private GVTGlyphVector gv;
    private GVTFont font;
    private GVTLineMetrics metrics;
    private AttributedCharacterIterator aci;
    private CharacterIterator ci;
    private FontRenderContext frc;
    private AffineTransform transform;
    private Point2D advance;
    private Point2D offset;
    private Point2D prevCharPosition;
    protected Shape[] glyphLogicalBounds;
    protected Point2D[] glyphPositions;

    /**
     * Creates the specified text layout using the
     * specified AttributedCharacterIterator and rendering context.
     * @param aci the AttributedCharacterIterator whose text is to
     *  be laid out
     * @param frc the FontRenderContext to use for generating glyphs.
     */
    public GlyphLayout(AttributedCharacterIterator aci, Point2D offset,
                          FontRenderContext frc) {

        this.aci = (AttributedCharacterIterator) aci.clone();

//         System.out.print("creating GlyphLayout for: '");
//          for (char c = aci.first(); c != aci.DONE; c = aci.next()) {
//              System.out.print(c);
//          }
//          System.out.println("',  at offset: " + offset.getX() + ", " + offset.getY());

        this.frc = frc;
        this.font = getFont(aci);
        this.metrics = font.getLineMetrics(
                   this.aci, this.aci.getBeginIndex(), this.aci.getEndIndex(), frc);

        ci = new ReorderedCharacterIterator(this.aci);
     //   GVTGlyphVector gvtgv = font.createGlyphVector(frc, ci);
     //   this.gv = gvtgv.getAWTGlyphVector();
        this.gv = font.createGlyphVector(frc, ci);
        this.gv.performDefaultLayout();
        this.glyphPositions = new Point2D.Float[gv.getNumGlyphs()];
        this.offset = offset;
        this.transform = null;
        doExplicitGlyphLayout(false);
        adjustTextSpacing();
        doPathLayout();
        computeGlyphLogicalBounds();
    }

    /**
     * Paints the specified text layout using the
     * specified Graphics2D and rendering context.
     * @param g2d the Graphics2D to use
     * @param x the x position of the rendered layout origin.
     * @param y the y position of the rendered layout origin.
     */
    public void draw(Graphics2D g2d, GraphicsNodeRenderContext context) {
        AffineTransform t;
        if (transform != null) {
            t = g2d.getTransform();
            g2d.transform(transform);
            gv.draw(g2d, context, aci);
          //  g2d.drawGlyphVector(gv, 0f, 0f);
            g2d.setTransform(t);
        } else {
           gv.draw(g2d, context, aci);
          // g2d.drawGlyphVector(gv, 0f, 0f);
        }
    }

    /**
     * Returns the outline of the completed glyph layout, transformed
     * by an AffineTransform.
     * @param t an AffineTransform to apply to the outline before returning it.
     */
    public Shape getOutline() {
        Shape s = gv.getOutline(0f, 0f);
        if (transform != null) {
            s = transform.createTransformedShape(s);
        }
        return s;
    }

    /**
     * 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 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) {
       // System.out.println("Offset set to "+offset);
        this.offset = offset;
        this.gv.performDefaultLayout();
        doExplicitGlyphLayout(true);
        adjustTextSpacing();
        doPathLayout();
        computeGlyphLogicalBounds();
    }

    /**
     * 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. <tt>DECORATION_UNDERLINE | DECORATION_STRIKETHROUGH</tt>
     */
    public Shape getDecorationOutline(int decorationType) {
        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);
        }
        if (transform != null) {
            g = transform.createTransformedShape(g);
        }
        return g;
    }

    /**
     * Returns the rectangular bounds of the completed glyph layout.
     */
    public Rectangle2D getBounds() {
        Rectangle2D bounds = gv.getVisualBounds();
        if (transform != null) {
            bounds = transform.createTransformedShape(bounds).getBounds2D();
        }
        return bounds;
    }

    /**
     * Returns the rectangular bounds of the completed glyph layout,
     * inclusive of "decoration" (underline, overline, etc.)
     */
    public Rectangle2D getDecoratedBounds() {
        return getBounds().createUnion(
            getDecorationOutline(
                DECORATION_ALL).getBounds2D());
    }

    /**
     * Returns the dimension of the completed glyph layout in the
     * primary text advance direction (e.g. width, for RTL or LTR text).
     * (This is the dimension that should be used for positioning
     * adjacent layouts.)
     */
    public float getAdvance() {
        return (float) advance.getX();
    }

    /**
     * Returns the current text position at the completion
     * of glyph layout.
     * (This is the position that should be used for positioning
     * adjacent layouts.)
     */
    public Point2D getAdvance2D() {
        return advance;
    }

    /**
     * Returns a Shape which encloses the currently selected glyphs
     * as specified by glyph indices <tt>begin/tt> and <tt>end</tt>.
     * @param begin the index of the first glyph in the contiguous selection.
     * @param end the index of the last glyph in the contiguous selection.
     */
    public Shape getLogicalHighlightShape(int begin, int end) {

        Shape shape = null;
        begin = Math.max(0, begin);
        end = Math.min(end, gv.getNumGlyphs());

        if (begin == 0 && end == gv.getNumGlyphs()) {
            shape = new GeneralPath(getBounds());
        } else {
            for (int i=begin; i<end; ++i) {

                Shape gbounds = getGlyphLogicalBounds(i);
                Rectangle2D gbounds2d = gbounds.getBounds2D();

                if (shape == null) {
                   shape = new GeneralPath(gbounds2d);
                } else {
                   ((GeneralPath) shape).append(gbounds2d, false);
                }
            }
            if (transform != null) {
                shape = transform.createTransformedShape(shape);
            }
        }
        return shape;
    }

    /**
     * Perform hit testing for coordinate at x, y.
     * @return a TextHit object encapsulating the character index for
     *     successful hits and whether the hit is on the character
     *     leading edge.
     * @param x the x coordinate of the point to be tested.
     * @param y the y coordinate of the point to be tested.
     */
    public TextHit hitTestChar(float x, float y) {
        int begin = 0;
        int end = gv.getNumGlyphs();
        TextHit textHit = null;
        GlyphMetrics gm;
        float maxX = (float) gv.getVisualBounds().getX();
        if (transform != null) {
            try {
                Point2D p = new Point2D.Float(x, y);
                transform.inverseTransform(p, p);
                x = (float) p.getX();
                y = (float) p.getY();
            } catch (java.awt.geom.NoninvertibleTransformException nite) {;}
        }
        for (int i=begin; i<end; ++i) {
            Shape gbounds = getGlyphLogicalBounds(i);

            Rectangle2D gbounds2d = gbounds.getBounds2D();

            if (gbounds2d.getX()+gbounds2d.getWidth() > maxX) {
                maxX = (float) (gbounds2d.getX()+gbounds2d.getWidth());
            }

            if (gbounds.contains(x, y)) {
                boolean isRightHalf =
                    (x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d)));
                boolean isLeadingEdge = !isRightHalf;
                textHit = new TextHit(i, isLeadingEdge);
                return textHit;
            }
        }

        // fallthrough: in text bbox but not on a glyph
       // textHit = new TextHit(-1, false);

        return textHit;
    }

    /**
     * Returns true if the advance direction of this text is vertical.
     */
    public boolean isVertical() {
        // TODO: Implement this!
        return false;
    }

    /**
     * Returns the number of characters in this layout.
     */
    public int getCharacterCount() {
     /*   int count = 0;
        for (char c = aci.first(); c != aci.DONE; c = aci.next()) {
            count++;
        }
        return count;
    */
        return gv.getNumGlyphs();
    }

    protected Shape getGlyphLogicalBounds(int i) {

        // We can't use GlyphVector.getGlyphLogicalBounds(i)
        // since it seems to have a nasty bug!

        return glyphLogicalBounds[i];
    }


    // private

    private void computeGlyphLogicalBounds() {

        int c = gv.getNumGlyphs();

        glyphLogicalBounds = new Rectangle2D.Double[c];

        Rectangle2D.Double lbox = null;

        for (int i=0; i<c; ++i) {

            GlyphMetrics gm = gv.getGlyphMetrics(i);
            Rectangle2D gbounds2d = gm.getBounds2D();
            Point2D gpos = glyphPositions[i];
            lbox = new Rectangle2D.Double(
                                    gpos.getX()+gbounds2d.getX(),
                                    gpos.getY()+gbounds2d.getY(),
                                    gbounds2d.getWidth(),
                                    gbounds2d.getHeight());

            glyphLogicalBounds[i] = lbox;
        }

        for (int i=0; i<c; ++i) {

            int begin = i;
            int end = begin;
            Point2D gpos = glyphPositions[begin];

            // calculate a "run" over the same y nominal position,
            // over which the glyphs have positive 'x' advances.
            // (means that RTL "runs" are not yet supported, sorry)

            float y = (float) gpos.getY();
            float x = (float) gpos.getX();
            lbox = (Rectangle2D.Double) glyphLogicalBounds[begin];
            float miny = (float) lbox.getY();
            float maxy = (float) (lbox.getY() + lbox.getHeight());
            float epsilon = (float) lbox.getHeight()/8f;
            float currY = y;
            float currX = x;
            while (end<c) {
                lbox = (Rectangle2D.Double) glyphLogicalBounds[end];
                currY = (float) glyphPositions[end].getY();
                currX = (float) glyphPositions[end].getX();
                if ((currX < x) || (Math.abs(currY - y) > epsilon)) {
                    break;
                }
                miny =
                  Math.min((float) lbox.getY(), miny);
                float h = (float) (lbox.getY() + lbox.getHeight());
                maxy =
                  Math.max(h, maxy);
                ++end;
            }
            i = (end > begin) ? end-1 : end;

            Rectangle2D.Double lboxPrev = null;

            float prevx = 0f;

            for (int n=begin; n<end; ++n) {

                // extend the vertical bbox for this run
                lbox = (Rectangle2D.Double) glyphLogicalBounds[n];

                x = (float) lbox.getX();

                // adjust left bounds if not first in run
                if (lboxPrev != null) {
                    x = (float) (x + (prevx+lboxPrev.getWidth()))/2f;
                    glyphLogicalBounds[n-1] =
                         new Rectangle2D.Double(
                              (double) prevx,
                              lboxPrev.getY(),
                              (double) (x - prevx),
                              lboxPrev.getHeight());

                    //System.out.println("Setting glb["+(n-1)+"]="+
                    //                       glyphLogicalBounds[n-1]);
                }

                lbox =
                     new Rectangle2D.Double(
                          (double) x,
                          (double) miny,
                          Math.max(0d, lbox.getWidth()+(lbox.getX() - x)),
                          (double) (maxy - miny));

                lboxPrev = lbox;
                prevx = x;
                glyphLogicalBounds[n] = lbox;
            }
        }
    }

//inner classes

    protected class ReorderedCharacterIterator implements CharacterIterator {

        private int ndx;
        private int begin;
        private int end;
        private char[] c;

        ReorderedCharacterIterator(AttributedCharacterIterator aci) {

            int aciIndex = aci.getBeginIndex();
            begin = 0;
            end = aci.getEndIndex()-aciIndex;
            ndx = begin;
            c = new char[end-begin];

            // set increment and initial array index according to
            // run direction of this aci

            int inc = (aci.getAttribute(TextAttribute.RUN_DIRECTION) ==
                                    TextAttribute.RUN_DIRECTION_RTL) ? -1 : 1;

          //  System.out.println("rundir = " + ((inc>0) ? "ltr" : "rtl"));

            ndx = (inc > 0) ? begin : end-1;

            // reordering section
            char ch = aci.first();
            while (ch != CharacterIterator.DONE) {

                 // get BiDi embedding
                 Integer embed = (Integer) aci.getAttribute(
                                               TextAttribute.BIDI_EMBEDDING);
               //  System.out.println("BiDi embedding level : "+embed);
                 // get BiDi span
                 int runLimit = aci.getRunLimit(TextAttribute.BIDI_EMBEDDING);

                 boolean isReversed = false;
                 int runEndNdx = ndx;

                 // have commented out this bit because I'm not sure
                   // why its here and it makes the textBidi test fail

           /*       if (embed != null) {
                     isReversed = (Math.abs(Math.IEEEremainder(
                         (double)embed.intValue(), 2d)) < 0.1) ? false : true;
                     if (isReversed) {
                    //     System.out.println("is reversed");
                         runEndNdx = ndx + inc*(runLimit-aciIndex);
                         inc = -inc;
                         ndx = runEndNdx + inc;
                     }
                 }
*/
                 for (;aciIndex < runLimit; ch=aci.next(),++aciIndex) {
                      c[ndx] = ch;
                  //    System.out.println("Setting c["+ndx+"] to "+ch);
                      ndx += inc;
                 }
                 if (isReversed) { // undo the reversal, run is done
                      ndx = runEndNdx;
                      inc = -inc;
                 }
            }

            aci.first();
            ndx = begin;
        }

        public Object clone() {
            return new ReorderedCharacterIterator(
                           (AttributedCharacterIterator) aci.clone());
        }

        public char current() {
            return c[ndx];
        }

        public char first() {
            ndx=begin;
            return c[ndx];
        }

        public int getBeginIndex() {
            return begin;
        }

        public int getEndIndex() {
            return end;
        }

        public int getIndex() {
            return ndx;
        }

        public char last() {
            ndx = end-1;
            return c[end-1];
        }

        public char next() {
            ++ndx;
            if (ndx >= end) {
                ndx = end;
                return CharacterIterator.DONE;
            }
            else return c[ndx];
        }

        public char previous() {
            --ndx;
            if (ndx < begin) {
                ndx = begin;
                return c[ndx];
            }
            else return c[ndx];
        }

        public char setIndex(int position) {
            if (position < begin || position > end) {
                throw new IllegalArgumentException();
            }
            ndx = position;
            return c[ndx];
        }
    }

//protected

    /**
     * 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;

        Stroke overlineStroke =
            new BasicStroke(overlineThickness);

        Point2D position = gv.getGlyphPosition(0);
        return overlineStroke.createStrokedShape(
                           new java.awt.geom.Line2D.Double(
                           position.getX(), position.getY()+y,
                           position.getX()+getAdvance(), position.getY()+y));
    }

    /**
     * Returns a shape describing the strikethrough line 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;

        Stroke underlineStroke =
            new BasicStroke(underlineThickness);

        Point2D position = gv.getGlyphPosition(0);
        return underlineStroke.createStrokedShape(
                           new java.awt.geom.Line2D.Double(
                           position.getX(), position.getY()+y,
                           position.getX()+getAdvance(), position.getY()+y));
    }

    /**
     * Returns a shape describing the strikethrough line for a given ACI.
     */
    protected Shape getStrikethroughShape() {
        double y = metrics.getStrikethroughOffset();

        Stroke strikethroughStroke =
            new BasicStroke(metrics.getStrikethroughThickness());

        Point2D position = gv.getGlyphPosition(0);
        return strikethroughStroke.createStrokedShape(
                           new java.awt.geom.Line2D.Double(
                           position.getX(), position.getY()+y,
                           position.getX()+getAdvance(), position.getY()+y));
    }

    // returns a GVTFont
    protected GVTFont getFont(AttributedCharacterIterator aci) {

     /* System.out.print("GVTFont for '");
        for (char c = aci.first(); c != aci.DONE; c = aci.next()) {
            System.out.print(c);
        }
*/
        aci.first();
        GVTFont gvtFont = (GVTFont)aci.getAttributes().get(
                GVTAttributedCharacterIterator.TextAttribute.GVT_FONT);
        if (gvtFont != null) {
      //      System.out.print("' is:  ");
       //     if (gvtFont instanceof SVGGVTFont) {
               // for now, turn it into an AWTGVTFont
        //       System.out.println("(SVGGVTFont) " + gvtFont.toString());
        //       return new AWTGVTFont(aci.getAttributes());
        //    }
        //    System.out.println(gvtFont.toString());
            return gvtFont;
        } else {
            return new AWTGVTFont(aci.getAttributes());
        }
    }

    protected void doPathLayout() {

        aci.first();
        Shape path =  (Shape) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.TEXTPATH);
        if (path != null) {
            System.out.println("Performing text layout");
            PathIterator pi = path.getPathIterator(new AffineTransform());
            int numGlyphs = gv.getNumGlyphs();
            double coords[] = new double[6];
            double tx = 0d;
            double dx = 0d;
            double dy = 0d;
            pi.currentSegment(coords);
            double x = coords[0];
            double y = coords[1];
            double d;
            for (int i=0; i<numGlyphs; ++i) {
                // XXX: TODO: generalize to vertical
                d = glyphPositions[i].getX();
                if (pi.isDone()) System.out.println("Done already??? "+i+"; tx="+tx);
                while (!pi.isDone()) {
                    switch(pi.currentSegment(coords)) {
                    case PathIterator.SEG_MOVETO:
                    case PathIterator.SEG_LINETO:
                        System.out.println("SEG MOVE/LINE "+i);
                        break;
                    case PathIterator.SEG_QUADTO:
                        System.out.println("QUAD "+i);
                        break;
                    case PathIterator.SEG_CUBICTO:
                        System.out.println("CUBIC "+i);
                       break;
                    default:
                        ;
                    }
                    dx = coords[0] - x;
                    dy = coords[1] - y;
                    if (dx!=0d || dy!=0d) tx += Math.sqrt(dx*dx+dy*dy);
                    if (tx < d) {
                        x = coords[0];
                        y = coords[1];
                        pi.next();
                    } else {
                        break;
                    }
                }
                if (tx != 0d) {
                    System.out.println("d="+d+"; tx="+tx+"; dx="+dx+"; x="+x);
                    x += (d/tx)*(dx);
                    y += (d/tx)*(dy);
                    tx -= d; // keep remainder...
                }
                glyphPositions[i] = new Point2D.Float(
                                (float) x,
                                (float) (y+glyphPositions[i].getY()));
                gv.setGlyphPosition(i, glyphPositions[i]);
                System.out.println("Placed glyph "+i+" at "+
                                   new Point2D.Float((float) x, (float) y));
            }
        }
    }

    protected void adjustTextSpacing() {

        aci.first();
        Boolean customSpacing =  (Boolean) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.CUSTOM_SPACING);
        Float length = (Float) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.BBOX_WIDTH);
        Integer lengthAdjust = (Integer) aci.getAttribute(
              GVTAttributedCharacterIterator.TextAttribute.LENGTH_ADJUST);
        if ((customSpacing != null) && customSpacing.booleanValue()) {
            applySpacingParams(length, lengthAdjust,
               (Float) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.KERNING),
               (Float) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.LETTER_SPACING),
               (Float) aci.getAttribute(
               GVTAttributedCharacterIterator.TextAttribute.WORD_SPACING));
        }

        if (lengthAdjust ==
            GVTAttributedCharacterIterator.TextAttribute.ADJUST_ALL) {
               transform = computeStretchTransform(length);
        }
    }

    protected AffineTransform computeStretchTransform(Float length) {
        AffineTransform t = null;
        if (length!= null && !length.isNaN()) {
            double xscale = 1d;
            double yscale = 1d;
            if (isVertical()) {
                yscale = length.floatValue()/gv.getVisualBounds().getHeight();
            } else {
                xscale = length.floatValue()/gv.getVisualBounds().getWidth();
            }
            try {
                Point2D startPos = gv.getGlyphPosition(0);
                AffineTransform translation =
                        AffineTransform.getTranslateInstance(
                                              startPos.getX(),
                                              startPos.getY());
                AffineTransform inverse = translation.createInverse();
                t = translation;
                t.concatenate(
                        AffineTransform.getScaleInstance(xscale, yscale));
                t.concatenate(inverse);
            } catch (java.awt.geom.NoninvertibleTransformException e) {;}
        }
        return t;
    }

    protected void applySpacingParams(Float length,
                                      Integer lengthAdjust,
                                      Float kern,
                                      Float letterSpacing,
                                      Float wordSpacing) {

       /**
        * Two passes required when textLength is specified:
        * First, apply spacing properties,
        * then adjust spacing with new advances based on ratio
        * of expected length to actual advance.
        */

        advance = doSpacing(kern, letterSpacing, wordSpacing);
        if ((lengthAdjust ==
             GVTAttributedCharacterIterator.TextAttribute.ADJUST_SPACING) &&
                 length!= null && !length.isNaN()) { // adjust if necessary
            float xscale = 1f;
            float yscale = 1f;
            if (!isVertical()) {
                float lastCharWidth =
                    (float) (gv.getGlyphMetrics(
                        gv.getNumGlyphs()-1).getBounds2D().getWidth());
                xscale = (length.floatValue()-lastCharWidth)/
                         (float) (gv.getVisualBounds().getWidth()-lastCharWidth);
            } else {
                yscale = length.floatValue()/(float) gv.getVisualBounds().getHeight();
            }
            rescaleSpacing(xscale, yscale);
        }
    }

    protected Point2D doSpacing(Float kern,
                             Float letterSpacing,
                             Float wordSpacing) {

        boolean autoKern = true;
        boolean doWordSpacing = false;
        boolean doLetterSpacing = false;
        float kernVal = 0f;
        float letterSpacingVal = 0f;
        float wordSpacingVal = 0f;

        if ((kern instanceof Float) && (!kern.isNaN())) {
            kernVal = kern.floatValue();
            autoKern = false;
            //System.out.println("KERNING: "+kernVal);
        }
        if ((letterSpacing instanceof Float) && (!letterSpacing.isNaN())) {
            letterSpacingVal = letterSpacing.floatValue();
            doLetterSpacing = true;
            //System.out.println("LETTER-SPACING: "+letterSpacingVal);
        }
        if ((wordSpacing instanceof Float) && (!wordSpacing.isNaN())) {
            wordSpacingVal = wordSpacing.floatValue();
            doWordSpacing = true;
            //System.out.println("WORD_SPACING: "+wordSpacingVal);
        }

        int numGlyphs = gv.getNumGlyphs();

        float dx = 0f;
        float dy = 0f;
        Point2D newPositions[] = new Point2D[numGlyphs];
        Point2D prevPos = glyphPositions[0];
        float x = (float) prevPos.getX();
        float y = (float) prevPos.getY();
        try {
        if ((numGlyphs > 1) &&
                            (doWordSpacing || doLetterSpacing || !autoKern)) {
            for (int i=1; i<numGlyphs; ++i) {
                Point2D gpos = glyphPositions[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;
                GlyphMetrics gm = gv.getGlyphMetrics(i);
                // BUG: gm.isWhitespace() fails for latin SPACE glyph!
                while ((gm.getBounds2D().getWidth()<0.01d) ||
                                                       gm.isWhitespace()) {
                    ++i;
                    ++endWS;
                    if (!inWS) inWS = true;
                    if (i>=numGlyphs) {
                        inWS = false;
                        break;
                    }
                    gpos = glyphPositions[i];
                    gm = gv.getGlyphMetrics(i);
                }
                // then apply wordSpacing
                if ( inWS ) {
                    if (doWordSpacing) {
                        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 (isVertical()) {
                            dy += (float) wordSpacing.floatValue()/(nWS+1);
                        } else {
                            dx += (float) 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());
                    if (autoKern) {
                        if (isVertical()) dy += letterSpacingVal;
                        else dx += letterSpacingVal;
                    } else {
                        // apply explicit kerning adjustments,
                        // discarding any auto-kern dx values
                        if (isVertical()) {
                            dy = (float)
                              gv.getGlyphMetrics(i-1).getBounds2D().getWidth()+
                              kernVal + letterSpacingVal;
                        } else {
                            dx = (float)
                              gv.getGlyphMetrics(i-1).getBounds2D().getWidth()+
                              kernVal + letterSpacingVal;
                        }
                    }
                    x += dx;
                    y += dy;
                    newPositions[i] = new Point2D.Float(x, y);
                }
                prevPos = gpos;
            }
            for (int i=1; i<numGlyphs; ++i) { // assign the new positions
                glyphPositions[i] = newPositions[i];
                gv.setGlyphPosition(i, glyphPositions[i]);
            }
        }
        if (isVertical()) {
            dx = 0f;
            dy = (float)
                gv.getGlyphMetrics(numGlyphs-1).getBounds2D().getHeight()+
                                              kernVal+letterSpacingVal;
        } else {
            dx = (float)
                gv.getGlyphMetrics(numGlyphs-1).getBounds2D().getWidth()+
                                              kernVal+letterSpacingVal;
            dy = 0f;
        }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //Point2D newAdvance = new Point2D.Float((float) prevPos.getX()+dx,
        //                                       (float) prevPos.getY()+dy);
        Point2D newAdvance = advance;
        gv.setGlyphPosition(numGlyphs, newAdvance);
        return newAdvance;
    }

    protected void rescaleSpacing(float xscale, float yscale) {
        Rectangle2D bounds = gv.getVisualBounds();
        float initX = (float) bounds.getX();
        float initY = (float) bounds.getY();
        int numGlyphs = gv.getNumGlyphs();
        float dx = 0f;
        float dy = 0f;
        for (int i=0; i<numGlyphs; ++i) {
            Point2D gpos = glyphPositions[i];
            dx = (float)gpos.getX()-initX;
            dy = (float)gpos.getY()-initY;
            glyphPositions[i] = new Point2D.Float(initX+dx*xscale,
                                                  initY+dy*yscale);
            gv.setGlyphPosition(i, glyphPositions[i]);
        }
        gv.setGlyphPosition(numGlyphs, new Point2D.Float(initX+dx*xscale,
                                                             initY+dy*yscale));
        advance = new Point2D.Float((float)(initX+dx*xscale-offset.getX()),
                                       (float)(initY+dy*yscale-offset.getY()));
    }


    protected void doExplicitGlyphLayout(boolean applyOffset) {
        char ch = aci.first();
        int i=0;
        float baselineAscent = isVertical() ?
                               (float) gv.getLogicalBounds().getWidth() :
                               (float) gv.getLogicalBounds().getHeight();

     //   float[] gp = new float[(gv.getNumGlyphs()+1)*2];
        int numGlyphs = gv.getNumGlyphs();
        float[] gp = new float[numGlyphs*2];
        gp = (float[]) gv.getGlyphPositions(0, numGlyphs, gp).clone();
        float init_x_pos = (float) offset.getX();
        float init_y_pos = (float) offset.getY();
        float curr_x_pos = init_x_pos; //gp[0] + init_x_pos;
        float curr_y_pos = init_y_pos; //gp[1] + init_y_pos;

      //  System.out.println("in doExplicitGL, offset = " + offset);
      //  System.out.println("g pos = " + gp[0] + ", " + gp[1]);

       // while ((ch != CharacterIterator.DONE) && (i < gp.length/2)) {
     //   while ((ch != CharacterIterator.DONE) || (i < numGlyphs)) {
        while (i < numGlyphs) {

           float ox = 0f;
           float oy = 0f;
           Float rotation = null;

            if (ch != CharacterIterator.DONE) {
                Float x = (Float) aci.getAttribute(
                             GVTAttributedCharacterIterator.TextAttribute.X);
                Float dx = (Float) aci.getAttribute(
                             GVTAttributedCharacterIterator.TextAttribute.DX);
                Float y = (Float) aci.getAttribute(
                             GVTAttributedCharacterIterator.TextAttribute.Y);
                Float dy = (Float) aci.getAttribute(
                             GVTAttributedCharacterIterator.TextAttribute.DY);
                rotation = (Float) aci.getAttribute(
                        GVTAttributedCharacterIterator.TextAttribute.ROTATION);
                if (x!= null && !x.isNaN()) {
                    if (i==0) {
                        if (applyOffset) {
                            curr_x_pos = (float) offset.getX();
                        } else {
                            curr_x_pos = x.floatValue();
                            init_x_pos = curr_x_pos;
                        }
                    } else {
                        curr_x_pos = x.floatValue();
                    }
                } else if (dx != null && !dx.isNaN()) {
                    curr_x_pos += dx.floatValue();
                }

                if (y != null && !y.isNaN()) {
                    if (i==0) {
                        if (applyOffset) {
                            curr_y_pos = (float) offset.getY();
                        } else {
                            curr_y_pos = y.floatValue();
                            init_y_pos = curr_y_pos;
                        }
                    } else {
                        curr_y_pos = y.floatValue();
                    }
                } else 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];
                }



                // ox and oy are origin adjustments for each glyph,
                // computed on the basis of baseline-shifts, etc.
               // float ox = 0f;
               // float oy = 0f;
                float baselineAdjust = 0f;
                Object baseline = aci.getAttribute(
                    GVTAttributedCharacterIterator.TextAttribute.BASELINE_SHIFT);
                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 (isVertical()) {
                        ox = baselineAdjust;
                    } else {
                        oy = -baselineAdjust;
                    }
                }
            }
            glyphPositions[i] = new Point2D.Float(curr_x_pos+ox,curr_y_pos+oy);
            gv.setGlyphPosition(i, glyphPositions[i]);
            if (rotation != null && !rotation.isNaN()) {
                gv.setGlyphTransform(i,
                    AffineTransform.getRotateInstance(
                        (double)rotation.floatValue()));
            }
            //System.out.print(ch);
            //System.out.print("["+curr_x_pos+","+curr_y_pos+"]");
            curr_x_pos += (float) gv.getGlyphMetrics(i).getAdvance();
            ch = aci.next();
            ++i;
        }

     //   gv.setGlyphPosition(i, new Point2D.Float(curr_x_pos, curr_y_pos));
        //System.out.println();

        advance = new Point2D.Float((float) (curr_x_pos-offset.getX()),
                                    (float) (curr_y_pos-offset.getY()));
       // System.out.println("advance = " + advance.getX() + ", " + advance.getY());

        offset = new Point2D.Float(init_x_pos, init_y_pos);

    }

}
TOP

Related Classes of org.apache.batik.gvt.text.GlyphLayout$ReorderedCharacterIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.