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 9bcbd5a41..7b47e9acc 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/Polygon.java +++ b/src/main/java/de/neemann/digital/draw/graphics/Polygon.java @@ -76,7 +76,7 @@ public class Polygon implements Iterable { } /** - * Adds a new bezier line to the polygon. + * Adds a new cubic bezier curve to the polygon. * * @param c1 the first control point to add * @param c2 the second control point to add @@ -84,6 +84,8 @@ public class Polygon implements Iterable { * @return this for chained calls */ public Polygon add(VectorInterface c1, VectorInterface c2, VectorInterface p) { + if (points.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); 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 2fff84713..48ed5fc87 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java +++ b/src/main/java/de/neemann/digital/draw/graphics/PolygonParser.java @@ -18,6 +18,8 @@ public class PolygonParser { private float value; private float x; private float y; + private VectorInterface lastQuadraticControlPoint; + private VectorInterface lastCubicControlPoint; /** * Creates a new instance @@ -126,47 +128,76 @@ public class PolygonParser { switch (command) { case 'M': p.add(nextVector()); + clearControl(); break; case 'm': p.add(nextVectorInc()); + clearControl(); break; case 'V': y = nextValue(); - p.add(new VectorFloat(x, y)); + p.add(getCurrent()); + clearControl(); break; case 'v': y += nextValue(); - p.add(new VectorFloat(x, y)); + p.add(getCurrent()); + clearControl(); break; case 'H': x = nextValue(); - p.add(new VectorFloat(x, y)); + p.add(getCurrent()); + clearControl(); break; case 'h': x += nextValue(); - p.add(new VectorFloat(x, y)); + p.add(getCurrent()); + clearControl(); break; case 'l': p.add(nextVectorInc()); + clearControl(); break; case 'L': p.add(nextVector()); + clearControl(); break; case 'c': - p.add(nextVectorRel(), nextVectorRel(), nextVectorInc()); + p.add(nextVectorRel(), setLastC3(nextVectorRel()), nextVectorInc()); break; case 'C': - p.add(nextVector(), nextVector(), nextVector()); + p.add(nextVector(), setLastC3(nextVector()), nextVector()); + break; + case 'q': + addQuadratic(p, getCurrent(), setLastC2(nextVectorRel()), nextVectorInc()); + break; + case 'Q': + addQuadratic(p, getCurrent(), setLastC2(nextVector()), nextVector()); + break; + case 's': + addCubicWithReflect(p, getCurrent(), setLastC3(nextVectorRel()), nextVectorInc()); + break; + case 'S': + addCubicWithReflect(p, getCurrent(), setLastC3(nextVector()), nextVector()); + break; + case 't': + addQuadraticWithReflect(p, getCurrent(), nextVectorInc()); + break; + case 'T': + addQuadraticWithReflect(p, getCurrent(), nextVector()); break; case 'a': addArc(p, nextVectorInc(), nextValue(), nextValue() != 0, nextValue() != 0, nextVectorInc()); + clearControl(); break; case 'A': addArc(p, nextVector(), nextValue(), nextValue() != 0, nextValue() != 0, nextVector()); + clearControl(); break; case 'Z': case 'z': p.setClosed(true); + clearControl(); break; default: throw new ParserException("unsupported path command " + command); @@ -175,10 +206,58 @@ public class PolygonParser { return p; } + private VectorInterface getCurrent() { + return new VectorFloat(x, y); + } + + private VectorInterface setLastC2(VectorInterface p) { + lastQuadraticControlPoint = p; + lastCubicControlPoint = null; + return p; + } + + private VectorInterface setLastC3(VectorInterface p) { + lastCubicControlPoint = p; + lastQuadraticControlPoint = null; + return p; + } + + private void clearControl() { + lastQuadraticControlPoint = null; + lastCubicControlPoint = null; + } + + private VectorInterface getLastC2() { + if (lastQuadraticControlPoint == null) + return getCurrent(); + return lastQuadraticControlPoint; + } + + private VectorInterface getLastC3() { + if (lastCubicControlPoint == null) + return getCurrent(); + return lastCubicControlPoint; + } + private void addArc(Polygon p, VectorFloat rad, float rot, boolean large, boolean sweep, VectorFloat pos) { p.add(pos); } + private void addQuadratic(Polygon poly, VectorInterface start, VectorInterface c, VectorInterface p) { + c = c.mul(2.0f / 3); + poly.add(start.mul(1f / 3).add(c), p.mul(1f / 3).add(c), p); + } + + private void addQuadraticWithReflect(Polygon poly, VectorInterface start, VectorInterface p) { + VectorInterface c = start.add(start.sub(getLastC2())); + addQuadratic(poly, start, setLastC2(c), p); + } + + private void addCubicWithReflect(Polygon poly, VectorInterface start, VectorInterface c2, VectorInterface p) { + VectorInterface c1 = start.add(start.sub(getLastC3())); + poly.add(c1, c2, p); + } + /** * The parser exception */ diff --git a/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java b/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java index 99b366d7b..7b78cfd60 100644 --- a/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java +++ b/src/main/java/de/neemann/digital/draw/graphics/VectorInterface.java @@ -54,6 +54,14 @@ public interface VectorInterface { */ VectorInterface div(int d); + /** + * Creates a new vector which has the value this*m + * + * @param m m + * @return this*m + */ + VectorFloat mul(float m); + /** * Creates a new vector which has the value this-a *