deweese 2002/07/09 10:17:54 Modified: sources/org/apache/batik/ext/awt/geom ExtendedGeneralPath.java sources/org/apache/batik/gvt MarkerShapePainter.java test-resources/org/apache/batik/test samplesRendering.xml Added: samples/tests/spec/painting markersExt.svg sources/org/apache/batik/ext/awt/geom ExtendedPathIterator.java ExtendedShape.java ShapeExtender.java Log: 1) Created an ExtendedShape class that can give an ExtendedPathIterator that may return SEG_ARCTO. So we can properly implement markers on paths that have elliptical arcs in them. 2) These new interfaces also do not collapse multiple moveTo calls into one moveTo. This allows a path consisting of many 'm' or 'M' commands to place markers on the canvas. This update fixes a long standing bug in the rendering of markersOrientA.svg 3) Updated MarkerShapePainter to take advantage of these interfaces (calculating the tangent at the end of an elliptical arc is a pain). 4) Added a new test 'paints/markersExt.svg' for move paths and elliptical arc paths (it's funny to look at in the old Batik). 5) Added new markers test and the 'filters/feGaussianDefault.svg' to samplesRendering.xml PR: 5806 Revision Changes Path 1.1 xml-batik/samples/tests/spec/painting/markersExt.svg Index: markersExt.svg =================================================================== <?xml version="1.0" standalone="no"?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <!-- ====================================================================== --> <!-- 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. --> <!-- ====================================================================== --> <!-- ====================================================================== --> <!-- This test validates arcto and multiple move to --> <!-- --> <!-- @author [EMAIL PROTECTED] --> <!-- @version $Id: markersExt.svg,v 1.1 2002/07/09 17:17:53 deweese Exp $ --> <!-- ====================================================================== --> <?xml-stylesheet type="text/css" href="../../resources/style/test.css" ?> <svg id="body" width="450" height="500" viewBox="0 0 450 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" > <title>Marker Test</title> <style type="text/css"><![CDATA[ .markedPath { fill:none; stroke:black; stroke-width:4; } .label { filter:url(#textBG); } ]]></style> <g id="content"> <g transform="translate(0, 40)"> <text class="title" x="50%" y="0">Markers on paths with just moves</text> <text class="title" x="50%" y="1em">and elliptical arcs</text> </g> <defs> <filter id="textBG" x="-5%" y="-5%" width="110%" height="110%" color-interpolation-filters="sRGB"> <feFlood flood-color="lightGrey" result="foo"/> <feComposite in="SourceGraphic" in2="foo" /> </filter> <path id="testPathMoves" d="M 0 0 m 60 0 m 60 0" /> <path id="testPathMoves2" d="M 0 0 m 40 -15 m 40 30 m 40 -15" /> <path id="testPathArcs1" d="M 0 0 a 50 30 30 1 1 60 0 a 50 30 30 1 0 60 0" /> <path id="testPathArcs2" d="M 0 0 a 60 30 0 1 1 60 0 a 60 30 0 1 0 60 0" /> <path id="testPathArcs3" d="M 0 0 a 30 15 0 1 1 60 0 a 30 15 0 1 0 60 0" /> <path id="testPathArcs4" d="M 0 0 a 60 30 0 0 1 60 0 a 60 30 0 0 0 60 0" /> <path id="testPathArcs5" d="M 0 0 a 60 30 30 0 1 40 -15 a 60 30 30 0 0 40 30 a 60 30 30 0 0 40 -15" /> <g id="crossHair" style="marker:none" stroke="black"> <line y1="-5" x2="0" y2="5" /> <line x1="-5" x2="5" y2="0" /> </g> <g id="startEndCrossHairs"> <use x="0" y="20" xlink:href="#crossHair" /> <use x="30" y="0" xlink:href="#crossHair" /> </g> <g id="startMidEndCrossHairs"> <use x="0" y="20" xlink:href="#crossHair" /> <use x="20" y="0" xlink:href="#crossHair" /> <use x="50" y="10" xlink:href="#crossHair" /> </g> <g id="testPathCrossHairs"> <use xlink:href="#crossHair" /> <use x="60" xlink:href="#crossHair" /> <use x="120" xlink:href="#crossHair" /> </g> <g id="testPathCrossHairs2"> <use xlink:href="#crossHair" /> <use x="40" y="-15" xlink:href="#crossHair" /> <use x="80" y="15" xlink:href="#crossHair" /> <use x="120" xlink:href="#crossHair" /> </g> <!-- ============================= --> <!-- Simple Marker Definition --> <!-- ============================= --> <marker id="markerStart" markerWidth="20" markerHeight="20" viewBox="-1 -1 2 2" orient="auto" refX="0" refY="0" markerUnits="strokeWidth" overflow="visible"> <g opacity="0.5"> <circle r="1.8" stroke="black" fill="blue" stroke-width=".4" /> <path d="M 0 -1.8 L 0.3 -0.3 L 1.8 0 L 0.3 0.3 L 0 1.8 L -.2 0.3 L -1.8 0 L -0.3 -0.3 Z" fill="yellow" stroke="none" /> <circle r="0.2" cx="0" cy="-1.8" fill="gold"/> </g> </marker> <marker id="markerMid" markerWidth="20" markerHeight="20" viewBox="-1 -1 2 2" orient="auto" refX="0" refY="0" markerUnits="strokeWidth" overflow="visible"> <g opacity="0.5"> <circle r="1.8" stroke="black" fill="crimson" stroke-width=".4" /> <path d="M 0 -1.8 L 0.3 -0.3 L 1.8 0 L 0.3 0.3 L 0 1.8 L -.2 0.3 L -1.8 0 L -0.3 -0.3 Z" fill="yellow" stroke="none" /> <circle r="0.2" cx="0" cy="-1.8" fill="gold"/> </g> </marker> <marker id="markerEnd" markerWidth="20" markerHeight="20" viewBox="-1 -1 2 2" orient="auto" refX="0" refY="0" markerUnits="strokeWidth" overflow="visible"> <g opacity="0.5"> <circle r="1.8" stroke="black" fill="green" stroke-width=".4" /> <path d="M 0 -1.8 L 0.3 -0.3 L 1.8 0 L 0.3 0.3 L 0 1.8 L -.2 0.3 L -1.8 0 L -0.3 -0.3 Z" fill="yellow" stroke="none" /> <circle r="0.2" cx="0" cy="-1.8" fill="gold"/> </g> </marker> </defs> <g transform="translate(50, 100)"> <!-- ==================================== --> <!-- Move Tests --> <!-- ==================================== --> <g transform="translate(0, 0)" > <!-- Top reference point --> <use xlink:href="#testPathMoves" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs" /> <g class="label" transform="translate(60, 35)" text-anchor="middle"> <text y="0">Simple test of multiple moves</text> </g> </g> <!-- ==================================== --> <!-- Move Tests2 --> <!-- ==================================== --> <g transform="translate(230, 0)" > <!-- Top reference point --> <use xlink:href="#testPathMoves2" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs2" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Another test of multiple moves</text> </g> </g> <!-- ==================================== --> <!-- Arc Test 1 --> <!-- ==================================== --> <g transform="translate(0, 110)" > <!-- Top reference point --> <use xlink:href="#testPathArcs1" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Test of rotated arcs</text> </g> </g> <!-- ==================================== --> <!-- Arc Test 2 --> <!-- ==================================== --> <g transform="translate(230, 110)" > <!-- Top reference point --> <use xlink:href="#testPathArcs2" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Test of arcs 2</text> </g> </g> <!-- ==================================== --> <!-- Arc Test 3 --> <!-- ==================================== --> <g transform="translate(0, 220)" > <!-- Top reference point --> <use xlink:href="#testPathArcs3" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Test of arcs 3</text> </g> </g> <!-- ==================================== --> <!-- Arc Test 4 --> <!-- ==================================== --> <g transform="translate(230, 220)" > <!-- Top reference point --> <use xlink:href="#testPathArcs4" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Test of arcs 4</text> </g> </g> <!-- ==================================== --> <!-- Arc Test 5 --> <!-- ==================================== --> <g transform="translate(115, 320)" > <!-- Top reference point --> <use xlink:href="#testPathArcs5" marker-start="url(#markerStart)" marker-mid="url(#markerMid)" marker-end="url(#markerEnd)" class="markedPath" style="stroke-width:1"/> <use xlink:href="#testPathCrossHairs2" /> <g class="label" transform="translate(60, 50)" text-anchor="middle"> <text y="0">Test of arcs 5</text> </g> </g> </g> </g> <!-- ============================================================= --> <!-- Batik sample mark --> <!-- ============================================================= --> <use xlink:href="../../../batikLogo.svg#Batik_Tag_Box" /> </svg> 1.3 +326 -20 xml-batik/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java Index: ExtendedGeneralPath.java =================================================================== RCS file: /home/cvs/xml-batik/sources/org/apache/batik/ext/awt/geom/ExtendedGeneralPath.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- ExtendedGeneralPath.java 6 Sep 2001 09:01:26 -0000 1.2 +++ ExtendedGeneralPath.java 9 Jul 2002 17:17:53 -0000 1.3 @@ -8,8 +8,13 @@ package org.apache.batik.ext.awt.geom; -import java.awt.*; -import java.awt.geom.*; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; /** * The <code>ExtendedGeneralPath</code> class represents a geometric @@ -24,12 +29,17 @@ * @author <a href="mailto:[EMAIL PROTECTED]">Thierry Kormann</a> * @version $Id$ */ -public class ExtendedGeneralPath implements Shape, Cloneable { +public class ExtendedGeneralPath implements ExtendedShape, Cloneable { /** The enclosed general path. */ protected GeneralPath path; - /** + int numVals = 0; + int numSeg = 0; + double [] values = null; + int [] types = null; + + /** * Constructs a new <code>ExtendedGeneralPath</code>. */ public ExtendedGeneralPath() { @@ -59,7 +69,8 @@ * an arbitrary <code>Shape</code> object. */ public ExtendedGeneralPath(Shape s) { - path = new GeneralPath(s); + this(); + append(s, false); } /** @@ -89,24 +100,62 @@ boolean largeArcFlag, boolean sweepFlag, double x, double y) { - // - // Elliptical arc implementation based on the SVG specification notes - // // Ensure radii are valid if (rx == 0 || ry == 0) { lineTo((float) x, (float) y); return; } + // Get the current (x, y) coordinates of the path Point2D p2d = path.getCurrentPoint(); double x0 = p2d.getX(); double y0 = p2d.getY(); - if (x0 == x && y0 == y) { - // If the endpoints (x, y) and (x0, y0) are identical, then this - // is equivalent to omitting the elliptical arc segment entirely. - return; - } + if (x0 == x && y0 == y) { + // If the endpoints (x, y) and (x0, y0) are identical, then this + // is equivalent to omitting the elliptical arc segment entirely. + return; + } + + Arc2D arc = computeArc(x0, y0, rx, ry, angle, + largeArcFlag, sweepFlag, x, y); + if (arc == null) return; + + AffineTransform t = AffineTransform.getRotateInstance + (Math.toRadians(angle), arc.getCenterX(), arc.getCenterY()); + Shape s = t.createTransformedShape(arc); + path.append(s, true); + + makeRoom(7); + types [numSeg++] = ExtendedPathIterator.SEG_ARCTO; + values[numVals++] = rx; + values[numVals++] = ry; + values[numVals++] = angle; + values[numVals++] = largeArcFlag?1:0; + values[numVals++] = sweepFlag?1:0; + values[numVals++] = x; + values[numVals++] = y; + } + + + /** + * This constructs an unrotated Arc2D from the SVG specification of an + * Elliptical arc. To get the final arc you need to apply a rotation + * transform such as: + * + * AffineTransform.getRotateInstance + * (angle, arc.getX()+arc.getWidth()/2, arc.getY()+arc.getHeight()/2); + */ + public static Arc2D computeArc(double x0, double y0, + double rx, double ry, + double angle, + boolean largeArcFlag, + boolean sweepFlag, + double x, double y) { + // + // Elliptical arc implementation based on the SVG specification notes + // + // Compute the half distance between the current and the final point double dx2 = (x0 - x) / 2.0; double dy2 = (y0 - y) / 2.0; @@ -191,9 +240,8 @@ arc.height = ry * 2.0; arc.start = -angleStart; arc.extent = -angleExtent; - AffineTransform t = AffineTransform.getRotateInstance(angle, cx, cy); - Shape s = t.createTransformedShape(arc); - append(s, true); + + return arc; } /** @@ -201,6 +249,11 @@ */ public synchronized void moveTo(float x, float y) { path.moveTo(x, y); + + makeRoom(2); + types [numSeg++] = PathIterator.SEG_MOVETO; + values[numVals++] = x; + values[numVals++] = y; } /** @@ -208,6 +261,11 @@ */ public synchronized void lineTo(float x, float y) { path.lineTo(x, y); + + makeRoom(2); + types [numSeg++] = PathIterator.SEG_LINETO; + values[numVals++] = x; + values[numVals++] = y; } /** @@ -215,6 +273,13 @@ */ public synchronized void quadTo(float x1, float y1, float x2, float y2) { path.quadTo(x1, y1, x2, y2); + + makeRoom(4); + types [numSeg++] = PathIterator.SEG_QUADTO; + values[numVals++] = x1; + values[numVals++] = y1; + values[numVals++] = x2; + values[numVals++] = y2; } /** @@ -224,6 +289,15 @@ float x2, float y2, float x3, float y3) { path.curveTo(x1, y1, x2, y2, x3, y3); + + makeRoom(6); + types [numSeg++] = PathIterator.SEG_CUBICTO; + values[numVals++] = x1; + values[numVals++] = y1; + values[numVals++] = x2; + values[numVals++] = y2; + values[numVals++] = x3; + values[numVals++] = y3; } /** @@ -231,20 +305,107 @@ */ public synchronized void closePath() { path.closePath(); + + makeRoom(0); + types [numSeg++] = PathIterator.SEG_CLOSE; } /** * Delegates to the enclosed <code>GeneralPath</code>. */ public void append(Shape s, boolean connect) { - path.append(s, connect); + append(s.getPathIterator(new AffineTransform()), connect); } /** * Delegates to the enclosed <code>GeneralPath</code>. */ public void append(PathIterator pi, boolean connect) { - path.append(pi, connect); + + while (!pi.isDone()) { + double [] vals = new double[6]; + int type = pi.currentSegment(vals); + pi.next(); + if (connect && (numVals != 0)) { + if (type == PathIterator.SEG_MOVETO) { + double x = vals[0]; + double y = vals[1]; + if ((x != values[numVals-2]) || + (y != values[numVals-1])) { + // Change MOVETO to LINETO. + type = PathIterator.SEG_LINETO; + } else { + // Redundent segment (move to current loc) drop it... + if (pi.isDone()) break; // Nothing interesting + type = pi.currentSegment(vals); + pi.next(); + } + } + connect = false; + } + + switch(type) { + case PathIterator.SEG_CLOSE: closePath(); break; + case PathIterator.SEG_MOVETO: + moveTo ((float)vals[0], (float)vals[1]); break; + case PathIterator.SEG_LINETO: + lineTo ((float)vals[0], (float)vals[1]); break; + case PathIterator.SEG_QUADTO: + quadTo ((float)vals[0], (float)vals[1], + (float)vals[2], (float)vals[3]); break; + case PathIterator.SEG_CUBICTO: + curveTo((float)vals[0], (float)vals[1], + (float)vals[2], (float)vals[3], + (float)vals[4], (float)vals[5]); break; + } + } + } + + /** + * Delegates to the enclosed <code>GeneralPath</code>. + */ + public void append(ExtendedPathIterator epi, boolean connect) { + while (!epi.isDone()) { + double [] vals = new double[7]; + int type = epi.currentSegment(vals); + epi.next(); + if (connect && (numVals != 0)) { + if (type == PathIterator.SEG_MOVETO) { + double x = vals[0]; + double y = vals[1]; + if ((x != values[numVals-2]) || + (y != values[numVals-1])) { + // Change MOVETO to LINETO. + type = PathIterator.SEG_LINETO; + } else { + // Redundent segment (move to current loc) drop it... + if (epi.isDone()) break; // Nothing interesting + type = epi.currentSegment(vals); + epi.next(); + } + } + connect = false; + } + + switch(type) { + case PathIterator.SEG_CLOSE: closePath(); break; + case PathIterator.SEG_MOVETO: + moveTo ((float)vals[0], (float)vals[1]); break; + case PathIterator.SEG_LINETO: + lineTo ((float)vals[0], (float)vals[1]); break; + case PathIterator.SEG_QUADTO: + quadTo ((float)vals[0], (float)vals[1], + (float)vals[2], (float)vals[3]); break; + case PathIterator.SEG_CUBICTO: + curveTo((float)vals[0], (float)vals[1], + (float)vals[2], (float)vals[3], + (float)vals[4], (float)vals[5]); break; + case ExtendedPathIterator.SEG_ARCTO: + arcTo (vals[0], vals[1], vals[2], + (vals[3]!=0), (vals[4]!=0), + vals[5], vals[6]); break; + } + } } /** @@ -273,13 +434,18 @@ */ public synchronized void reset() { path.reset(); + + numSeg = 0; + numVals = 0; } /** * Delegates to the enclosed <code>GeneralPath</code>. */ public void transform(AffineTransform at) { - path.transform(at); + if (at.getType() != AffineTransform.TYPE_IDENTITY) + throw new IllegalArgumentException + ("ExtendedGeneralPaths can not be transformed"); } /** @@ -362,12 +528,152 @@ /** * Delegates to the enclosed <code>GeneralPath</code>. */ + public ExtendedPathIterator getExtendedPathIterator() { + return new EPI(); + } + + class EPI implements ExtendedPathIterator { + int segNum = 0; + int valsIdx = 0; + + public int currentSegment(double[] coords) { + int ret = types[segNum]; + switch (ret) { + case SEG_CLOSE: break; + case SEG_MOVETO: + case SEG_LINETO: + coords[0] = values[valsIdx]; + coords[1] = values[valsIdx+1]; + break; + case SEG_QUADTO: + coords[0] = values[valsIdx]; + coords[1] = values[valsIdx+1]; + coords[2] = values[valsIdx+2]; + coords[3] = values[valsIdx+3]; + break; + case SEG_CUBICTO: + coords[0] = values[valsIdx]; + coords[1] = values[valsIdx+1]; + coords[2] = values[valsIdx+2]; + coords[3] = values[valsIdx+3]; + coords[4] = values[valsIdx+4]; + coords[5] = values[valsIdx+5]; + break; + case SEG_ARCTO: + coords[0] = values[valsIdx]; + coords[1] = values[valsIdx+1]; + coords[2] = values[valsIdx+2]; + coords[3] = values[valsIdx+3]; + coords[4] = values[valsIdx+4]; + coords[5] = values[valsIdx+5]; + coords[6] = values[valsIdx+6]; + break; + } + // System.out.println("Seg: [" + segNum + "] type: " + ret + + // " vals: [" + coords[0] + ", " + coords[1] + + // "]"); + return ret; + } + + public int currentSegment(float[] coords) { + int ret = types[segNum]; + switch (ret) { + case SEG_CLOSE: break; + case SEG_MOVETO: + case SEG_LINETO: + coords[0] = (float)values[valsIdx]; + coords[1] = (float)values[valsIdx+1]; + break; + case SEG_QUADTO: + coords[0] = (float)values[valsIdx]; + coords[1] = (float)values[valsIdx+1]; + coords[2] = (float)values[valsIdx+2]; + coords[3] = (float)values[valsIdx+3]; + break; + case SEG_CUBICTO: + coords[0] = (float)values[valsIdx]; + coords[1] = (float)values[valsIdx+1]; + coords[2] = (float)values[valsIdx+2]; + coords[3] = (float)values[valsIdx+3]; + coords[4] = (float)values[valsIdx+4]; + coords[5] = (float)values[valsIdx+5]; + break; + case SEG_ARCTO: + coords[0] = (float)values[valsIdx]; + coords[1] = (float)values[valsIdx+1]; + coords[2] = (float)values[valsIdx+2]; + coords[3] = (float)values[valsIdx+3]; + coords[4] = (float)values[valsIdx+4]; + coords[5] = (float)values[valsIdx+5]; + coords[6] = (float)values[valsIdx+6]; + break; + } + return ret; + } + + public int getWindingRule() { + return path.getWindingRule(); + } + public boolean isDone() { + return segNum == numSeg; + } + public void next() { + int type = types[segNum++]; + switch (type) { + case SEG_CLOSE: break; + case SEG_MOVETO: + case SEG_LINETO: valsIdx+=2; break; + case SEG_QUADTO: valsIdx+=4; break; + case SEG_CUBICTO:valsIdx+=6; break; + case SEG_ARCTO: valsIdx+=7; break; + } + } + }; + + /** + * Delegates to the enclosed <code>GeneralPath</code>. + */ public Object clone() { try { ExtendedGeneralPath result = (ExtendedGeneralPath) super.clone(); result.path = (GeneralPath) path.clone(); + + result.values = new double[values.length]; + System.arraycopy(result.values, 0, values, 0, values.length); + result.numVals = numVals; + + result.types = new int[types.length]; + System.arraycopy(result.types, 0, types, 0, types.length); + result.numSeg = numSeg; + return result; } catch (CloneNotSupportedException ex) {} return null; + } + + private void makeRoom(int numValues) { + if (values == null) { + values = new double[2*numValues]; + types = new int[2]; + numVals = 0; + numSeg = 0; + return; + } + + if ((numVals + numValues) > values.length) { + int nlen = values.length*2; + if (nlen < (numVals + numValues)) + nlen = numVals + numValues; + + double [] nvals = new double[nlen]; + System.arraycopy(values, 0, nvals, 0, numVals); + values = nvals; + } + + if (numSeg == types.length) { + int [] ntypes = new int[types.length*2]; + System.arraycopy(types, 0, ntypes, 0, types.length); + types = ntypes; + } } } 1.1 xml-batik/sources/org/apache/batik/ext/awt/geom/ExtendedPathIterator.java Index: ExtendedPathIterator.java =================================================================== /***************************************************************************** * 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.ext.awt.geom; import java.awt.geom.PathIterator; /** * The <code>ExtendedPathIterator</code> class represents a geometric * path constructed from straight lines, quadratic and cubic (Bézier) * curves and elliptical arcs. This interface is identical to that of * PathIterator except it can return SEG_ARCTO from currentSegment, * also the array of values passed to currentSegment must be of length * 7 or an error will be thrown. * * This does not extend PathIterator as it would break the interface * contract for that class. * * @author <a href="mailto:[EMAIL PROTECTED]">Thomas DeWeese</a> * @version $Id: ExtendedPathIterator.java,v 1.1 2002/07/09 17:17:53 deweese Exp $ */ public interface ExtendedPathIterator { /** * The segment type constant that specifies that the preceding * subpath should be closed by appending a line segment back to * the point corresponding to the most recent SEG_MOVETO. */ public static final int SEG_CLOSE = PathIterator.SEG_CLOSE; /** * The segment type constant for a point that specifies the end * point of a line to be drawn from the most recently specified * point. */ public static final int SEG_MOVETO = PathIterator.SEG_MOVETO; /** * The segment type constant for a point that specifies the end * point of a line to be drawn from the most recently specified * point. */ public static final int SEG_LINETO = PathIterator.SEG_LINETO; /** * The segment type constant for the pair of points that specify a * quadratic parametric curve to be drawn from the most recently * specified point. The curve is interpolated by solving the * parametric control equation in the range (t=[0..1]) using the * most recently specified (current) point (CP), the first control * point (P1), and the final interpolated control point (P2). */ public static final int SEG_QUADTO = PathIterator.SEG_QUADTO; /** * The segment type constant for the set of 3 points that specify * a cubic parametric curve to be drawn from the most recently * specified point. The curve is interpolated by solving the * parametric control equation in the range (t=[0..1]) using the * most recently specified (current) point (CP), the first control * point (P1), the second control point (P2), and the final * interpolated control point (P3). */ public static final int SEG_CUBICTO = PathIterator.SEG_CUBICTO; /** The segment type constant for an elliptical arc. This consists of * Seven values [rx, ry, angle, largeArcFlag, sweepFlag, x, y]. * rx, ry are the radious of the ellipse. * angle is angle of the x axis of the ellipse. * largeArcFlag is zero if the smaller of the two arcs are to be used. * sweepFlag is zero if the 'left' branch is taken one otherwise. * x and y are the destination for the ellipse. */ public static final int SEG_ARCTO = 4321; /** The winding rule constant for specifying an even-odd rule for * determining the interior of a path. The even-odd rule specifies * that a point lies inside the path if a ray drawn in any * direction from that point to infinity is crossed by path * segments an odd number of times. */ public static final int WIND_EVEN_ODD = PathIterator.WIND_EVEN_ODD; /** * The winding rule constant for specifying a non-zero rule for * determining the interior of a path. The non-zero rule specifies * that a point lies inside the path if a ray drawn in any * direction from that point to infinity is crossed by path * segments a different number of times in the counter-clockwise * direction than the clockwise direction. */ public static final int WIND_NON_ZERO = PathIterator.WIND_NON_ZERO; public int currentSegment(double[] coords); public int currentSegment(float[] coords); public int getWindingRule(); public boolean isDone(); public void next(); } 1.1 xml-batik/sources/org/apache/batik/ext/awt/geom/ExtendedShape.java Index: ExtendedShape.java =================================================================== /***************************************************************************** * 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.ext.awt.geom; import java.awt.Shape; /** * The <code>ExtendedShape</code> class represents a geometric * path constructed from straight lines, quadratic and cubic (Bézier) * curves and elliptical arcs. * @author <a href="mailto:[EMAIL PROTECTED]">Thomas DeWeese</a> * @version $Id: ExtendedShape.java,v 1.1 2002/07/09 17:17:54 deweese Exp $ */ public interface ExtendedShape extends Shape { /** * Get an extended Path iterator that may return SEG_ARCTO commands */ public ExtendedPathIterator getExtendedPathIterator(); } 1.1 xml-batik/sources/org/apache/batik/ext/awt/geom/ShapeExtender.java Index: ShapeExtender.java =================================================================== /***************************************************************************** * 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.ext.awt.geom; import java.awt.Shape; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.AffineTransform; import java.awt.geom.PathIterator; /** * This class wraps a normal path into an extended path. * @author <a href="mailto:[EMAIL PROTECTED]">Thomas DeWeese</a> * @version $Id: ShapeExtender.java,v 1.1 2002/07/09 17:17:54 deweese Exp $ */ public class ShapeExtender implements ExtendedShape { Shape shape; public ShapeExtender(Shape shape) { this.shape = shape; } public boolean contains(double x, double y) { return shape.contains(x, y); } public boolean contains(double x, double y, double w, double h) { return shape.contains(x, y, w, h); } public boolean contains(Point2D p) { return shape.contains(p); } public boolean contains(Rectangle2D r) { return shape.contains(r); } public Rectangle getBounds() { return shape.getBounds(); } public Rectangle2D getBounds2D() { return shape.getBounds2D(); } public PathIterator getPathIterator(AffineTransform at) { return shape.getPathIterator(at); } public PathIterator getPathIterator(AffineTransform at, double flatness) { return shape.getPathIterator(at, flatness); } public ExtendedPathIterator getExtendedPathIterator() { return new EPIWrap(shape.getPathIterator(null)); } public boolean intersects(double x, double y, double w, double h) { return shape.intersects(x, y, w, h); } public boolean intersects(Rectangle2D r) { return shape.intersects(r); } public static class EPIWrap implements ExtendedPathIterator { PathIterator pi = null; public EPIWrap(PathIterator pi) { this.pi = pi; } public int currentSegment(double[] coords) { return pi.currentSegment(coords); } public int currentSegment(float[] coords) { return pi.currentSegment(coords); } public int getWindingRule() { return pi.getWindingRule(); } public boolean isDone() { return pi.isDone(); } public void next() { pi.next(); } }; } 1.8 +177 -84 xml-batik/sources/org/apache/batik/gvt/MarkerShapePainter.java Index: MarkerShapePainter.java =================================================================== RCS file: /home/cvs/xml-batik/sources/org/apache/batik/gvt/MarkerShapePainter.java,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- MarkerShapePainter.java 6 Feb 2002 17:52:37 -0000 1.7 +++ MarkerShapePainter.java 9 Jul 2002 17:17:54 -0000 1.8 @@ -12,6 +12,7 @@ import java.awt.Graphics2D; import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.PathIterator; @@ -19,6 +20,11 @@ import java.util.List; import java.util.Vector; +import org.apache.batik.ext.awt.geom.ExtendedGeneralPath; +import org.apache.batik.ext.awt.geom.ExtendedPathIterator; +import org.apache.batik.ext.awt.geom.ExtendedShape; +import org.apache.batik.ext.awt.geom.ShapeExtender; + /** * A shape painter that can be used to paint markers on a shape. * @@ -30,7 +36,7 @@ /** * The Shape to be painted. */ - protected Shape shape; + protected ExtendedShape extShape; /** * Start Marker @@ -88,7 +94,11 @@ if (shape == null) { throw new IllegalArgumentException(); } - this.shape = shape; + if (shape instanceof ExtendedShape) { + this.extShape = (ExtendedShape)shape; + } else { + this.extShape = new ShapeExtender(shape); + } } /** @@ -138,7 +148,12 @@ if (shape == null) { throw new IllegalArgumentException(); } - this.shape = shape; + if (shape instanceof ExtendedShape) { + this.extShape = (ExtendedShape)shape; + } else { + this.extShape = new ShapeExtender(shape); + } + this.startMarkerProxy = null; this.middleMarkerProxies = null; this.endMarkerProxy = null; @@ -146,12 +161,21 @@ } /** + * Gets the Shape this shape painter is associated with as an + * Extended Shape. + * + * @return shape associated with this painter */ + public ExtendedShape getExtShape(){ + return extShape; + } + + /** * Gets the Shape this shape painter is associated with. * * @return shape associated with this painter */ public Shape getShape(){ - return shape; + return extShape; } /** @@ -259,10 +283,10 @@ */ protected ProxyGraphicsNode buildStartMarkerProxy() { - PathIterator iter = getShape().getPathIterator(null); + ExtendedPathIterator iter = getExtShape().getExtendedPathIterator(); // Get initial point on the path - double coords[] = new double[6]; + double coords[] = new double[7]; int segType = 0; if (iter.isDone()) { @@ -282,7 +306,7 @@ double rotation = startMarker.getOrient(); if (Double.isNaN(rotation)) { if (!iter.isDone()) { - double next[] = new double[6]; + double next[] = new double[7]; int nextSegType = 0; nextSegType = iter.currentSegment(next); if(nextSegType == PathIterator.SEG_CLOSE){ @@ -316,7 +340,7 @@ */ protected ProxyGraphicsNode buildEndMarkerProxy() { - PathIterator iter = getShape().getPathIterator(null); + ExtendedPathIterator iter = getExtShape().getExtendedPathIterator(); int nPoints = 0; @@ -326,7 +350,7 @@ return null; } - double coords[] = new double[6]; + double coords[] = new double[7]; double moveTo[] = new double[2]; int segType = 0; segType = iter.currentSegment(coords); @@ -340,9 +364,10 @@ iter.next(); // Now, get the last two points on the path - double[] lastButOne = new double[6]; + double[] lastButOne = new double[7]; double[] last = {coords[0], coords[1], coords[2], - coords[3], coords[4], coords[5] }, tmp = null; + coords[3], coords[4], coords[5], coords[6] }; + double[] tmp = null; int lastSegType = segType; int lastButOneSegType = 0; @@ -398,17 +423,17 @@ } /** - * Builds a proxy <tt>GraphicsNode</tt> for the input <tt>Marker</tt> to be - * drawn at the middle positions + * Builds a proxy <tt>GraphicsNode</tt> for the input + * <tt>Marker</tt> to be drawn at the middle positions */ protected ProxyGraphicsNode[] buildMiddleMarkerProxies() { - PathIterator iter = getShape().getPathIterator(null); + ExtendedPathIterator iter = getExtShape().getExtendedPathIterator(); - double[] prev = new double[6]; - double[] cur = new double[6]; - double[] next = new double[6], tmp = null; - int prevSegType = 0, curSegType = 0, nextSegType = 0; + double[] prev = new double[7]; + double[] curr = new double[7]; + double[] next = new double[7], tmp = null; + int prevSegType = 0, currSegType = 0, nextSegType = 0; // Get the first three points on the path if (iter.isDone()) { @@ -430,15 +455,15 @@ return null; } - curSegType = iter.currentSegment(cur); + currSegType = iter.currentSegment(curr); - if (curSegType == PathIterator.SEG_MOVETO) { - moveTo[0] = cur[0]; - moveTo[1] = cur[1]; - } else if (curSegType == PathIterator.SEG_CLOSE) { - curSegType = PathIterator.SEG_LINETO; - cur[0] = moveTo[0]; - cur[1] = moveTo[1]; + if (currSegType == PathIterator.SEG_MOVETO) { + moveTo[0] = curr[0]; + moveTo[1] = curr[1]; + } else if (currSegType == PathIterator.SEG_CLOSE) { + currSegType = PathIterator.SEG_LINETO; + curr[0] = moveTo[0]; + curr[1] = moveTo[1]; } iter.next(); @@ -457,14 +482,14 @@ } proxies.addElement(createMiddleMarker(prev, prevSegType, - cur, curSegType, + curr, currSegType, next, nextSegType)); tmp = prev; - prev = cur; - prevSegType = curSegType; - cur = next; - curSegType = nextSegType; + prev = curr; + prevSegType = currSegType; + curr = next; + currSegType = nextSegType; next = tmp; iter.next(); @@ -479,22 +504,20 @@ /** * Creates a ProxyGraphicsNode for a middle marker. */ - private ProxyGraphicsNode createMiddleMarker(double[] prev, - int prevSegType, - double[] cur, - int curSegType, - double[] next, - int nextSegType){ + private ProxyGraphicsNode createMiddleMarker + (double[] prev, int prevSegType, + double[] curr, int currSegType, + double[] next, int nextSegType) { - // Turn the cur segment into a position - Point2D markerPosition = getSegmentTerminatingPoint(cur, curSegType); + // Turn the curr segment into a position + Point2D markerPosition = getSegmentTerminatingPoint(curr, currSegType); // If the marker's orient property is NaN, // the slope needs to be computed double rotation = middleMarker.getOrient(); if (Double.isNaN(rotation)) { rotation = computeRotation(prev, prevSegType, - cur, curSegType, + curr, currSegType, next, nextSegType); } @@ -514,21 +537,18 @@ /** * Returns the rotation according to the specified parameters. */ - private double computeRotation(double[] prev, - int prevSegType, - double[] cur, - int curSegType, - double[] next, - int nextSegType){ + private double computeRotation(double[] prev, int prevSegType, + double[] curr, int currSegType, + double[] next, int nextSegType){ // Compute in slope, i.e., the slope of the segment // going into the current point double[] inSlope = computeInSlope(prev, prevSegType, - cur, curSegType); + curr, currSegType); // Compute out slope, i.e., the slope of the segment // going out of the current point - double[] outSlope = computeOutSlope(cur, curSegType, + double[] outSlope = computeOutSlope(curr, currSegType, next, nextSegType); if (inSlope == null) { @@ -553,39 +573,76 @@ /** * Returns dx/dy for the in slope. */ - private double[] computeInSlope(double[] prev, - int prevSegType, - double[] cur, - int curSegType){ + private double[] computeInSlope(double[] prev, int prevSegType, + double[] curr, int currSegType){ // Compute point into which the slope runs - Point2D curEndPoint = getSegmentTerminatingPoint(cur, curSegType); + Point2D currEndPoint = getSegmentTerminatingPoint(curr, currSegType); double dx = 0; - double dy = 0; + double dy = 0; - switch(curSegType){ - case PathIterator.SEG_QUADTO: - // If the current segment is a line, quad or cubic curve. the slope - // is about equal to that of the line from the last control point - // and the curEndPoint - dx = curEndPoint.getX() - cur[0]; - dy = curEndPoint.getY() - cur[1]; - break; - case PathIterator.SEG_LINETO: + switch(currSegType){ + case PathIterator.SEG_LINETO: { // This is equivalent to a line from the previous segment's // terminating point and the current end point. Point2D prevEndPoint = - getSegmentTerminatingPoint(prev, prevSegType); - dx = curEndPoint.getX() - prevEndPoint.getX(); - dy = curEndPoint.getY() - prevEndPoint.getY(); + getSegmentTerminatingPoint(prev, prevSegType); + dx = currEndPoint.getX() - prevEndPoint.getX(); + dy = currEndPoint.getY() - prevEndPoint.getY(); + } + break; + case PathIterator.SEG_QUADTO: + // If the current segment is a line, quad or cubic curve. + // the slope is about equal to that of the line from the + // last control point and the curEndPoint + dx = currEndPoint.getX() - curr[0]; + dy = currEndPoint.getY() - curr[1]; break; case PathIterator.SEG_CUBICTO: - // If the current segment is a line, quad or cubic curve. the slope - // is about equal to that of the line from the last control point - // and the curEndPoint - dx = curEndPoint.getX() - cur[2]; - dy = curEndPoint.getY() - cur[3]; + // If the current segment is a quad or cubic curve. + // the slope is about equal to that of the line from the + // last control point and the curEndPoint + dx = currEndPoint.getX() - curr[2]; + dy = currEndPoint.getY() - curr[3]; + break; + case ExtendedPathIterator.SEG_ARCTO: { + // If the current segment is an ARCTO then we build the + // arc and ask for it's end angle and get the tangent there. + Point2D prevEndPoint = + getSegmentTerminatingPoint(prev, prevSegType); + boolean large = (curr[3]!=0.); + boolean goLeft = (curr[4]!=0.); + Arc2D arc = ExtendedGeneralPath.computeArc + (prevEndPoint.getX(), prevEndPoint.getY(), + curr[0], curr[1], curr[2], + large, goLeft, curr[5], curr[6]); + double theta = arc.getAngleStart()+arc.getAngleExtent(); + theta = Math.toRadians(theta); + dx = -arc.getWidth()/2.0*Math.sin(theta); + dy = arc.getHeight()/2.0*Math.cos(theta); + + // System.out.println("In Theta: " + Math.toDegrees(theta) + + // " Dx/Dy: " + dx + "/" + dy); + if (curr[2] != 0) { + double ang = Math.toRadians(-curr[2]); + double sinA = Math.sin(ang); + double cosA = Math.cos(ang); + double tdx = dx*cosA - dy*sinA; + double tdy = dx*sinA + dy*cosA; + dx = tdx; + dy = tdy; + } + // System.out.println(" Rotate: " + curr[2] + + // " Dx/Dy: " + dx + "/" + dy); + if (goLeft) { + dx = -dx; + } else { + dy = -dy; + } + // System.out.println(" GoLeft? " + goLeft + + // " Dx/Dy: " + dx + "/" + dy); + } break; case PathIterator.SEG_CLOSE: // Should not have any close at this point @@ -606,28 +663,62 @@ /** * Returns dx/dy for the out slope. */ - private double[] computeOutSlope(double[] cur, - int curSegType, - double[] next, - int nextSegType){ + private double[] computeOutSlope(double[] curr, int currSegType, + double[] next, int nextSegType){ - Point2D curEndPoint = getSegmentTerminatingPoint(cur, curSegType); + Point2D currEndPoint = getSegmentTerminatingPoint(curr, currSegType); double dx = 0, dy = 0; switch(nextSegType){ case PathIterator.SEG_CLOSE: - // Should not happen at this point, because all close segments have - // been replaced by lineTo segments. + // Should not happen at this point, because all close + // segments have been replaced by lineTo segments. break; case PathIterator.SEG_CUBICTO: case PathIterator.SEG_LINETO: case PathIterator.SEG_QUADTO: - // If the next segment is a line, quad or cubic curve. the slope is - // about equal to that of the line from curEndPoint and the first - // control point - dx = next[0] - curEndPoint.getX(); - dy = next[1] - curEndPoint.getY(); + // If the next segment is a line, quad or cubic curve. + // the slope is about equal to that of the line from + // curEndPoint and the first control point + dx = next[0] - currEndPoint.getX(); + dy = next[1] - currEndPoint.getY(); + break; + case ExtendedPathIterator.SEG_ARCTO: { + // If the current segment is an ARCTO then we build the + // arc and ask for it's end angle and get the tangent there. + boolean large = (next[3]!=0.); + boolean goLeft = (next[4]!=0.); + Arc2D arc = ExtendedGeneralPath.computeArc + (currEndPoint.getX(), currEndPoint.getY(), + next[0], next[1], next[2], + large, goLeft, next[5], next[6]); + double theta = arc.getAngleStart(); + theta = Math.toRadians(theta); + dx = -arc.getWidth()/2.0*Math.sin(theta); + dy = arc.getHeight()/2.0*Math.cos(theta); + // System.out.println("Out Theta: " + Math.toDegrees(theta) + + // " Dx/Dy: " + dx + "/" + dy); + if (next[2] != 0) { + double ang = Math.toRadians(-next[2]); + double sinA = Math.sin(ang); + double cosA = Math.cos(ang); + double tdx = dx*cosA - dy*sinA; + double tdy = dx*sinA + dy*cosA; + dx = tdx; + dy = tdy; + } + // System.out.println(" Rotate: " + next[2] + + // " Dx/Dy: " + dx + "/" + dy); + + if (goLeft) { + dx = -dx; + } else { + dy = -dy; + } + // System.out.println(" GoLeft? " + goLeft + + // " Dx/Dy: " + dx + "/" + dy); + } break; case PathIterator.SEG_MOVETO: // Cannot compute the out slope @@ -680,6 +771,8 @@ return new Point2D.Double(coords[0], coords[1]); case PathIterator.SEG_QUADTO: return new Point2D.Double(coords[2], coords[3]); + case ExtendedPathIterator.SEG_ARCTO: + return new Point2D.Double(coords[5], coords[6]); case PathIterator.SEG_CLOSE: default: throw new Error(); 1.80 +3 -1 xml-batik/test-resources/org/apache/batik/test/samplesRendering.xml Index: samplesRendering.xml =================================================================== RCS file: /home/cvs/xml-batik/test-resources/org/apache/batik/test/samplesRendering.xml,v retrieving revision 1.79 retrieving revision 1.80 diff -u -r1.79 -r1.80 --- samplesRendering.xml 4 Jul 2002 07:19:02 -0000 1.79 +++ samplesRendering.xml 9 Jul 2002 17:17:54 -0000 1.80 @@ -96,6 +96,7 @@ <test id="samples/tests/spec/filters/feComposite.svg" /> <test id="samples/tests/spec/filters/feConvolveMatrix.svg" /> <test id="samples/tests/spec/filters/feDisplacementMap.svg" /> + <test id="samples/tests/spec/filters/feGaussianDefault.svg" /> <test id="samples/tests/spec/filters/feImage.svg" /> <test id="samples/tests/spec/filters/feMerge.svg" /> <test id="samples/tests/spec/filters/feMorphology.svg" /> @@ -161,6 +162,7 @@ <test id="samples/tests/spec/painting/bboxOnText.svg" /> <test id="samples/tests/spec/painting/display.svg" /> <test id="samples/tests/spec/painting/image-rendering.svg" /> + <test id="samples/tests/spec/painting/markersExt.svg" /> <test id="samples/tests/spec/painting/markersMisc.svg" /> <test id="samples/tests/spec/painting/markersOrientA.svg" /> <test id="samples/tests/spec/painting/markersOrientB.svg" />
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]