From b1a82601c9adee0d3b41d4652314ab67e0efeedc Mon Sep 17 00:00:00 2001 From: hneemann Date: Tue, 27 Nov 2018 21:15:17 +0100 Subject: [PATCH] Improved the Polygon and the PolygonParser to deal with compound paths. --- .../digital/draw/graphics/GraphicMinMax.java | 9 +- .../digital/draw/graphics/GraphicSVG.java | 19 +- .../digital/draw/graphics/GraphicSwing.java | 20 +- .../digital/draw/graphics/Polygon.java | 335 ++++++++++++++---- .../draw/graphics/PolygonConverter.java | 7 +- .../digital/draw/graphics/PolygonParser.java | 17 +- .../linemerger/GraphicLineCollector.java | 2 +- .../digital/draw/graphics/PolygonTest.java | 46 +-- .../linemerger/GraphicLineCollectorTest.java | 83 +++++ 9 files changed, 400 insertions(+), 138 deletions(-) create mode 100644 src/test/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollectorTest.java diff --git a/src/main/java/de/neemann/digital/draw/graphics/GraphicMinMax.java b/src/main/java/de/neemann/digital/draw/graphics/GraphicMinMax.java index 103369a8b..765ac73ea 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/GraphicMinMax.java +++ b/src/main/java/de/neemann/digital/draw/graphics/GraphicMinMax.java @@ -57,8 +57,13 @@ public class GraphicMinMax implements Graphic { @Override public void drawPolygon(Polygon p, Style style) { - for (VectorInterface v : p) - check(v); + for (Polygon.PathElement v : p) { + if (v==null) + System.out.println(v); + final VectorInterface point = v.getPoint(); + if (point != null) + check(point); + } } @Override diff --git a/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java b/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java index 95202874b..e32011a46 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java +++ b/src/main/java/de/neemann/digital/draw/graphics/GraphicSVG.java @@ -113,22 +113,11 @@ public class GraphicSVG implements Graphic { @Override public void drawPolygon(Polygon p, Style style) { try { - //modification of loop variable i is intended! - //CHECKSTYLE.OFF: ModifiedControlVariable - w.write("\n"); else diff --git a/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java b/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java index b051fbb57..638b970b1 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java +++ b/src/main/java/de/neemann/digital/draw/graphics/GraphicSwing.java @@ -64,25 +64,7 @@ public class GraphicSwing implements Graphic { public void drawPolygon(Polygon p, Style style) { applyStyle(style); Path2D path = new GeneralPath(); - //modification of loop variable i is intended! - //CHECKSTYLE.OFF: ModifiedControlVariable - for (int i = 0; i < p.size(); i++) { - if (i == 0) { - path.moveTo(p.get(i).getXFloat(), p.get(i).getYFloat()); - } else { - if (p.isBezierStart(i)) { - path.curveTo(p.get(i).getXFloat(), p.get(i).getYFloat(), - p.get(i + 1).getXFloat(), p.get(i + 1).getYFloat(), - p.get(i + 2).getXFloat(), p.get(i + 2).getYFloat()); - i += 2; - } else - path.lineTo(p.get(i).getXFloat(), p.get(i).getYFloat()); - } - } - //CHECKSTYLE.ON: ModifiedControlVariable - - if (p.isClosed()) - path.closePath(); + p.drawTo(path); if (style.isFilled() && p.isClosed()) gr.fill(path); diff --git a/src/main/java/de/neemann/digital/draw/graphics/Polygon.java b/src/main/java/de/neemann/digital/draw/graphics/Polygon.java index ac8465daf..5e4253475 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/Polygon.java +++ b/src/main/java/de/neemann/digital/draw/graphics/Polygon.java @@ -5,18 +5,19 @@ */ package de.neemann.digital.draw.graphics; +import java.awt.geom.Path2D; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; /** * A polygon representation used by the {@link Graphic} interface. */ -public class Polygon implements Iterable { +public class Polygon implements Iterable { - private final ArrayList points; - private final HashSet isBezierStart; + private final ArrayList path; private boolean closed; + private boolean hasSpecialElements = false; + private boolean evenOdd; /** * Creates e new closed polygon @@ -41,9 +42,10 @@ public class Polygon implements Iterable { * @param closed true if polygon is closed */ public Polygon(ArrayList points, boolean closed) { - this.points = points; this.closed = closed; - isBezierStart = new HashSet<>(); + this.path = new ArrayList<>(); + for (VectorInterface p : points) + add(p); } /** @@ -71,10 +73,17 @@ public class Polygon implements Iterable { * @return this for chained calls */ public Polygon add(VectorInterface p) { - points.add(p); + if (path.isEmpty()) + path.add(new MoveTo(p)); + else + path.add(new LineTo(p)); return this; } + private void add(PathElement pe) { + path.add(pe); + } + /** * Adds a new cubic bezier curve to the polygon. * @@ -84,45 +93,29 @@ public class Polygon implements Iterable { * @return this for chained calls */ public Polygon add(VectorInterface c1, VectorInterface c2, VectorInterface p) { - if (points.size() == 0) + if (path.size() == 0) throw new RuntimeException("cubic bezier curve is not allowed to be the first path element"); - isBezierStart.add(points.size()); - points.add(c1); - points.add(c2); - points.add(p); + path.add(new CurveTo(c1, c2, p)); + hasSpecialElements = true; return this; } /** - * Returns true if the point with the given index is a bezier start point - * - * @param n the index - * @return true if point is bezier start + * @return true if filled in even odd mode */ - public boolean isBezierStart(int n) { - return isBezierStart.contains(n); + public boolean getEvenOdd() { + return evenOdd; } /** - * @return the number of points - */ - public int size() { - return points.size(); - } - - /** - * Returns one of the points + * Sets the even odd mode used to fill the polygon * - * @param i the index - * @return the i'th point + * @param evenOdd true is even odd mode is needed + * @return this for chained calls */ - public VectorInterface get(int i) { - return points.get(i); - } - - @Override - public Iterator iterator() { - return points.iterator(); + public Polygon setEvenOdd(boolean evenOdd) { + this.evenOdd = evenOdd; + return this; } /** @@ -140,28 +133,43 @@ public class Polygon implements Iterable { } private boolean check(VectorInterface p1, VectorInterface p2) { + if (closed) + return false; + if (p1.equals(getFirst())) { - points.add(0, p2); + if (p2.equals(getLast())) + closed = true; + else { + removeInitialMoveTo(); + path.add(0, new MoveTo(p2)); + } return true; } else if (p1.equals(getLast())) { - points.add(p2); + if (p2.equals(getFirst())) + closed = true; + else + path.add(new LineTo(p2)); return true; } else return false; } + private void removeInitialMoveTo() { + path.set(0, new LineTo(path.get(0))); + } + /** * @return the first point of the polygon */ public VectorInterface getFirst() { - return points.get(0); + return path.get(0).getPoint(); } /** * @return the last point of the polygon */ public VectorInterface getLast() { - return points.get(points.size() - 1); + return path.get(path.size() - 1).getPoint(); } /** @@ -171,10 +179,17 @@ public class Polygon implements Iterable { * @return this for chained calls */ public Polygon append(Polygon p2) { - if (!p2.isBezierStart.isEmpty()) + if (hasSpecialElements || p2.hasSpecialElements) throw new RuntimeException("append of bezier not supported"); - for (int i = 1; i < p2.points.size(); i++) - points.add(p2.points.get(i)); + + if (p2.getLast().equals(getFirst())) { + for (int i = 1; i < p2.path.size() - 1; i++) + add(p2.path.get(i).getPoint()); + closed = true; + } else { + for (int i = 1; i < p2.path.size(); i++) + add(p2.path.get(i).getPoint()); + } return this; } @@ -184,11 +199,11 @@ public class Polygon implements Iterable { * @return returns this polygon with reverse order of points */ public Polygon reverse() { - if (!isBezierStart.isEmpty()) - throw new RuntimeException("reverse of bezier not supported"); + if (hasSpecialElements) + throw new RuntimeException("append of bezier not supported"); Polygon p = new Polygon(closed); - for (int i = points.size() - 1; i >= 0; i--) - p.add(points.get(i)); + for (int i = path.size() - 1; i >= 0; i--) + p.add(path.get(i).getPoint()); return p; } @@ -202,43 +217,24 @@ public class Polygon implements Iterable { if (transform == Transform.IDENTITY) return this; - Polygon p = new Polygon(closed); - for (VectorInterface v : points) - p.add(v.transform(transform)); - p.isBezierStart.addAll(isBezierStart); + Polygon p = new Polygon(closed).setEvenOdd(evenOdd); + for (PathElement pe : path) + p.add(pe.transform(transform)); return p; } @Override public String toString() { - StringBuilder sb = new StringBuilder("M "); - VectorInterface v = points.get(0); - sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" "); - //modification of loop variable i is intended! - //CHECKSTYLE.OFF: ModifiedControlVariable - for (int i = 1; i < points.size(); i++) { - v = points.get(i); - if (isBezierStart.contains(i)) { - sb.append("C ").append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" "); - v = points.get(i + 1); - sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" "); - v = points.get(i + 2); - sb.append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" "); - i += 2; - } else - sb.append("L ").append(str(v.getXFloat())).append(",").append(str(v.getYFloat())).append(" "); + StringBuilder sb = new StringBuilder(); + for (PathElement pe : path) { + if (sb.length() > 0) + sb.append(' '); + sb.append(pe.toString()); } - //CHECKSTYLE.ON: ModifiedControlVariable - if (closed) - sb.append("z"); - return sb.toString(); - } - private static String str(float f) { - if (f == Math.round(f)) - return Integer.toString(Math.round(f)); - else - return Float.toString(f); + if (closed) + sb.append(" Z"); + return sb.toString(); } /** @@ -258,4 +254,191 @@ public class Polygon implements Iterable { void setClosed(boolean closed) { this.closed = closed; } + + @Override + public Iterator iterator() { + return path.iterator(); + } + + /** + * Closes the actual path + */ + public void addClosePath() { + add(new ClosePath()); + } + + /** + * Adds a moveto to the path + * + * @param p the point to move to + */ + public void addMoveTo(VectorFloat p) { + add(new MoveTo(p)); + } + + /** + * Draw this polygon to a {@link Path2D} instance. + * + * @param path2d the Path2d instance. + */ + public void drawTo(Path2D path2d) { + for (PathElement pe : path) + pe.drawTo(path2d); + if (closed) + path2d.closePath(); + if (evenOdd) + path2d.setWindingRule(Path2D.WIND_EVEN_ODD); + } + + /** + * A element of the path + */ + public interface PathElement { + /** + * @return the coordinate of this path element + */ + VectorInterface getPoint(); + + /** + * Returns the transormated path element + * + * @param transform the transformation + * @return the transormated path element + */ + PathElement transform(Transform transform); + + /** + * Draws this path element to a Path2D instance. + * + * @param path2d the a Path2D instance + */ + void drawTo(Path2D path2d); + } + + private static String str(float f) { + if (f == Math.round(f)) + return Integer.toString(Math.round(f)); + else + return Float.toString(f); + } + + private static String str(VectorInterface p) { + return str(p.getXFloat()) + "," + str(p.getYFloat()); + } + + //LineTo can not be final because its overridden. Maybe checkstyle has a bug? + //CHECKSTYLE.OFF: FinalClass + private static class LineTo implements PathElement { + protected final VectorInterface p; + + private LineTo(VectorInterface p) { + this.p = p; + } + + private LineTo(PathElement pathElement) { + this(pathElement.getPoint()); + } + + @Override + public VectorInterface getPoint() { + return p; + } + + @Override + public PathElement transform(Transform transform) { + return new LineTo(p.transform(transform)); + } + + @Override + public void drawTo(Path2D path2d) { + path2d.lineTo(p.getXFloat(), p.getYFloat()); + } + + @Override + public String toString() { + return "L " + str(p); + } + } + //CHECKSTYLE.ON: FinalClass + + private static final class MoveTo extends LineTo { + private MoveTo(VectorInterface p) { + super(p); + } + + @Override + public String toString() { + return "M " + str(p); + } + + @Override + public void drawTo(Path2D path2d) { + path2d.moveTo(p.getXFloat(), p.getYFloat()); + } + + @Override + public PathElement transform(Transform transform) { + return new MoveTo(p.transform(transform)); + } + } + + private static final class CurveTo implements PathElement { + private final VectorInterface c1; + private final VectorInterface c2; + private final VectorInterface p; + + private CurveTo(VectorInterface c1, VectorInterface c2, VectorInterface p) { + this.c1 = c1; + this.c2 = c2; + this.p = p; + } + + @Override + public VectorInterface getPoint() { + return p; + } + + @Override + public PathElement transform(Transform transform) { + return new CurveTo( + c1.transform(transform), + c2.transform(transform), + p.transform(transform) + ); + } + + @Override + public String toString() { + return "C " + str(c1) + " " + str(c2) + " " + str(p); + } + + @Override + public void drawTo(Path2D path2d) { + path2d.curveTo(c1.getXFloat(), c1.getYFloat(), + c2.getXFloat(), c2.getYFloat(), + p.getXFloat(), p.getYFloat()); + } + } + + private class ClosePath implements PathElement { + @Override + public VectorInterface getPoint() { + return null; + } + + @Override + public PathElement transform(Transform transform) { + return this; + } + + @Override + public void drawTo(Path2D path2d) { + path2d.closePath(); + } + + @Override + public String toString() { + return "Z"; + } + } } diff --git a/src/main/java/de/neemann/digital/draw/graphics/PolygonConverter.java b/src/main/java/de/neemann/digital/draw/graphics/PolygonConverter.java index 35a15b72f..772705f8c 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/PolygonConverter.java +++ b/src/main/java/de/neemann/digital/draw/graphics/PolygonConverter.java @@ -25,12 +25,17 @@ public class PolygonConverter implements Converter { public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext marshallingContext) { Polygon p = (Polygon) o; writer.addAttribute("path", p.toString()); + writer.addAttribute("evenOdd", Boolean.toString(p.getEvenOdd())); } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext) { String path = reader.getAttribute("path"); - return Polygon.createFromPath(path); + boolean evenOdd = Boolean.parseBoolean(reader.getAttribute("evenOdd")); + final Polygon polygon = Polygon.createFromPath(path); + if (polygon != null) + polygon.setEvenOdd(evenOdd); + return polygon; } } diff --git a/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java b/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java index da4a49117..3c45be139 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java +++ b/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java @@ -117,6 +117,7 @@ public class PolygonParser { public Polygon create() throws ParserException { Polygon p = new Polygon(false); Token tok; + boolean closedPending = false; while ((tok = next()) != Token.EOF) { if (tok == Token.NUMBER) { unreadToken(); @@ -127,11 +128,19 @@ public class PolygonParser { } switch (command) { case 'M': - p.add(nextVector()); + if (closedPending) { + closedPending = false; + p.addClosePath(); + } + p.addMoveTo(nextVector()); clearControl(); break; case 'm': - p.add(nextVectorInc()); + if (closedPending) { + closedPending = false; + p.addClosePath(); + } + p.addMoveTo(nextVectorInc()); clearControl(); break; case 'V': @@ -196,13 +205,15 @@ public class PolygonParser { break; case 'Z': case 'z': - p.setClosed(true); + closedPending = true; clearControl(); break; default: throw new ParserException("unsupported path command " + command); } } + if (closedPending) + p.setClosed(true); return p; } diff --git a/src/main/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollector.java b/src/main/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollector.java index 28a79c770..470305095 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollector.java +++ b/src/main/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollector.java @@ -75,7 +75,7 @@ public class GraphicLineCollector implements Graphic { private void tryMerge(Polygon p1) { for (Polygon p2 : polyList) - if (p1 != p2) { + if (p1 != p2 && !p1.isClosed() && !p2.isClosed()) { if (p1.getLast().equals(p2.getFirst())) { p1.append(p2); polyList.remove(p2); diff --git a/src/test/java/de/neemann/digital/draw/graphics/PolygonTest.java b/src/test/java/de/neemann/digital/draw/graphics/PolygonTest.java index 03c52bba9..bfc64feb2 100644 --- a/src/test/java/de/neemann/digital/draw/graphics/PolygonTest.java +++ b/src/test/java/de/neemann/digital/draw/graphics/PolygonTest.java @@ -10,42 +10,46 @@ import junit.framework.TestCase; public class PolygonTest extends TestCase { public void testPath() { - checkLine(Polygon.createFromPath("m 10,10 L 20,10 20,20 10,20 z")); - checkLine(Polygon.createFromPath("m 10,10 l 10,0 0,10 -10,0 z")); - checkLine(Polygon.createFromPath("m 10,10 h 10 v 10 h -10 z")); - checkLine(Polygon.createFromPath("m 10,10 H 20 V 20 H 10 z")); + checkLine(Polygon.createFromPath("m 10,10 L 20,10 20,20 10,20 Z")); + checkLine(Polygon.createFromPath("m 10,10 l 10,0 0,10 -10,0 Z")); + checkLine(Polygon.createFromPath("m 10,10 h 10 v 10 h -10 Z")); + checkLine(Polygon.createFromPath("m 10,10 H 20 V 20 H 10 Z")); } private void checkLine(Polygon p) { - checkCoor(p); - assertEquals("M 10,10 L 20,10 L 20,20 L 10,20 z", p.toString()); - } - - private void checkCoor(Polygon p) { - assertEquals(4, p.size()); - assertEquals(new VectorFloat(10, 10), p.get(0)); - assertEquals(new VectorFloat(20, 10), p.get(1)); - assertEquals(new VectorFloat(20, 20), p.get(2)); - assertEquals(new VectorFloat(10, 20), p.get(3)); + assertNotNull(p); + assertEquals("M 10,10 L 20,10 L 20,20 L 10,20 Z", p.toString()); } private void checkBezier(Polygon p) { - checkCoor(p); - assertEquals("M 10,10 C 20,10 20,20 10,20 z", p.toString()); + assertEquals("M 10,10 C 20,10 20,20 10,20 Z", p.toString()); } public void testBezierPath() { - checkBezier(Polygon.createFromPath("m 10,10 C 20 10 20 20 10 20 z")); - checkBezier(Polygon.createFromPath("m 10,10 c 10 0 10 10 0 10 z")); + checkBezier(Polygon.createFromPath("m 10,10 C 20 10 20 20 10 20 Z")); + checkBezier(Polygon.createFromPath("m 10,10 c 10 0 10 10 0 10 Z")); } public void testCubicReflect() { - assertEquals("M 0,0 C 0,10 10,10 10,0 C 10,-10 20,-10 20,0 C 20,10 30,10 30,0 z", - Polygon.createFromPath("m 0,0 c 0,10 10,10 10,0 s 10,-10 10,0 s 10,10 10,0 z").toString()); + assertEquals("M 0,0 C 0,10 10,10 10,0 C 10,-10 20,-10 20,0 C 20,10 30,10 30,0 Z", + Polygon.createFromPath("m 0,0 c 0,10 10,10 10,0 s 10,-10 10,0 s 10,10 10,0 Z").toString()); } public void testQuadraticReflect() { - assertEquals("M 0,0 C 20,20 40,20 60,0 C 80,-20 100,-20 120,0 C 140,20 160,20 180,0 C 200,-20 220,-20 240,0 ", + assertEquals("M 0,0 C 20,20 40,20 60,0 C 80,-20 100,-20 120,0 C 140,20 160,20 180,0 C 200,-20 220,-20 240,0", Polygon.createFromPath("m 0,0 Q 30,30 60,0 t 60,0 t 60,0 t 60,0").toString()); } + + public void testMultiPath() { + assertEquals("M 0,0 C 0,40 20,60 60,60 C 100,60 120,40 120,0 C 120,-40 100,-60 60,-60 C 20,-60 0,-40 0,0 Z M 30,0 C 30,20 40,30 60,30 C 80,30 90,20 90,0 C 90,-20 80,-30 60,-30 C 40,-30 30,-20 30,0 Z", + Polygon.createFromPath("M 0,0 Q 0,60 60,60 T 120,0 T 60,-60 T 0,0 z M 30,0 Q 30,30 60,30 T 90,0 T 60,-30 T 30,0 Z").toString()); + } + + public void testAppend() { + Polygon p1 = new Polygon(false).add(0, 0).add(0, 10).add(10, 10); + Polygon p2 = new Polygon(false).add(10, 10).add(10, 0).add(0, 0); + + assertEquals("M 0,0 L 0,10 L 10,10 L 10,0 Z", p1.append(p2).toString()); + } + } \ No newline at end of file diff --git a/src/test/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollectorTest.java b/src/test/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollectorTest.java new file mode 100644 index 000000000..4c174b3fd --- /dev/null +++ b/src/test/java/de/neemann/digital/draw/graphics/linemerger/GraphicLineCollectorTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2018 Helmut Neemann + * Use of this source code is governed by the GPL v3 license + * that can be found in the LICENSE file. + */ +package de.neemann.digital.draw.graphics.linemerger; + +import de.neemann.digital.draw.graphics.*; +import junit.framework.TestCase; + +import java.util.ArrayList; + +public class GraphicLineCollectorTest extends TestCase { + + public void testOpenSquare() { + GraphicLineCollector col = new GraphicLineCollector(); + col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL); + col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL); + + col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL); + + ArrayList poly = new ArrayList<>(); + col.drawTo(new MyGraphic(poly)); + + assertEquals(1, poly.size()); + assertEquals("M 10,0 L 0,0 L 0,10 L 10,10", poly.get(0).toString()); + } + + public void testClosedSquare() { + GraphicLineCollector col = new GraphicLineCollector(); + col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL); + col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL); + + col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL); + col.drawLine(new Vector(10, 0), new Vector(10, 10), Style.NORMAL); + + ArrayList poly = new ArrayList<>(); + col.drawTo(new MyGraphic(poly)); + + assertEquals(1, poly.size()); + assertEquals("M 10,0 L 0,0 L 0,10 L 10,10 Z", poly.get(0).toString()); + } + + public void testClosedSquare2() { + GraphicLineCollector col = new GraphicLineCollector(); + col.drawLine(new Vector(0, 0), new Vector(10, 0), Style.NORMAL); + col.drawLine(new Vector(0, 10), new Vector(10, 10), Style.NORMAL); + + col.drawLine(new Vector(0, 0), new Vector(0, 10), Style.NORMAL); + col.drawLine(new Vector(10, 0), new Vector(10, 10), Style.NORMAL); + + ArrayList poly = new ArrayList<>(); + col.drawTo(new MyGraphic(poly)); + + assertEquals(1, poly.size()); + assertEquals("M 10,0 L 0,0 L 0,10 L 10,10 Z", poly.get(0).toString()); + } + + private static class MyGraphic implements Graphic { + private final ArrayList poly; + + private MyGraphic(ArrayList poly) { + this.poly = poly; + } + + @Override + public void drawLine(VectorInterface p1, VectorInterface p2, Style style) { + } + + @Override + public void drawPolygon(Polygon p, Style style) { + poly.add(p); + } + + @Override + public void drawCircle(VectorInterface p1, VectorInterface p2, Style style) { + } + + @Override + public void drawText(VectorInterface p1, VectorInterface p2, String text, Orientation orientation, Style style) { + } + } +} \ No newline at end of file