// Copyright 2001-2004, FreeHEP.
package org.freehep.postscript;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Stack;
import java.util.Vector;
/**
* Path Construction Operators for PostScript Processor
*
* @author Mark Donszelmann
* @version $Id: PathOperator.java 10178 2006-12-08 09:03:07Z duns $
*/
public class PathOperator extends PSOperator {
public static Class[] operators = {
NewPath.class, CurrentPoint.class,
MoveTo.class, RMoveTo.class, LineTo.class, RLineTo.class,
Arc.class, ArcN.class, ArcT.class, ArcTo.class,
CurveTo.class, RCurveTo.class,
ClosePath.class, FlattenPath.class, ReversePath.class,
StrokePath.class, UStrokePath.class, CharPath.class,
UAppend.class, ClipPath.class, SetBBox.class, PathBBox.class,
PathForAll.class, UPath.class,
InitClip.class, Clip.class, EOClip.class, RectClip.class,
UCache.class,
// type 1 font
HLineTo.class, VLineTo.class, HMoveTo.class, VMoveTo.class,
RRCurveTo.class, VHCurveTo.class, HVCurveTo.class,
};
public boolean execute(OperandStack os) {
throw new RuntimeException("Cannot execute class: "+getClass());
}
}
class NewPath extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().newPath();
return true;
}
}
class CurrentPoint extends PathOperator {
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
os.push(point.getX());
os.push(point.getY());
}
return true;
}
}
class MoveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
PSNumber y = os.popNumber();
PSNumber x = os.popNumber();
os.gstate().path().moveTo(x.getFloat(), y.getFloat());
return true;
}
}
class RMoveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dy = os.popNumber();
PSNumber dx = os.popNumber();
os.gstate().path().moveTo((float)point.getX()+dx.getFloat(),
(float)point.getY()+dy.getFloat());
}
return true;
}
}
class LineTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber y = os.popNumber();
PSNumber x = os.popNumber();
os.gstate().path().lineTo(x.getFloat(), y.getFloat());
}
return true;
}
}
class RLineTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dy = os.popNumber();
PSNumber dx = os.popNumber();
os.gstate().path().lineTo((float)point.getX()+dx.getFloat(),
(float)point.getY()+dy.getFloat());
}
return true;
}
}
class Arc extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
double a2 = os.popNumber().getDouble();
double a1 = os.popNumber().getDouble();
double r = os.popNumber().getDouble();
double y = os.popNumber().getDouble();
double x = os.popNumber().getDouble();
while (a2 < a1) {
a2 += 360;
}
Arc2D arc = new Arc2D.Double();
arc.setArcByCenter(x, y, r, -a1, -(a2-a1), Arc2D.OPEN);
os.gstate().path().append(arc, true);
return true;
}
}
class ArcN extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
double a2 = os.popNumber().getDouble();
double a1 = os.popNumber().getDouble();
double r = os.popNumber().getDouble();
double y = os.popNumber().getDouble();
double x = os.popNumber().getDouble();
while (a2 > a1) {
a2 -= 360;
}
Arc2D arc = new Arc2D.Double();
arc.setArcByCenter(x, y, r, -a1, -(a2-a1), Arc2D.OPEN);
os.gstate().path().append(arc, true);
return true;
}
}
class ArcT extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
double r = os.popNumber().getDouble();
double y2 = os.popNumber().getDouble();
double x2 = os.popNumber().getDouble();
double y1 = os.popNumber().getDouble();
double x1 = os.popNumber().getDouble();
Arc2D arc = new Arc2D.Double(Arc2D.OPEN);
arc.setArcByTangent(p0, new Point2D.Double(x1,y1),
new Point2D.Double(x2,y2), r);
os.gstate().path().append(arc, true);
}
return true;
}
}
class ArcTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
double r = os.popNumber().getDouble();
double y2 = os.popNumber().getDouble();
double x2 = os.popNumber().getDouble();
double y1 = os.popNumber().getDouble();
double x1 = os.popNumber().getDouble();
Arc2D arc = new Arc2D.Double(Arc2D.OPEN);
arc.setArcByTangent(p0, new Point2D.Double(x1,y1),
new Point2D.Double(x2,y2), r);
os.gstate().path().append(arc, true);
Point2D p1t = arc.getStartPoint();
os.push(p1t.getX());
os.push(p1t.getY());
Point2D p2t = arc.getEndPoint();
os.push(p2t.getX());
os.push(p2t.getY());
}
return true;
}
}
class CurveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
float y3 = os.popNumber().getFloat();
float x3 = os.popNumber().getFloat();
float y2 = os.popNumber().getFloat();
float x2 = os.popNumber().getFloat();
float y1 = os.popNumber().getFloat();
float x1 = os.popNumber().getFloat();
os.gstate().path().curveTo(x1,y1,x2,y2,x3,y3);
}
return true;
}
}
class RCurveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
float y0 = (float)p0.getY();
float x0 = (float)p0.getX();
float y3 = os.popNumber().getFloat()+y0;
float x3 = os.popNumber().getFloat()+x0;
float y2 = os.popNumber().getFloat()+y0;
float x2 = os.popNumber().getFloat()+x0;
float y1 = os.popNumber().getFloat()+y0;
float x1 = os.popNumber().getFloat()+x0;
os.gstate().path().curveTo(x1,y1,x2,y2,x3,y3);
}
return true;
}
}
class ClosePath extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().path().closePath();
return true;
}
}
class FlattenPath extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().flattenPath();
return true;
}
}
class ReversePath extends PathOperator {
private float coord[] = new float[6];
private Stack stack;
private void createSubPath(GeneralPath reverse) {
while (!stack.empty()) {
String s = (String)stack.pop();
if (s.equals("moveto")) {
float x = ((Float)stack.pop()).floatValue();
float y = ((Float)stack.pop()).floatValue();
reverse.moveTo(x, y);
} else if (s.equals("lineto")) {
float x = ((Float)stack.pop()).floatValue();
float y = ((Float)stack.pop()).floatValue();
reverse.lineTo(x, y);
} else {
float cx1 = ((Float)stack.pop()).floatValue();
float cy1 = ((Float)stack.pop()).floatValue();
float cx2 = ((Float)stack.pop()).floatValue();
float cy2 = ((Float)stack.pop()).floatValue();
float x = ((Float)stack.pop()).floatValue();
float y = ((Float)stack.pop()).floatValue();
reverse.curveTo(cx1, cy1, cx2, cy2, x, y);
}
}
}
public boolean execute(OperandStack os) {
stack = new Stack();
GeneralPath path = (GeneralPath)os.gstate().path().clone();
PathIterator iterator = path.getPathIterator(new AffineTransform());
GeneralPath reverse = os.gstate().newPath();
while (!iterator.isDone()) {
switch (iterator.currentSegment(coord)) {
case PathIterator.SEG_MOVETO:
createSubPath(reverse);
stack.push(new Float(coord[1]));
stack.push(new Float(coord[0]));
break;
case PathIterator.SEG_LINETO:
stack.push("lineto");
stack.push(new Float(coord[1]));
stack.push(new Float(coord[0]));
break;
case PathIterator.SEG_CUBICTO:
stack.push(new Float(coord[1]));
stack.push(new Float(coord[0]));
stack.push(new Float(coord[3]));
stack.push(new Float(coord[2]));
stack.push("curveto");
stack.push(new Float(coord[5]));
stack.push(new Float(coord[4]));
break;
case PathIterator.SEG_CLOSE:
stack.push("moveto");
createSubPath(reverse);
reverse.closePath();
break;
default:
error(os, new RangeCheck());
return true;
}
iterator.next();
}
return true;
}
}
class StrokePath extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().strokePath();
return true;
}
}
class UStrokePath extends PathOperator {
private boolean done;
private AffineTransform matrix;
private UStrokePath(boolean d, AffineTransform m) {
done = d;
matrix = m;
}
public UStrokePath() {
}
public boolean execute(OperandStack os) {
if (!done) {
if (!os.checkType(PSPackedArray.class)) {
error(os, new TypeCheck());
return true;
}
AffineTransform matrix = null;
PSPackedArray proc = os.popPackedArray();
if (proc.size() == 6) {
try {
matrix = new AffineTransform(proc.toDoubles());
if (!os.checkType(PSPackedArray.class)) {
error(os, new TypeCheck());
return true;
}
proc = os.popPackedArray();
} catch (ClassCastException e) {
// no matrix
}
}
// newpath, systemdict, begin
os.gstate().newPath();
os.dictStack().push(os.dictStack().systemDictionary());
PSPackedArray upath = (PSPackedArray)proc.copy();
upath.setExecutable();
os.execStack().pop();
os.execStack().push(new UStrokePath(true, matrix));
os.execStack().push(upath);
return false;
}
// upath was executed, end, strokepath
os.dictStack().pop();
if (matrix != null) {
AffineTransform ctm = os.gstate().getTransform();
os.gstate().transform(matrix);
os.gstate().strokePath();
os.gstate().setTransform(ctm);
} else {
os.gstate().strokePath();
}
// FIXME: where is the gsave???
os.grestore();
return true;
}
}
class CharPath extends PathOperator {
{ operandTypes = new Class[] {PSString.class, PSBoolean.class}; }
public boolean execute(OperandStack os) {
boolean strokePath = os.popBoolean().getValue();
String text = os.popString().getValue();
PSGState gs = os.gstate();
float x = 0.0f;
float y = 0.0f;
Point2D point = gs.position();
if (point != null) {
x = (float)point.getX();
y = (float)point.getY();
}
for (int i=0; i<text.length(); i++) {
int ch = text.charAt(i);
Shape outline = gs.charPath(ch, x, y, strokePath);
// FIXME: not necessary if fonts are all implemented in charPath
if (outline != null) gs.path().append(outline, false);
x += FontOperator.stringWidth(os, ch);
gs.path().moveTo(x, y);
}
return true;
}
}
class UAppend extends PathOperator {
private boolean done;
private UAppend(boolean d) {
done = d;
}
public UAppend() {
}
public boolean execute(OperandStack os) {
if (!done) {
if (!os.checkType(PSPackedArray.class)) {
error(os, new TypeCheck());
return true;
}
PSPackedArray proc = os.popPackedArray();
// systemdict, begin
os.dictStack().push(os.dictStack().systemDictionary());
PSPackedArray upath = (PSPackedArray)proc.copy();
upath.setExecutable();
os.execStack().pop();
os.execStack().push(new UAppend(true));
os.execStack().push(upath);
return false;
}
// upath was executed, end
os.dictStack().pop();
return true;
}
}
class ClipPath extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().clipPath();
return true;
}
}
class SetBBox extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
double ury = ((PSNumber)os.pop()).getDouble();
double urx = ((PSNumber)os.pop()).getDouble();
double lly = ((PSNumber)os.pop()).getDouble();
double llx = ((PSNumber)os.pop()).getDouble();
os.gstate().setBoundingBox(new Rectangle2D.Double(llx, lly, urx - llx, ury - lly));
return true;
}
}
class PathBBox extends PathOperator {
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
Rectangle2D bb = (os.gstate().boundingBox() != null) ? os.gstate().boundingBox() : os.gstate().path().getBounds2D();
os.push(bb.getMinX());
os.push(bb.getMinY());
os.push(bb.getMaxX());
os.push(bb.getMaxY());
}
return true;
}
}
class PathForAll extends PathOperator implements LoopingContext {
private Shape path = null;
private PSPackedArray moveProc;
private PSPackedArray lineProc;
private PSPackedArray curveProc;
private PSPackedArray closeProc;
private PathIterator iterator;
private double[] coord;
private PathForAll(Shape shape, PSPackedArray move, PSPackedArray line, PSPackedArray curve, PSPackedArray close) {
path = shape;
moveProc = move;
lineProc = line;
curveProc = curve;
closeProc = close;
iterator = path.getPathIterator(new AffineTransform());
coord = new double[6];
}
public PathForAll() {
}
public boolean execute(OperandStack os) {
if (path == null) {
if (!os.checkType(PSPackedArray.class, PSPackedArray.class,
PSPackedArray.class, PSPackedArray.class)) {
error(os, new TypeCheck());
return true;
}
PSPackedArray close = os.popPackedArray();
PSPackedArray curve = os.popPackedArray();
PSPackedArray line = os.popPackedArray();
PSPackedArray move = os.popPackedArray();
Shape shape = (Shape)os.gstate().path().clone();
os.execStack().pop();
os.execStack().push(new PathForAll(shape, move, line, curve, close));
return false;
}
if (iterator.isDone()) {
return true;
}
switch (iterator.currentSegment(coord)) {
case PathIterator.SEG_MOVETO:
os.push(coord[0]);
os.push(coord[1]);
os.execStack().push(moveProc);
break;
case PathIterator.SEG_LINETO:
os.push(coord[0]);
os.push(coord[1]);
os.execStack().push(lineProc);
break;
case PathIterator.SEG_CUBICTO:
os.push(coord[0]);
os.push(coord[1]);
os.push(coord[2]);
os.push(coord[3]);
os.push(coord[4]);
os.push(coord[5]);
os.execStack().push(curveProc);
break;
case PathIterator.SEG_CLOSE:
os.execStack().push(closeProc);
break;
default:
error(os, new RangeCheck());
return true;
}
iterator.next();
return false;
}
}
class UPath extends PathOperator {
{ operandTypes = new Class[] {PSBoolean.class}; }
private double coord[] = new double[6];
public boolean execute(OperandStack os) {
boolean ucache = os.popBoolean().getValue();
AffineTransform inverse;
try {
inverse = os.gstate().getTransform().createInverse();
} catch (NoninvertibleTransformException e) {
error(os, new Undefined());
return true;
}
PathIterator iterator = os.gstate().path().getPathIterator(inverse);
Vector path = new Vector();
if (ucache) path.add(new PSName("ucache", true));
Rectangle2D bb = os.gstate().path().getBounds2D();
path.add(new PSReal(bb.getMinX()));
path.add(new PSReal(bb.getMinY()));
path.add(new PSReal(bb.getMaxX()));
path.add(new PSReal(bb.getMaxY()));
path.add(new PSName("setbbox", true));
while (!iterator.isDone()) {
switch (iterator.currentSegment(coord)) {
case PathIterator.SEG_MOVETO:
path.add(new PSReal(coord[0]));
path.add(new PSReal(coord[1]));
path.add(new PSName("moveto", true));
break;
case PathIterator.SEG_LINETO:
path.add(new PSReal(coord[0]));
path.add(new PSReal(coord[1]));
path.add(new PSName("lineto", true));
break;
case PathIterator.SEG_CUBICTO:
path.add(new PSReal(coord[0]));
path.add(new PSReal(coord[1]));
path.add(new PSReal(coord[2]));
path.add(new PSReal(coord[3]));
path.add(new PSReal(coord[4]));
path.add(new PSReal(coord[5]));
path.add(new PSName("curveto", true));
break;
case PathIterator.SEG_CLOSE:
path.add(new PSName("closepath", true));
break;
default:
error(os, new RangeCheck());
return true;
}
iterator.next();
}
PSObject[] obj = new PSObject[path.size()];
path.copyInto(obj);
PSPackedArray upath = new PSPackedArray(obj);
upath.setExecutable();
os.push(upath);
return true;
}
}
class InitClip extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().initClip();
return true;
}
}
class Clip extends PathOperator {
public boolean execute(OperandStack os) {
os.gstate().clip(os.gstate().path());
return true;
}
}
class EOClip extends PathOperator {
public boolean execute(OperandStack os) {
GeneralPath eoPath = new GeneralPath(os.gstate().path());
eoPath.setWindingRule(GeneralPath.WIND_EVEN_ODD);
os.gstate().clip(eoPath);
return true;
}
}
class RectClip extends PathOperator {
{ operandTypes = new Class[] {PSObject.class}; }
public boolean execute(OperandStack os) {
if (os.checkType(PSNumber.class, PSNumber.class, PSNumber.class, PSNumber.class)) {
double h = os.popNumber().getDouble();
double w = os.popNumber().getDouble();
double y = os.popNumber().getDouble();
double x = os.popNumber().getDouble();
Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
os.gstate().clip(r);
os.gstate().newPath();
} else if (os.checkType(PSPackedArray.class)) {
PSPackedArray a = os.popPackedArray();
GeneralPath p = new GeneralPath();
for (int i=0; i<a.size()/4; i++) {
double x = ((PSNumber)a.get(i*4)).getDouble();
double y = ((PSNumber)a.get(i*4+1)).getDouble();
double w = ((PSNumber)a.get(i*4+2)).getDouble();
double h = ((PSNumber)a.get(i*4+3)).getDouble();
Rectangle2D r = new Rectangle2D.Double(x, y, w, h);
p.append(r, false);
}
os.gstate().clip(p);
os.gstate().newPath();
} else {
// FIXME: encoded number string not handled
error(os, new TypeCheck());
}
return true;
}
}
class UCache extends PathOperator {
public boolean execute(OperandStack os) {
// FIXME: ignored
return true;
}
}
// --------------------------------------------------------------------------------
// Type 1 Font Operators
// --------------------------------------------------------------------------------
class HLineTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dx = os.popNumber();
os.gstate().path().lineTo((float)point.getX()+dx.getFloat(),
(float)point.getY());
}
return true;
}
}
class VLineTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dy = os.popNumber();
os.gstate().path().lineTo((float)point.getX(),
(float)point.getY()+dy.getFloat());
}
return true;
}
}
class HMoveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dx = os.popNumber();
System.out.println(point + " + " + dx.getFloat());
os.gstate().path().moveTo((float)point.getX()+dx.getFloat(),
(float)point.getY());
}
return true;
}
}
class VMoveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D point = os.gstate().position();
if (point == null) {
error(os, new NoCurrentPoint());
} else {
PSNumber dy = os.popNumber();
os.gstate().path().moveTo((float)point.getX(),
(float)point.getY()+dy.getFloat());
}
return true;
}
}
class RRCurveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
float y0 = (float)p0.getY();
float x0 = (float)p0.getX();
float dy3 = os.popNumber().getFloat();
float dx3 = os.popNumber().getFloat();
float dy2 = os.popNumber().getFloat();
float dx2 = os.popNumber().getFloat();
float dy1 = os.popNumber().getFloat();
float dx1 = os.popNumber().getFloat();
os.gstate().path().curveTo(x0+dx1, y0+dy1,
x0+dx1+dx2, y0+dy1+dy2,
x0+dx1+dx2+dx3, y0+dy1+dy2+dy3);
}
return true;
}
}
class HVCurveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
float y0 = (float)p0.getY();
float x0 = (float)p0.getX();
float dy3 = os.popNumber().getFloat();
float dx3 = 0;
float dy2 = os.popNumber().getFloat();
float dx2 = os.popNumber().getFloat();
float dy1 = 0;
float dx1 = os.popNumber().getFloat();
os.gstate().path().curveTo(x0+dx1, y0+dy1,
x0+dx1+dx2, y0+dy1+dy2,
x0+dx1+dx2+dx3, y0+dy1+dy2+dy3);
}
return true;
}
}
class VHCurveTo extends PathOperator {
{ operandTypes = new Class[] {PSNumber.class, PSNumber.class,
PSNumber.class, PSNumber.class}; }
public boolean execute(OperandStack os) {
Point2D p0 = os.gstate().position();
if (p0 == null) {
error(os, new NoCurrentPoint());
} else {
float y0 = (float)p0.getY();
float x0 = (float)p0.getX();
float dy3 = 0;
float dx3 = os.popNumber().getFloat();
float dy2 = os.popNumber().getFloat();
float dx2 = os.popNumber().getFloat();
float dy1 = os.popNumber().getFloat();
float dx1 = 0;
os.gstate().path().curveTo(x0+dx1, y0+dy1,
x0+dx1+dx2, y0+dy1+dy2,
x0+dx1+dx2+dx3, y0+dy1+dy2+dy3);
}
return true;
}
}