xmlgraphics-batik-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From bi...@apache.org
Subject cvs commit: xml-batik/sources/org/apache/batik/refimpl/util JSVGCanvas.java
Date Mon, 15 Jan 2001 17:10:57 GMT
billh       01/01/15 09:10:57

  Modified:    sources/org/apache/batik/apps/svgviewer ViewerFrame.java
               sources/org/apache/batik/gvt/text
                        GVTAttributedCharacterIterator.java
                        TextLayoutFactory.java TextSpanLayout.java
               sources/org/apache/batik/refimpl/bridge
                        SVGTextElementBridge.java SVGUtilities.java
               sources/org/apache/batik/refimpl/gvt/renderer
                        BasicTextPainter.java StrokingTextPainter.java
               sources/org/apache/batik/refimpl/gvt/text
                        ConcreteTextLayoutFactory.java GlyphLayout.java
                        TextLayoutAdapter.java
               sources/org/apache/batik/refimpl/util JSVGCanvas.java
  Log:
  Added support for per-glyph positioning via X, Y, DX, DY.
  Initial work for printing.
  Some revision of the bridge's whitespace handling.
  
  Revision  Changes    Path
  1.38      +28 -2     xml-batik/sources/org/apache/batik/apps/svgviewer/ViewerFrame.java
  
  Index: ViewerFrame.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/apps/svgviewer/ViewerFrame.java,v
  retrieving revision 1.37
  retrieving revision 1.38
  diff -u -r1.37 -r1.38
  --- ViewerFrame.java	2001/01/08 13:19:51	1.37
  +++ ViewerFrame.java	2001/01/15 17:10:46	1.38
  @@ -15,11 +15,16 @@
   import java.awt.EventQueue;
   import java.awt.FlowLayout;
   import java.awt.Font;
  +import java.awt.Graphics;
   import java.awt.Graphics2D;
   import java.awt.Point;
   import java.awt.Rectangle;
   import java.awt.Toolkit;
   
  +import java.awt.print.PrinterJob;
  +import java.awt.print.Printable;
  +import java.awt.print.PageFormat;
  +
   import java.awt.geom.AffineTransform;
   import java.awt.geom.Dimension2D;
   
  @@ -135,7 +140,7 @@
    *
    * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
    * @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
  - * @version $Id: ViewerFrame.java,v 1.37 2001/01/08 13:19:51 hillion Exp $
  + * @version $Id: ViewerFrame.java,v 1.38 2001/01/15 17:10:46 billh Exp $
    */
   public class ViewerFrame
       extends    JFrame
  @@ -169,6 +174,7 @@
       public final static String USER_STYLE_ACTION  = "UserStyleAction";
       public final static String MONITOR_ACTION     = "MonitorAction";
       public final static String ABOUT_ACTION       = "AboutAction";
  +    public final static String PRINT_ACTION       = "PrintAction";
   
       /**
        * The default cursor.
  @@ -384,7 +390,7 @@
           // Create the SVG canvas.
           canvas = new JSVGCanvas(this);
   
  -        eventDispatcher = 
  +        eventDispatcher =
               new ConcreteEventDispatcher(
                   canvas.getRendererFactory().getRenderContext());
   
  @@ -410,6 +416,7 @@
           listeners.put(USER_STYLE_ACTION,  new UserStyleAction());
           listeners.put(MONITOR_ACTION,     new MonitorAction());
           listeners.put(ABOUT_ACTION,       new AboutAction());
  +        listeners.put(PRINT_ACTION,       new PrintAction());
   
           JPanel p = null;
           try {
  @@ -1170,6 +1177,25 @@
               while (it.hasNext()) {
                   ((JComponent)it.next()).setEnabled(isRunning);
               }
  +        }
  +    }
  +
  +    /**
  +     * To print the current SVG document
  +     */
  +    public class PrintAction extends AbstractAction {
  +        public PrintAction() {}
  +        public void actionPerformed(ActionEvent e) {
  +            PrinterJob job = PrinterJob.getPrinterJob();
  +            job.setPrintable(new Printable() {
  +                public int print(Graphics g, PageFormat pf, int i) {
  +                    if (i > 0) {
  +                        return Printable.NO_SUCH_PAGE;
  +                    } else {
  +                        canvas.paintComponent(g);
  +                        return Printable.PAGE_EXISTS;
  +                    }
  +                }});
           }
       }
   
  
  
  
  1.7       +8 -2      xml-batik/sources/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java
  
  Index: GVTAttributedCharacterIterator.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/text/GVTAttributedCharacterIterator.java,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- GVTAttributedCharacterIterator.java	2001/01/05 17:05:08	1.6
  +++ GVTAttributedCharacterIterator.java	2001/01/15 17:10:48	1.7
  @@ -28,7 +28,7 @@
    * SVGAttributedCharacterIterator.TextAttributes.
    *
    * @author <a href="mailto:bill.haneman@ireland.sun.com">Bill Haneman</a>
  - * @version $Id: GVTAttributedCharacterIterator.java,v 1.6 2001/01/05 17:05:08 billh Exp $
  + * @version $Id: GVTAttributedCharacterIterator.java,v 1.7 2001/01/15 17:10:48 billh Exp $
    */
   
   public interface GVTAttributedCharacterIterator extends
  @@ -207,7 +207,7 @@
           }
   
           /** Attribute span delimiter - new tspan, tref, or textelement.*/
  -        public final static TextAttribute TEXT_COMPOUND_DELIMITER = 
  +        public final static TextAttribute TEXT_COMPOUND_DELIMITER =
                                 new TextAttribute("TEXT_COMPOUND_DELIMITER");
   
           /** User-space X coordinate for character.*/
  @@ -215,6 +215,12 @@
   
           /** User-space Y coordinate for character.*/
           public final static TextAttribute Y = new TextAttribute("Y");
  +
  +        /** User-space relative X coordinate for character.*/
  +        public final static TextAttribute DX = new TextAttribute("DX");
  +
  +        /** User-space relative Y coordinate for character.*/
  +        public final static TextAttribute DY = new TextAttribute("DY");
   
           /** Rotation for character, in degrees.*/
           public final static TextAttribute ROTATION =
  
  
  
  1.2       +3 -1      xml-batik/sources/org/apache/batik/gvt/text/TextLayoutFactory.java
  
  Index: TextLayoutFactory.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/text/TextLayoutFactory.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- TextLayoutFactory.java	2000/12/21 16:35:42	1.1
  +++ TextLayoutFactory.java	2001/01/15 17:10:48	1.2
  @@ -8,6 +8,7 @@
   
   package org.apache.batik.gvt.text;
   
  +import java.awt.geom.Point2D;
   import java.text.AttributedCharacterIterator;
   import java.awt.font.FontRenderContext;
   
  @@ -18,7 +19,7 @@
    *
    * @see org.apache.batik.gvt.text.TextSpanLayout
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: TextLayoutFactory.java,v 1.1 2000/12/21 16:35:42 billh Exp $
  + * @version $Id: TextLayoutFactory.java,v 1.2 2001/01/15 17:10:48 billh Exp $
    */
   public interface TextLayoutFactory {
   
  @@ -29,6 +30,7 @@
        * @param frc the rendering context for the fonts used.
        */
       public TextSpanLayout createTextLayout(AttributedCharacterIterator aci,
  +                                 Point2D offset,
                                    FontRenderContext frc);
   
   }
  
  
  
  1.3       +10 -1     xml-batik/sources/org/apache/batik/gvt/text/TextSpanLayout.java
  
  Index: TextSpanLayout.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/text/TextSpanLayout.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- TextSpanLayout.java	2001/01/05 17:05:09	1.2
  +++ TextSpanLayout.java	2001/01/15 17:10:49	1.3
  @@ -11,6 +11,7 @@
   import java.awt.Graphics2D;
   import java.awt.Shape;
   import java.awt.geom.Rectangle2D;
  +import java.awt.geom.Point2D;
   import java.awt.geom.AffineTransform;
   
   /**
  @@ -24,7 +25,7 @@
    * @see org.apache.batik.gvt.TextPainter.
    *
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: TextSpanLayout.java,v 1.2 2001/01/05 17:05:09 billh Exp $
  + * @version $Id: TextSpanLayout.java,v 1.3 2001/01/15 17:10:49 billh Exp $
    */
   public interface TextSpanLayout {
   
  @@ -80,6 +81,14 @@
        * adjacent layouts.)
        */
       public float getAdvance();
  +
  +    /**
  +     * 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();
   
       /**
        * Returns a Shape which encloses the currently selected glyphs
  
  
  
  1.32      +238 -77   xml-batik/sources/org/apache/batik/refimpl/bridge/SVGTextElementBridge.java
  
  Index: SVGTextElementBridge.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/bridge/SVGTextElementBridge.java,v
  retrieving revision 1.31
  retrieving revision 1.32
  diff -u -r1.31 -r1.32
  --- SVGTextElementBridge.java	2001/01/12 13:36:16	1.31
  +++ SVGTextElementBridge.java	2001/01/15 17:10:50	1.32
  @@ -66,7 +66,7 @@
    * A factory for the &lt;text&gt; SVG element.
    *
    * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
  - * @version $Id: SVGTextElementBridge.java,v 1.31 2001/01/12 13:36:16 tkormann Exp $
  + * @version $Id: SVGTextElementBridge.java,v 1.32 2001/01/15 17:10:50 billh Exp $
    */
   public class SVGTextElementBridge implements GraphicsNodeBridge, SVGConstants {
       protected final static Map fonts = new HashMap(11);
  @@ -220,10 +220,12 @@
   
   
           AttributedString result = null;
  +
           List l = buildAttributedStrings(ctx,
                                           element,
                                           node,
  -                                        true);
  +                                        true,
  +                                        new LinkedList());
           // Simple cases
           switch (l.size()) {
           case 0:
  @@ -238,34 +240,62 @@
           List buffers = new LinkedList();
           List maps = new LinkedList();
           Iterator it = l.iterator();
  -        // Build the StringBuffer list and the attribute map list.
  +
  +        // First pass: build the string buffer.
  +        StringBuffer sb = new StringBuffer();
           while (it.hasNext()) {
               AttributedString s = (AttributedString)it.next();
               AttributedCharacterIterator aci = s.getIterator();
  +            // Build the StringBuffer
               char c = aci.first();
  -            StringBuffer sb = new StringBuffer();
  -            Map m = aci.getAttributes();
               for (; c != CharacterIterator.DONE; c = aci.next()) {
                   sb.append(c);
               }
  -            buffers.add(sb);
  -            maps.add(m);
           }
  -        // Build the attributed string
  -        it = buffers.iterator();
  -        StringBuffer sb = new StringBuffer();
  -        while (it.hasNext()) {
  -            sb.append(it.next());
  -        }
  +
           result = new AttributedString(sb.toString());
   
  -        // Decorate the attributed string
  -        int i = 0;
  -        it = buffers.iterator();
  -        Iterator it2 = maps.iterator();
  +        // Second pass: decorate the attributed string.
  +        int i=0;
  +        it = l.iterator();
           while (it.hasNext()) {
  -            sb = (StringBuffer)it.next();
  -            result.addAttributes((Map)it2.next(), i, i += sb.length());
  +            AttributedString s = (AttributedString)it.next();
  +            AttributedCharacterIterator aci = s.getIterator();
  +            Iterator attrIter = aci.getAllAttributeKeys().iterator();
  +            while (attrIter.hasNext()) { // for each attribute key...
  +                AttributedCharacterIterator.Attribute key =
  +                    (AttributedCharacterIterator.Attribute) attrIter.next();
  +
  +                int begin;
  +                int end;
  +                aci.first();
  +                do {
  +                    begin = aci.getRunStart(key);
  +                    end = aci.getRunLimit(key);
  +                    aci.setIndex(begin);
  +                    Object value = aci.getAttribute(key);
  +                    //System.out.println("Adding attribute "+key+": "+value+" from "+(i+begin)+"->"+(i+end));
  +                    result.addAttribute(key, value, i+begin, i+end);
  +                    aci.setIndex(end);
  +                } while (end < aci.getEndIndex()); // more runs in aci
  +            }
  +            i += aci.getEndIndex();
  +        }
  +
  +        AttributedCharacterIterator iter = result.getIterator();
  +        int ch = iter.first();
  +        while (ch != CharacterIterator.DONE) {
  +            Float dx = (Float) iter.getAttribute(
  +                           GVTAttributedCharacterIterator.TextAttribute.DX);
  +            Float dy = (Float) iter.getAttribute(
  +                           GVTAttributedCharacterIterator.TextAttribute.DY);
  +            //System.out.println((char) ch+" deltas : ("+dx+","+dy+")");
  +            Float x = (Float) iter.getAttribute(
  +                           GVTAttributedCharacterIterator.TextAttribute.X);
  +            Float y = (Float) iter.getAttribute(
  +                           GVTAttributedCharacterIterator.TextAttribute.Y);
  +            //System.out.println((char) ch+" pos : ("+x+","+y+")");
  +            ch = iter.next();
           }
           return result;
       }
  @@ -277,30 +307,46 @@
       protected List buildAttributedStrings(BridgeContext ctx,
                                             Element element,
                                             GraphicsNode node,
  -                                          boolean top) {
  +                                          boolean top,
  +                                          LinkedList result) {
   
  -
           // !!! return two lists
   
  -        List result = new LinkedList();
           Map m = getAttributeMap(ctx, element, node);
           String s = XMLSupport.getXMLSpace(element);
           boolean preserve = s.equals("preserve");
           boolean first = true;
           boolean last;
  +        boolean stripFirst = true;
  +        boolean stripLast = true;
  +        Element nodeElement = element;
  +        AttributedString as = null;
  +
  +        if (!result.isEmpty()) {
  +            as = (AttributedString) result.getLast();
  +        }
  +
           for (Node n = element.getFirstChild();
                n != null;
                n = n.getNextSibling()) {
   
               last = n.getNextSibling() == null;
   
  +            int lastChar = (as != null) ? (as.getIterator().last()) : CharacterIterator.DONE;
  +            stripFirst = first &&
  +                         (top || (lastChar == ' ') || (lastChar == CharacterIterator.DONE));
  +
               switch (n.getNodeType()) {
               case Node.ELEMENT_NODE:
  +
  +                nodeElement = (Element)n;
  +
                   if (n.getLocalName().equals("tspan")) {
  -                    result.addAll(buildAttributedStrings(ctx,
  -                                                         (Element)n,
  -                                                         node,
  -                                                         false));
  +                    buildAttributedStrings(ctx,
  +                                           nodeElement,
  +                                           node,
  +                                           false,
  +                                           result);
                   } else if (n.getLocalName().equals("tref")) {
                       try {
                           String uriStr = XLinkSupport.getXLinkHref((Element)n);
  @@ -313,42 +359,44 @@
                           URIResolver resolver = new URIResolver(svgDoc, loader);
                           Element ref = (Element)resolver.getNode(url.toString());
                           s = getElementContent(ref);
  -                        AttributedString as;
  -                        Map map = getAttributeMap(ctx, (Element)n, node);
  -                        as = createAttributedString(s, map, preserve, top,
  -                                                    first, last);
  +                        Map map = getAttributeMap(ctx, nodeElement, node);
  +                        int[] indexMap = new int[s.length()];
  +                        as = createAttributedString(s, map, indexMap, preserve,
  +                                                    stripFirst, last && top);
                           if (as != null) {
  +                            addGlyphPositionAttributes(
  +                                 as, indexMap, ctx, nodeElement);
  +                            stripLast = last && (as.getIterator().first() == ' ');
  +                            if (stripLast) {
  +                                AttributedString las =
  +                                     (AttributedString) result.removeLast();
  +                                if (las != null) {
  +                                    AttributedCharacterIterator iter = las.getIterator();
  +                                    AttributedCharacterIterator.Attribute[] atts =
  +                                       (AttributedCharacterIterator.Attribute[])
  +                                       iter.getAllAttributeKeys().toArray();
  +                                    las = new AttributedString(
  +                                         las.getIterator(atts,
  +                                             iter.getBeginIndex(), iter.getEndIndex()-1));
  +                                    result.add(las);
  +                                }
  +                            }
                               result.add(as);
                           }
                       } catch(MalformedURLException ex) {
                           throw new IllegalAttributeValueException(
                            Messages.formatMessage("tref.xlinkHref.badURL", null));
                       } catch (Exception ex) { /* Nothing to do */ }
  -                    /*
  -                      s = XLinkSupport.getXLinkHref((Element)n);
  -                    if (s.startsWith("#")) {
  -                        Document doc = n.getOwnerDocument();
  -                        Element ref = doc.getElementById(s.substring(1));
  -                        s = getElementContent(ref);
  -                        AttributedString as;
  -                        Map map = getAttributeMap(ctx, (Element)n, node);
  -                        as = createAttributedString(s, map, preserve, top,
  -                                                    first, last);
  -                        if (as != null) {
  -                            result.add(as);
  -                        }
  -                    } else {
  -                        System.out.println(" !!! <tref> Non local URI");
  -                    }
  -                    */
                   }
                   break;
               case Node.TEXT_NODE:
                   s = n.getNodeValue();
  -                AttributedString as = createAttributedString(s, m, preserve,
  -                                                             top, first, last);
  +                int[] indexMap = new int[s.length()];
  +                as = createAttributedString(
  +                              s, m, indexMap, preserve, stripFirst, last && top);
                   if (as != null) {
  -                    result.add(as);
  +                     addGlyphPositionAttributes(as, indexMap, ctx, element);
  +                     result.add(as);
                   }
               }
               first = false;
  @@ -380,10 +428,11 @@
        */
       protected AttributedString createAttributedString(String s,
                                                         Map m,
  +                                                      int[] indexMap,
                                                         boolean preserve,
  -                                                      boolean top,
  -                                                      boolean first,
  -                                                      boolean last) {
  +                                                      boolean stripfirst,
  +                                                      boolean striplast) {
  +        AttributedString as = null;
           StringBuffer sb = new StringBuffer();
           if (preserve) {
               for (int i = 0; i < s.length(); i++) {
  @@ -397,6 +446,7 @@
                   default:
                       sb.append(c);
                   }
  +                indexMap[i] = i;
               }
           } else {
               boolean space = false;
  @@ -405,54 +455,164 @@
                   switch (c) {
                   case 10:
                   case 13:
  -                    // I don't think behavior below is correct for tspan...
  -                    // including "space = false" means that newlines
  -                    // cause leading whitespace on next line to turn into
  -                    // a single space!
  -                    // space = false;
                       break; // should break, newlines are not whitespace
                   case ' ':
                   case '\t':
                       if (!space) {
                           sb.append(' ');
  +                        indexMap[sb.length()-1] = i;
                           space = true;
                       }
                       break;
                   default:
                       sb.append(c);
  +                    indexMap[sb.length()-1] = i;
                       space = false;
  -
                   }
               }
  -            if (top) {
  -                if (first) {
  -                    while (sb.length() > 0) {
  -                        if (sb.charAt(0) == ' ') {
  -                            sb.deleteCharAt(0);
  -                        } else {
  -                            break;
  -                        }
  +            if (stripfirst) {
  +                while (sb.length() > 0) {
  +                    if (sb.charAt(0) == ' ') {
  +                        sb.deleteCharAt(0);
  +                        System.arraycopy(indexMap, 1, indexMap, 0, sb.length());
  +                    } else {
  +                        break;
                       }
                   }
  -                if (last) {
  -                    int len;
  -                    while ((len = sb.length()) > 0) {
  -                        if (sb.charAt(len - 1) == ' ') {
  -                            sb.deleteCharAt(len - 1);
  -                        } else {
  -                            break;
  -                        }
  +             }
  +             if (striplast) {
  +                int len;
  +                while ((len = sb.length()) > 0) {
  +                    if (sb.charAt(len - 1) == ' ') {
  +                        sb.deleteCharAt(len - 1);
  +                    } else {
  +                        break;
                       }
                   }
                }
  +             for (int i=sb.length(); i<indexMap.length; ++i) {
  +                indexMap[i] = -1;
  +             }
           }
           if (sb.length() > 0) {
  -            return new AttributedString(sb.toString(), m);
  +            as = new AttributedString(sb.toString(), m);
           }
  -        return null;
  +
  +        return as;
       }
   
       /**
  +     * Adds glyph position attributes to an AttributedString.
  +     */
  +    protected void addGlyphPositionAttributes(AttributedString as,
  +                                  int[] indexMap,
  +                                  BridgeContext ctx,
  +                                  Element element) {
  +
  +        CSSStyleDeclaration cssDecl
  +            = ctx.getViewCSS().getComputedStyle(element, null);
  +        UnitProcessor.Context uctx
  +            = new DefaultUnitProcessorContext(ctx, cssDecl);
  +
  +        int asLength =
  +            as.getIterator().getEndIndex();
  +            // AttributedStrings always start at index 0, we hope!
  +
  +        // glyph and sub-element positions
  +        // (Don't do these for <text> elements
  +        if (((Node)element).getLocalName() != "text") {
  +            String s = element.getAttributeNS(null, SVG_X_ATTRIBUTE);
  +            //System.out.println("X: "+s);
  +            if (s.length() != 0) {
  +                float x[] = SVGUtilities.svgToUserSpaceArray(element,
  +                                            SVG_X_ATTRIBUTE, s,
  +                                            uctx,
  +                                            UnitProcessor.HORIZONTAL_LENGTH);
  +
  +                for (int i=0; i<asLength; ++i) {
  +                    if (i < x.length) {
  +                            as.addAttribute(
  +                                GVTAttributedCharacterIterator.TextAttribute.X,
  +                                    new Float(x[i]), i, i+1);
  +                    }
  +                }
  +            }
  +            // parse the y attribute, (default is 0)
  +            s = element.getAttributeNS(null, SVG_Y_ATTRIBUTE);
  +            //System.out.println("Y: "+s);
  +            if (s.length() != 0) {
  +                float y[] = SVGUtilities.svgToUserSpaceArray(element,
  +                                            SVG_Y_ATTRIBUTE, s,
  +                                            uctx,
  +                                            UnitProcessor.VERTICAL_LENGTH);
  +
  +                for (int i=0; i<asLength; ++i) {
  +                    if (i < y.length) {
  +                        as.addAttribute(
  +                            GVTAttributedCharacterIterator.TextAttribute.Y,
  +                                new Float(y[i]), i, i+1);
  +                    }
  +                }
  +
  +            }
  +            s = element.getAttributeNS(null, SVG_DX_ATTRIBUTE);
  +            //System.out.println("DX: "+s);
  +            if (s.length() != 0) {
  +                float x[] = SVGUtilities.svgToUserSpaceArray(element,
  +                                            SVG_DX_ATTRIBUTE, s,
  +                                            uctx,
  +                                            UnitProcessor.HORIZONTAL_LENGTH);
  +                int i=0;
  +                float cx = 0f;
  +                for (int n=0; n<indexMap.length && indexMap[n] >= 0; ++n) {
  +                    boolean hasAnother = false;
  +                    while ((indexMap[n] != i) && (i<x.length)) {
  +                        cx += x[i];
  +                        ++i;
  +                        hasAnother = true;
  +                    }
  +                    if ((i<x.length) && (indexMap[n] == i)) {
  +                        cx += x[i];
  +                        hasAnother = true;
  +                    }
  +                    if (hasAnother) as.addAttribute(
  +                          GVTAttributedCharacterIterator.TextAttribute.DX,
  +                                    new Float(cx), n, n+1);
  +                    cx = 0f;
  +                }
  +            }
  +            // parse the y attribute, (default is 0)
  +            s = element.getAttributeNS(null, SVG_DY_ATTRIBUTE);
  +            //System.out.println("DY: "+s);
  +            if (s.length() != 0) {
  +                float y[] = SVGUtilities.svgToUserSpaceArray(element,
  +                                            SVG_DY_ATTRIBUTE, s,
  +                                            uctx,
  +                                            UnitProcessor.VERTICAL_LENGTH);
  +                int i=0;
  +                float cy = 0f;
  +                for (int n=0; n<indexMap.length && indexMap[n] >= 0; ++n) {
  +                    boolean hasAnother = false;
  +                    while ((indexMap[n] != i) && (i<y.length)) {
  +                        cy += y[i];
  +                        ++i;
  +                        hasAnother = true;
  +                    }
  +                    if ((i<y.length) && (indexMap[n] == i)) {
  +                        cy += y[i];
  +                        hasAnother = true;
  +                    }
  +                    if (hasAnother) as.addAttribute(
  +                           GVTAttributedCharacterIterator.TextAttribute.DY,
  +                                    new Float(cy), n, n+1);
  +                    //System.out.println("Setting DY to "+cy+" at "+n);
  +                    cy = 0f;
  +                }
  +            }
  +        }
  +    }
  +
  +    /**
        * Returns the map to pass to the current characters.
        */
       protected Map getAttributeMap(BridgeContext ctx,
  @@ -783,4 +943,5 @@
   
           return result;
       }
  +
   }
  
  
  
  1.14      +52 -1     xml-batik/sources/org/apache/batik/refimpl/bridge/SVGUtilities.java
  
  Index: SVGUtilities.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/bridge/SVGUtilities.java,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- SVGUtilities.java	2001/01/14 21:27:46	1.13
  +++ SVGUtilities.java	2001/01/15 17:10:51	1.14
  @@ -13,6 +13,7 @@
   import java.awt.geom.Rectangle2D;
   import java.io.StringReader;
   import java.util.StringTokenizer;
  +import java.util.ArrayList;
   
   import org.apache.batik.bridge.IllegalAttributeValueException;
   import org.apache.batik.bridge.MissingAttributeException;
  @@ -45,7 +46,7 @@
    *
    * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
    * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
  - * @version $Id: SVGUtilities.java,v 1.13 2001/01/14 21:27:46 tkormann Exp $
  + * @version $Id: SVGUtilities.java,v 1.14 2001/01/15 17:10:51 billh Exp $
    */
   public class SVGUtilities implements SVGConstants {
       /**
  @@ -1172,6 +1173,56 @@
                                               (SVGElement)element,
                                               direction,
                                               uctx);
  +    }
  +
  +    /**
  +     * Returns the float array that represents a set of values or percentage.
  +     *
  +     * @param element the element that defines the specified coordinates
  +     * @param attrName the name of the attribute (used by error handling)
  +     * @param valueStr the delimited string containing values of the coordinate
  +     * @param uctx the context used to compute units and percentages
  +     * @param direction HORIZONTAL_LENGTH | VERTICAL_LENGTH | OTHER_LENGTH
  +     * @exception IllegalAttributeValueException if the value is not a valid
  +     */
  +    public static float[] svgToUserSpaceArray(Element element,
  +                                       String attrName,
  +                                       String valueStr,
  +                                       UnitProcessor.Context uctx,
  +                                       short direction) {
  +
  +        // INTERNAL : check for correct arguments - should never happen
  +        if (valueStr == null || valueStr.length() == 0) {
  +            throw new Error("The value is null or empty");
  +        }
  +
  +        LengthParser p = uctx.getParserFactory().createLengthParser();
  +        UnitProcessor.UnitResolver ur = new UnitProcessor.UnitResolver();
  +        p.setLengthHandler(ur);
  +        ArrayList values = new ArrayList();
  +        StringTokenizer st = new StringTokenizer(valueStr, ", ", false);
  +        int c = 0; // must count, can't rely in ArrayList.size()
  +        while (st.hasMoreTokens()) {
  +            String s = st.nextToken();
  +            try {
  +                p.parse(new StringReader(s));
  +                ++c;
  +            } catch (ParseException ex) {
  +                throw new IllegalAttributeValueException(
  +                    Messages.formatMessage("length.invalid",
  +                                       new Object[] {s, attrName}));
  +            }
  +            values.add(new Float(UnitProcessor.svgToUserSpace(ur.unit,
  +                                            ur.value,
  +                                            (SVGElement)element,
  +                                            direction,
  +                                            uctx)));
  +        }
  +        float[] floats = new float[c];
  +        for (int i=0; i<c; ++i) {
  +            floats[i] = ((Float) values.get(i)).floatValue();
  +        }
  +        return floats;
       }
   
       /**
  
  
  
  1.15      +13 -5     xml-batik/sources/org/apache/batik/refimpl/gvt/renderer/BasicTextPainter.java
  
  Index: BasicTextPainter.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/gvt/renderer/BasicTextPainter.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- BasicTextPainter.java	2001/01/05 17:05:11	1.14
  +++ BasicTextPainter.java	2001/01/15 17:10:52	1.15
  @@ -39,7 +39,7 @@
    *
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
    * @author <a href="vincent.hardy@sun.com>Vincent Hardy</a>
  - * @version $Id: BasicTextPainter.java,v 1.14 2001/01/05 17:05:11 billh Exp $
  + * @version $Id: BasicTextPainter.java,v 1.15 2001/01/15 17:10:52 billh Exp $
    */
   public class BasicTextPainter implements TextPainter {
   
  @@ -63,7 +63,7 @@
            *     works for J2SE base implementation of AttributeCharacterIterator
            */
           TextSpanLayout layout = getTextLayoutFactory().
  -                                      createTextLayout(aci, frc);
  +                       createTextLayout(aci, new Point2D.Float(0f, 0f), frc);
   
           float advance = layout.getAdvance();
           float tx = 0f;
  @@ -313,6 +313,7 @@
            AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
            TextSpanLayout layout
                       = getTextLayoutFactory().createTextLayout(aci,
  +                          new Point2D.Float(0f, 0f),
                             new java.awt.font.FontRenderContext(
                                               new AffineTransform(),
                                                   true, true));
  @@ -374,7 +375,8 @@
           AttributedCharacterIterator aci = node.getAttributedCharacterIterator();
   
           TextSpanLayout layout = getTextLayoutFactory().createTextLayout(aci,
  -                      new java.awt.font.FontRenderContext(
  +                          new Point2D.Float(0f, 0f),
  +                          new java.awt.font.FontRenderContext(
                                               new AffineTransform(),
                                                             true,
                                                             true));
  @@ -472,7 +474,9 @@
   
           FontRenderContext frc = context.getFontRenderContext();
           TextSpanLayout layout =
  -               getTextLayoutFactory().createTextLayout(aci, frc);
  +               getTextLayoutFactory().createTextLayout(aci,
  +               new Point2D.Float(0f, 0f),
  +               frc);
           float advance = layout.getAdvance();
           float tx = 0f;
   
  @@ -487,10 +491,14 @@
           TextHit textHit =
               layout.hitTestChar((float) (x+tx), (float) y);
   
  +        // Note that a texthit char index of -1 signals that the
  +        // hit, though within the text element bounds, did not
  +        // coincide with a glyph.
           if ((aci != cachedACI) ||
               (textHit == null) ||
               (cachedHit == null) ||
  -            (textHit.getInsertionIndex() != cachedHit.getInsertionIndex())) {
  +            ((textHit.getCharIndex() != -1) &&
  +            (textHit.getInsertionIndex() != cachedHit.getInsertionIndex()))) {
               cachedMark = new BasicTextPainter.Mark(x, y, layout, textHit);
               cachedACI = aci;
               cachedHit = textHit;
  
  
  
  1.19      +18 -14    xml-batik/sources/org/apache/batik/refimpl/gvt/renderer/StrokingTextPainter.java
  
  Index: StrokingTextPainter.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/gvt/renderer/StrokingTextPainter.java,v
  retrieving revision 1.18
  retrieving revision 1.19
  diff -u -r1.18 -r1.19
  --- StrokingTextPainter.java	2001/01/05 17:05:12	1.18
  +++ StrokingTextPainter.java	2001/01/15 17:10:53	1.19
  @@ -42,7 +42,7 @@
    * @see org.apache.batik.gvt.text.GVTAttributedCharacterIterator
    *
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: StrokingTextPainter.java,v 1.18 2001/01/05 17:05:12 billh Exp $
  + * @version $Id: StrokingTextPainter.java,v 1.19 2001/01/15 17:10:53 billh Exp $
    */
   public class StrokingTextPainter extends BasicTextPainter {
   
  @@ -71,7 +71,8 @@
           Point2D location = node.getLocation();
           TextNode.Anchor anchor = node.getAnchor();
           List textRuns = new ArrayList();
  -        double advance = 0d;
  +        double xadvance = 0d;
  +        Point2D advance = new Point2D.Float(0f, 0f);
   
           aci.first();
           /*
  @@ -80,19 +81,14 @@
            * accumulate an overall advance for the text display.
            */
           while (aci.current() != CharacterIterator.DONE) {
  -            double x = 0d;
  -            double y = 0d;
  -            /*
  -             * note that these can be superseded by X, Y attributes
  -             * but this hasn't been implemented yet
  -             */
  +
               int start = aci.getRunStart(extendedAtts);
               int end = aci.getRunLimit(extendedAtts);
   
               AttributedCharacterIterator runaci =
                       new AttributedCharacterSpanIterator(aci, start, end);
   
  -            TextSpanLayout layout = getTextLayoutFactory().createTextLayout(runaci, frc);
  +            TextSpanLayout layout = getTextLayoutFactory().createTextLayout(runaci, advance, frc);
   
               if (layout.isVertical()) {
                   AttributedString as = new AttributedString(runaci);
  @@ -112,8 +108,13 @@
               TextRun run = new TextRun(layout, runaci);
   
               textRuns.add(run);
  +
  +            Point2D layoutAdvance = layout.getAdvance2D();
  +            advance = new Point2D.Float(
  +                       (float) (advance.getX()+layoutAdvance.getX()), 
  +                       (float) (advance.getY()+layoutAdvance.getY()));
   
  -            advance += (double) layout.getAdvance();
  +            xadvance = advance.getX();
   
               // FIXME: not BIDI compliant yet!
   
  @@ -121,13 +122,16 @@
           }
   
           double x = 0d;
  +        double y = 0d;
  +
  +        // XXX: horizontal layouts only!
   
           switch(anchor.getType()){
           case TextNode.Anchor.ANCHOR_MIDDLE:
  -            x = -advance/2d;
  +            x = -xadvance/2d;
               break;
           case TextNode.Anchor.ANCHOR_END:
  -            x = -advance;
  +            x = -xadvance;
           }
   
           /*
  @@ -173,7 +177,8 @@
   
   
               AffineTransform tx = AffineTransform.getTranslateInstance(
  -                                        location.getX() + x, location.getY());
  +                                        location.getX() + x, 
  +                                        location.getY() + y);
               Shape outline = layout.getOutline(tx);
   
               // check if we need to fill this glyph
  @@ -203,7 +208,6 @@
               if (strikethrough && !layout.isVertical()) {
                   paintStrikethrough(textRun, location, x, g2d);
               }
  -            x += textRun.getLayout().getAdvance();
           }
   
           // FIXME: Finish implementation!
  
  
  
  1.3       +187 -3    xml-batik/sources/org/apache/batik/refimpl/gvt/text/ConcreteTextLayoutFactory.java
  
  Index: ConcreteTextLayoutFactory.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/gvt/text/ConcreteTextLayoutFactory.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- ConcreteTextLayoutFactory.java	2001/01/05 17:05:13	1.2
  +++ ConcreteTextLayoutFactory.java	2001/01/15 17:10:54	1.3
  @@ -10,9 +10,17 @@
   
   import java.util.Set;
   import java.util.HashSet;
  +import java.util.StringTokenizer;
  +import java.io.StreamTokenizer;
  +import java.io.FileReader;
  +import java.io.IOException;
  +import java.io.FileNotFoundException;
  +import java.awt.geom.Point2D;
   import java.awt.font.TextLayout;
   import java.awt.font.FontRenderContext;
  +import java.text.CharacterIterator;
   import java.text.AttributedCharacterIterator;
  +import java.lang.reflect.Array;
   
   import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
   import org.apache.batik.gvt.text.TextSpanLayout;
  @@ -25,28 +33,204 @@
    *
    * @see org.apache.batik.gvt.text.TextSpanLayout
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: ConcreteTextLayoutFactory.java,v 1.2 2001/01/05 17:05:13 billh Exp $
  + * @version $Id: ConcreteTextLayoutFactory.java,v 1.3 2001/01/15 17:10:54 billh Exp $
    */
   public class ConcreteTextLayoutFactory implements TextLayoutFactory {
   
  +   public static final int L = 0x0002;
  +   public static final int R = 0x0001;
  +   public static final int AL = 0x0005;
  +   public static final int RLE = 0x0011;
  +   public static final int RLO = 0x0031;
  +   public static final int LRE = 0x0010;
  +   public static final int LRO = 0x0030;
  +   public static final int B =  0x0080;
  +   public static final int BN = 0x0180;
  +   public static final int S =  0x000c;
  +   public static final int WS = 0x0008;
  +   public static final int AN = 0x0140;
  +   public static final int ON = 0x0102;
  +   public static final int EN = 0x0100;
  +   public static final int ET = 0x0200;
  +
  +   public static String unicodeFileName = "UnicodeData.txt";
  +
       /**
        * Returns an instance of TextSpanLayout suitable for rendering the
        * AttributedCharacterIterator.
        * @param aci the character iterator to be laid out
        */
       public TextSpanLayout createTextLayout(AttributedCharacterIterator aci,
  +                                                Point2D offset, 
                                                   FontRenderContext frc) {
           Set keys = aci.getAllAttributeKeys();
           Set glyphPositionKeys = new HashSet();
           glyphPositionKeys.add(GVTAttributedCharacterIterator.TextAttribute.X);
           glyphPositionKeys.add(GVTAttributedCharacterIterator.TextAttribute.Y);
  +        glyphPositionKeys.add(GVTAttributedCharacterIterator.TextAttribute.DX);
  +        glyphPositionKeys.add(GVTAttributedCharacterIterator.TextAttribute.DY);
           glyphPositionKeys.add(
                          GVTAttributedCharacterIterator.TextAttribute.ROTATION);
           glyphPositionKeys.retainAll(keys);
           if (glyphPositionKeys.isEmpty()) {
  -            return new TextLayoutAdapter(new TextLayout(aci, frc), aci);
  +            return new TextLayoutAdapter(new TextLayout(aci, frc), offset, aci);
           } else {
  -            return new GlyphLayout(aci, frc);
  +            char ch = aci.first();
  +            do {
  +                if (isRTL(ch)) {
  +                  return new TextLayoutAdapter(new TextLayout(aci, frc), offset, aci);
  +                }
  +                ch = aci.next();
  +            } while (ch != CharacterIterator.DONE);
  +            //System.out.println("Using GlyphLayout");
  +            return new GlyphLayout(aci, offset, frc);
  +        }
  +    }
  +
  +    private boolean isRTL(char ch) {
  +        int bidiCode = UnicodeData.getBiDiCode(Character.getNumericValue(ch));
  +        switch (bidiCode) {
  +            case R:
  +            case AL:
  +            case RLE:
  +            case RLO:
  +                return true;
  +            default:
  +                return false;
  +        }
  +    }
  +
  +
  +    public static class UnicodeData {
  +
  +        protected static int BIDI_CODE_NDX = 4;
  +
  +        protected static Object[] unicodeValues = new Object[0xFF00];
  +
  +        public static int getBiDiCode(int ch) {
  +            int i;
  +            try {
  +                 i = ((Integer) Array.get(getUnicodeData(ch), BIDI_CODE_NDX)).intValue();
  +            } catch (Exception e) {
  +                 i = 0;
  +            }
  +            return i;
           }
  +
  +        public static Array getUnicodeData(int ch) {
  +             Array data = (Array) unicodeValues[ch];
  +             if (data == null) {
  +                 data = parseUnicodeDataEntry(ch);
  +                 unicodeValues[ch] = data;
  +             }
  +             return data;
  +        }
  +
  +        public static Array parseUnicodeDataEntry(int ch) {
  +            Array values = (Array) Array.newInstance(Object.class, 14);
  +            String s = readUnicodeData(ch, unicodeFileName);
  +            StringTokenizer st = new StringTokenizer(s, ";");
  +            Array.set(values, 0, new Integer(st.nextToken())); // unicode value
  +            Array.set(values, 1, st.nextToken()); // character name
  +            Array.set(values, 2, st.nextToken()); // case
  +            Array.set(values, 3, st.nextToken());  //
  +            Array.set(values, 4, new Integer(getBiDiValue(st.nextToken()))); // BiDi value
  +            Array.set(values, 5, new Integer(st.nextToken())); // unicode value
  +            Array.set(values, 6, st.nextToken());
  +            Array.set(values, 7, st.nextToken());
  +            Array.set(values, 8, st.nextToken());
  +            Array.set(values, 9, st.nextToken());
  +            Array.set(values, 10, st.nextToken());
  +            Array.set(values, 11, st.nextToken());
  +            Array.set(values, 12, st.nextToken());
  +            Array.set(values, 13, st.nextToken());
  +            return values;
  +        }
  +
  +        private static int getBiDiValue(String string) {
  +            int val = 0;
  +            char s[] = string.toCharArray();
  +
  +            switch (s[0]) {
  +            case 'L':
  +                if (s.length > 2) {
  +                    switch (s[2]) {
  +                    case 'E':
  +                        val = LRE;
  +                        break;
  +                    default:
  +                        val = LRO;
  +                    }
  +                } else {
  +                   val = L;
  +                }
  +                break;
  +            case 'R':
  +                if (s.length > 2) {
  +                    switch (s[2]) {
  +                    case 'E':
  +                        val = RLE;
  +                        break;
  +                    default:
  +                        val = RLO;
  +                    }
  +                } else {
  +                   val = R;
  +                }
  +                break;
  +            case 'A':
  +                switch (s[2]) {
  +                case 'L':
  +                    val = AL;
  +                    break;
  +                default:
  +                    val = AN;
  +                }
  +                break;
  +            case 'B':
  +                if (s.length > 1) {
  +                    val = BN;
  +                } else {
  +                    val = B;
  +                }
  +                break;
  +            case 'E':
  +                switch (s[1]) {
  +                case 'N':
  +                    val = EN;
  +                    break;
  +                default:
  +                    val = ET;
  +                }
  +                break;
  +            case 'O':
  +                val = ON;
  +                break;
  +            case 'S':
  +                val = S;
  +            }
  +            return val;
  +        }
  +
  +        private static String readUnicodeData(int ch, String filename) {
  +            String s = null;
  +            try {
  +                StreamTokenizer st = new StreamTokenizer(new FileReader(filename));
  +                st.resetSyntax();
  +                st.eolIsSignificant(true);
  +                for (int i=0; i<ch; ++i) {
  +                    s = st.toString();
  +                    st.nextToken(); // eol token
  +                    st.nextToken(); // next line
  +                }
  +                System.out.println("UnicodeData for "+ch+": "+s);
  +            } catch (FileNotFoundException fnfe) {
  +                System.out.println(fnfe);
  +            } catch (IOException ioe) {
  +                System.out.println("Error reading Unicode Database: "+ioe);
  +            }
  +            return s;
  +        }
  +
       }
   }
  
  
  
  1.2       +206 -60   xml-batik/sources/org/apache/batik/refimpl/gvt/text/GlyphLayout.java
  
  Index: GlyphLayout.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/gvt/text/GlyphLayout.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- GlyphLayout.java	2001/01/05 17:05:13	1.1
  +++ GlyphLayout.java	2001/01/15 17:10:54	1.2
  @@ -21,11 +21,13 @@
   import java.awt.geom.Rectangle2D;
   import java.awt.geom.AffineTransform;
   import java.awt.geom.GeneralPath;
  +import java.awt.geom.Point2D;
   import java.text.AttributedCharacterIterator;
   import java.text.CharacterIterator;
   import java.text.AttributedString;
   
   import org.apache.batik.gvt.text.TextSpanLayout;
  +import org.apache.batik.gvt.text.GVTAttributedCharacterIterator;
   import org.apache.batik.gvt.text.TextHit;
   
   /**
  @@ -33,7 +35,7 @@
    * @see org.apache.batik.gvt.TextSpanLayout.
    *
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: GlyphLayout.java,v 1.1 2001/01/05 17:05:13 billh Exp $
  + * @version $Id: GlyphLayout.java,v 1.2 2001/01/15 17:10:54 billh Exp $
    */
   public class GlyphLayout implements TextSpanLayout {
   
  @@ -43,15 +45,20 @@
       private AttributedCharacterIterator aci;
       private CharacterIterator ci;
       private FontRenderContext frc;
  +    private Point2D advance;
  +    private Point2D offset;
  +    private Point2D prevCharPosition;
  +    protected Shape[] glyphLogicalBounds;
   
       /**
  -     * 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.
  +     * 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, FontRenderContext frc) {
  +    public GlyphLayout(AttributedCharacterIterator aci, Point2D offset,
  +                          FontRenderContext frc) {
           this.aci = aci;
           this.frc = frc;
           this.font = getFont(aci);
  @@ -60,6 +67,8 @@
           ci = new ReorderedCharacterIterator(aci);
           this.gv = font.createGlyphVector(frc, ci);
           this.gv.performDefaultLayout();
  +        this.offset = offset;
  +        doExplicitGlyphLayout();
       }
   
       /**
  @@ -82,7 +91,7 @@
           Shape s;
   
           if (t.getType() == AffineTransform.TYPE_TRANSLATION) {
  -           s = gv.getOutline((float) t.getTranslateX(), 
  +           s = gv.getOutline((float) t.getTranslateX(),
                                (float) t.getTranslateY());
           } else {
              s = t.createTransformedShape(gv.getOutline(0f, 0f));
  @@ -91,11 +100,11 @@
       }
   
       /**
  -     * Returns the outline of the specified decorations on the glyphs, 
  +     * Returns the outline of the specified decorations on the glyphs,
        * transformed by an AffineTransform.
        * @param decorationType an integer indicating the type(s) of decorations
        *     included in this shape.  May be the result of "OR-ing" several
  -     *     values together: 
  +     *     values together:
        * e.g. <tt>DECORATION_UNDERLINE | DECORATION_STRIKETHROUGH</tt>
        * @param t an AffineTransform to apply to the outline before returning it.
        */
  @@ -133,11 +142,21 @@
       /**
        * 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 
  +     * (This is the dimension that should be used for positioning
        * adjacent layouts.)
        */
       public float getAdvance() {
  -        return (float) gv.getLogicalBounds().getWidth();
  +        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;
       }
   
       /**
  @@ -148,40 +167,34 @@
        */
       public Shape getLogicalHighlightShape(int begin, int end) {
   
  -        Rectangle2D shape = null;
  +        GeneralPath shape = null;
           begin = Math.max(0, begin);
           end = Math.min(end, gv.getNumGlyphs());
  -        for (int i=begin; i<end; ++i) {
  -            Shape gbounds = gv.getGlyphLogicalBounds(i);
  -            // XXX !!! there is a bug somewhere in GlyphVector!
  -            // the glyph logical bounds returned above don't contain the
  -            // glyph !
  -  
  -            Rectangle2D gbounds2d = gbounds.getBounds2D();
  -            gbounds = new Rectangle2D.Double(gbounds2d.getX(), gbounds2d.getY(),
  -                                    gbounds2d.getWidth(),
  -                                    gbounds2d.getHeight()-gbounds2d.getY());
  -            gbounds2d = gbounds.getBounds2D();
  -            if (shape == null) {
  -               shape = gbounds2d;
  -            } else {
  -               shape.add(gbounds2d);
  -            }
  -            // XXX: FIXME: should not always be a rectangle 
  -            // (oblique text case, for instance)
  -            // also, gbounds2d does not seem to be a correct
  -            // enclosing rectangle in all cases (maybe we need to use
  -            // a transform ?)
   
  +        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 {
  +                   shape.append(gbounds2d, false);
  +                }
  +
  +            }
           }
   
           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 
  +     *     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.
  @@ -193,16 +206,9 @@
           GlyphMetrics gm;
           float maxX = (float) gv.getVisualBounds().getX();
           for (int i=begin; i<end; ++i) {
  -            Shape gbounds = gv.getGlyphLogicalBounds(i);
  +            Shape gbounds = getGlyphLogicalBounds(i);
   
  -            // XXX !!! there is a bug somewhere in GlyphVector!
  -            // The gbounds above is not correct!
  -  
               Rectangle2D gbounds2d = gbounds.getBounds2D();
  -            gbounds = new Rectangle2D.Double(gbounds2d.getX(), gbounds2d.getY(),
  -                                    gbounds2d.getWidth(),
  -                                    gbounds2d.getHeight()-gbounds2d.getY());
  -            gbounds2d = gbounds.getBounds2D();
   
               if (gbounds2d.getX()+gbounds2d.getWidth() > maxX) {
                   maxX = (float) (gbounds2d.getX()+gbounds2d.getWidth());
  @@ -213,22 +219,18 @@
   
               if (gbounds.contains(x, y)) {
                   gm = gv.getGlyphMetrics(i);
  -                boolean isRightHalf = 
  +                boolean isRightHalf =
                       (x > (gbounds2d.getX()+(gbounds2d.getWidth()/2d)));
                   boolean isLeadingEdge = !isRightHalf;
                   textHit = new TextHit(i, isLeadingEdge);
  -              //System.out.println("Hit at "+i+", leadingEdge "+isLeadingEdge);
  +                //System.out.println("Hit at "+i+", leadingEdge "+isLeadingEdge);
                   return textHit;
               }
  -        }
  -        // XXX: fallthrough below is invalid for text whose primary 
  -        // direction is not LTR
  -        if (x >= maxX) {
  -            textHit = new TextHit(end, false);
  -        } else {
  -            textHit = new TextHit(0, true);
           }
   
  +        // fallthrough: in text bbox but not on a glyph
  +        textHit = new TextHit(-1, false);
  +
           return textHit;
       }
   
  @@ -248,6 +250,104 @@
           // XXX: probably wrong for CTL, work to be done here!
       }
   
  +
  +    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 = gv.getGlyphPosition(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 = gv.getGlyphPosition(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 currY = y;
  +            float currX = x;
  +            while (end<c) {
  +                lbox = (Rectangle2D.Double) glyphLogicalBounds[end];
  +                currY = (float) gv.getGlyphPosition(end).getY();
  +                currX = (float) gv.getGlyphPosition(end).getX();
  +                if ((currX < x) || (currY != y)) {
  +                    if (end > begin) --end;
  +                    break;
  +                }
  +                miny =
  +                  Math.min((float) lbox.getY(), miny);
  +                float h = (float) (lbox.getY() + lbox.getHeight());
  +                maxy =
  +                  Math.max(h, maxy);
  +                ++end;
  +            }
  +            i = end;
  +
  +            Rectangle2D.Double lboxPrev = null;
  +
  +            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 + (lboxPrev.getX()+lboxPrev.getWidth()))/2f;
  +                    glyphLogicalBounds[n-1] =
  +                         new Rectangle2D.Double(
  +                              lboxPrev.getX(), lboxPrev.getY(),
  +                              (double) (x - lboxPrev.getX()),
  +                              lboxPrev.getHeight());
  +                }
  +
  +                lbox =
  +                     new Rectangle2D.Double(
  +                          (double) x, (double) miny,
  +                          lbox.getWidth()+(lbox.getX() - x), (double) (maxy - miny));
  +
  +                lboxPrev = lbox;
  +                glyphLogicalBounds[n] = lbox;
  +            }
  +        }
  +    }
  +
   //inner classes
   
       protected class ReorderedCharacterIterator implements CharacterIterator {
  @@ -265,30 +365,30 @@
               ndx = begin;
               c = new char[end-begin];
   
  -            // set increment and initial array index according to 
  +            // set increment and initial array index according to
               // run direction of this aci
   
  -            int inc = (aci.getAttribute(TextAttribute.RUN_DIRECTION) == 
  +            int inc = (aci.getAttribute(TextAttribute.RUN_DIRECTION) ==
                                       TextAttribute.RUN_DIRECTION_LTR) ? 1 : -1;
   
               ndx = (inc > 0) ? begin : end-1;
   
               // reordering section
  -            char ch = aci.first(); 
  +            char ch = aci.first();
               while (ch != CharacterIterator.DONE) {
   
                    // get BiDi embedding
  -                 Integer embed = (Integer) aci.getAttribute(TextAttribute.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);  
  -                
  +                 int runLimit = aci.getRunLimit(TextAttribute.BIDI_EMBEDDING);
  +
                    boolean isReversed = false;
                    int runEndNdx = ndx;
   
                    if (embed != null) {
                        isReversed = (Math.abs(Math.IEEEremainder((double)embed.intValue(), 2d)) < 0.1) ? false : true;
  -                     if (isReversed) {                         
  +                     if (isReversed) {
                            runEndNdx = ndx + inc*(runLimit-aciIndex);
                            inc = -inc;
                            ndx = runEndNdx + inc;
  @@ -415,6 +515,52 @@
       protected Font getFont(AttributedCharacterIterator aci) {
           aci.first();
           return new Font(aci.getAttributes());
  +    }
  +
  +    protected void doExplicitGlyphLayout() {
  +        char ch = aci.first();
  +        int i=0;
  +        float[] gp = new float[(gv.getNumGlyphs()+1)*2];
  +        gp = (float[]) gv.getGlyphPositions(0, gv.getNumGlyphs(), gp).clone();
  +        float curr_x_pos = gp[0] + (float) offset.getX();
  +        float curr_y_pos = gp[1] + (float) offset.getY();
  +        //System.out.print("Explicit layout for: ");
  +        while ((ch != CharacterIterator.DONE) && (i < gp.length/2)) {
  +            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);
  +            if (x != null) {
  +                //System.out.println("X explicit");
  +                curr_x_pos = x.floatValue();
  +            } else if (dx != null) {
  +                //System.out.println("DX explicit");
  +                curr_x_pos += dx.floatValue();
  +            }
  +
  +            if (y != null) {
  +                curr_y_pos = y.floatValue();
  +            } else if (dy != null) {
  +                curr_y_pos += dy.floatValue();
  +            } else if (i>0) {
  +                curr_y_pos += gp[i*2 + 1]-gp[i*2 - 1];
  +            }
  +            gv.setGlyphPosition(i, new Point2D.Float(curr_x_pos,  curr_y_pos));
  +            //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;
  +        }
  +        //System.out.println();
  +
  +        advance = new Point2D.Float((float) (curr_x_pos-offset.getX()),
  +                                    (float) (curr_y_pos-offset.getY()));
  +        computeGlyphLogicalBounds();
       }
   
   }
  
  
  
  1.3       +34 -9     xml-batik/sources/org/apache/batik/refimpl/gvt/text/TextLayoutAdapter.java
  
  Index: TextLayoutAdapter.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/gvt/text/TextLayoutAdapter.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- TextLayoutAdapter.java	2001/01/05 17:05:13	1.2
  +++ TextLayoutAdapter.java	2001/01/15 17:10:54	1.3
  @@ -13,6 +13,7 @@
   import java.awt.Stroke;
   import java.awt.BasicStroke;
   import java.awt.geom.Rectangle2D;
  +import java.awt.geom.Point2D;
   import java.awt.geom.AffineTransform;
   import java.awt.geom.GeneralPath;
   import java.awt.font.TextLayout;
  @@ -29,15 +30,17 @@
    * @see org.apache.batik.gvt.TextPainter.
    *
    * @author <a href="bill.haneman@ireland.sun.com>Bill Haneman</a>
  - * @version $Id: TextLayoutAdapter.java,v 1.2 2001/01/05 17:05:13 billh Exp $
  + * @version $Id: TextLayoutAdapter.java,v 1.3 2001/01/15 17:10:54 billh Exp $
    */
   public class TextLayoutAdapter implements TextSpanLayout {
   
       private TextLayout layout;
       private AttributedCharacterIterator aci;
  +    private Point2D offset;
   
  -    public TextLayoutAdapter(TextLayout layout, AttributedCharacterIterator aci) {
  +    public TextLayoutAdapter(TextLayout layout, Point2D offset, AttributedCharacterIterator aci) {
           this.layout = layout;
  +        this.offset = offset;
           this.aci = aci;
       }
   
  @@ -49,7 +52,7 @@
        * @param y the y position of the rendered layout origin.
        */
       public void draw(Graphics2D g2d, float x, float y) {
  -        layout.draw(g2d, x, y);
  +        layout.draw(g2d, x+(float) offset.getX(), y+(float) offset.getY());
       }
   
       /**
  @@ -58,7 +61,9 @@
        * @param t an AffineTransform to apply to the outline before returning it.
        */
       public Shape getOutline(AffineTransform t) {
  -        return layout.getOutline(t);
  +        AffineTransform nt = (AffineTransform) t.clone();
  +        nt.translate(offset.getX(), offset.getY());
  +        return layout.getOutline(nt);
       }
   
       /**
  @@ -81,14 +86,20 @@
           if ((decorationType & DECORATION_OVERLINE) != 0) {
                g.append(getOverlineShape(aci, layout), false);
           }
  -        return t.createTransformedShape(g);
  +        AffineTransform nt = (AffineTransform) t.clone();
  +        nt.translate(offset.getX(), offset.getY());
  +        return nt.createTransformedShape(g);
       }
   
       /**
        * Returns the rectangular bounds of the completed glyph layout.
        */
       public Rectangle2D getBounds() {
  -        return layout.getBounds();
  +        Rectangle2D bounds = layout.getBounds();
  +        return new Rectangle2D.Double(bounds.getX()+offset.getX(),
  +                                      bounds.getY()+offset.getY(),
  +                                      bounds.getWidth(), 
  +                                      bounds.getHeight());
       }
   
       /**
  @@ -97,7 +108,7 @@
       public Rectangle2D getDecoratedBounds() {
           Rectangle2D dbounds = getDecorationOutline(
             DECORATION_UNDERLINE|DECORATION_OVERLINE|DECORATION_STRIKETHROUGH,
  -                     new AffineTransform()).getBounds2D();
  +          new AffineTransform()).getBounds2D();
          return dbounds.createUnion(getBounds());
       }
   
  @@ -112,13 +123,26 @@
       }
   
       /**
  +     * 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 new Point2D.Float(layout.getAdvance(), 0f);
  +    }
  +
  +    /**
        * 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) {
  -        return layout.getLogicalHighlightShape(begin, end);
  +        AffineTransform nt = AffineTransform.getTranslateInstance(
  +                                           offset.getX(), offset.getY());
  +        return nt.createTransformedShape(
  +                  layout.getLogicalHighlightShape(begin, end));
       }
   
       /**
  @@ -130,7 +154,8 @@
        * @param y the y coordinate of the point to be tested.
        */
       public TextHit hitTestChar(float x, float y) {
  -        TextHitInfo hit = layout.hitTestChar(x, y);
  +        TextHitInfo hit = layout.hitTestChar(x-(float) offset.getX(), 
  +                                             y-(float) offset.getY());
           return new TextHit(hit.getCharIndex(), hit.isLeadingEdge());
       }
   
  
  
  
  1.70      +2 -2      xml-batik/sources/org/apache/batik/refimpl/util/JSVGCanvas.java
  
  Index: JSVGCanvas.java
  ===================================================================
  RCS file: /home/cvs/xml-batik/sources/org/apache/batik/refimpl/util/JSVGCanvas.java,v
  retrieving revision 1.69
  retrieving revision 1.70
  diff -u -r1.69 -r1.70
  --- JSVGCanvas.java	2001/01/14 21:27:47	1.69
  +++ JSVGCanvas.java	2001/01/15 17:10:56	1.70
  @@ -114,7 +114,7 @@
    *
    * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
    * @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
  - * @version $Id: JSVGCanvas.java,v 1.69 2001/01/14 21:27:47 tkormann Exp $
  + * @version $Id: JSVGCanvas.java,v 1.70 2001/01/15 17:10:56 billh Exp $
    */
   public class JSVGCanvas
       extends    JComponent
  @@ -869,7 +869,7 @@
        * rendering thread and cue another repaint.  Otherwise,
        * it simply repaints the offscreen buffer and overlays.
        */
  -    protected void paintComponent(Graphics g) {
  +    public void paintComponent(Graphics g) {
   
           if (!EventQueue.isDispatchThread()) {
               System.err.println(
  
  
  

Mime
View raw message