Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.canvas;
027
028import com.sun.javafx.geom.Arc2D;
029import com.sun.javafx.geom.Path2D;
030import com.sun.javafx.geom.PathIterator;
031import com.sun.javafx.geom.transform.Affine2D;
032import com.sun.javafx.geom.transform.NoninvertibleTransformException;
033import com.sun.javafx.image.BytePixelGetter;
034import com.sun.javafx.image.BytePixelSetter;
035import com.sun.javafx.image.ByteToBytePixelConverter;
036import com.sun.javafx.image.IntPixelGetter;
037import com.sun.javafx.image.IntToBytePixelConverter;
038import com.sun.javafx.image.PixelConverter;
039import com.sun.javafx.image.PixelGetter;
040import com.sun.javafx.image.PixelUtils;
041import com.sun.javafx.image.impl.ByteBgraPre;
042import com.sun.javafx.sg.GrowableDataBuffer;
043import com.sun.javafx.sg.PGCanvas;
044import com.sun.javafx.tk.Toolkit;
045import java.nio.Buffer;
046import java.nio.ByteBuffer;
047import java.nio.IntBuffer;
048import java.util.LinkedList;
049import javafx.geometry.VPos;
050import javafx.scene.effect.Blend;
051import javafx.scene.effect.BlendMode;
052import javafx.scene.effect.Effect;
053import javafx.scene.image.Image;
054import javafx.scene.image.PixelFormat;
055import javafx.scene.image.PixelReader;
056import javafx.scene.image.PixelWriter;
057import javafx.scene.paint.Color;
058import javafx.scene.paint.Paint;
059import javafx.scene.shape.ArcType;
060import javafx.scene.shape.FillRule;
061import javafx.scene.shape.StrokeLineCap;
062import javafx.scene.shape.StrokeLineJoin;
063import javafx.scene.text.Font;
064import javafx.scene.text.TextAlignment;
065import javafx.scene.transform.Affine;
066
067/**
068 * This class is used to issue draw calls to a {@code Canvas} using a buffer. 
069 * <p>
070 * Each call pushes the necessary parameters onto the buffer
071 * where it is executed on the image of the {@code Canvas} node.
072 * <p>
073 * A {@code Canvas} only contains one {@code GraphicsContext}, and only one buffer.
074 * If it is not attached to any scene, then it can be modified by any thread,
075 * as long as it is only used from one thread at a time. Once a {@code Canvas} 
076 * node is attached to a scene, it must be modified on the JavaFX Application 
077 * Thread.
078 * <p>
079 * Calling any method on the {@code GraphicsContext} is considered modifying
080 * its corresponding {@code Canvas} and is subject to the same threading
081 * rules. 
082 * <p>
083 * A {@code GraphicsContext} also manages a stack of state objects that can
084 * be saved or restored at anytime. 
085 *
086 * <p>Example:</p>
087 *
088 * <p>
089 * <pre>
090import javafx.scene.*;
091import javafx.scene.paint.*;
092import javafx.scene.canvas.*;
093
094Group root = new Group();
095Scene s = new Scene(root, 300, 300, Color.BLACK);
096
097final Canvas canvas = new Canvas(250,250);
098GraphicsContext gc = canvas.getGraphicsContext2D();
099 
100gc.setFill(Color.BLUE);
101gc.fillRect(75,75,100,100);
102 
103root.getChildren().add(canvas);
104 * </pre>
105 * </p>
106 *
107 * @since 2.2
108 */
109public final class GraphicsContext {
110    Canvas theCanvas;
111    Path2D path;
112    boolean pathDirty;
113
114    State curState;
115    LinkedList<State> stateStack;
116    LinkedList<Path2D> clipStack;
117  
118    GraphicsContext(Canvas theCanvas) {
119        this.theCanvas = theCanvas;
120        this.path = new Path2D();
121        pathDirty = true;
122
123        this.curState = new State();
124        this.stateStack = new LinkedList<State>();
125        this.clipStack = new LinkedList<Path2D>();
126    }
127
128    static class State {
129        double globalAlpha;
130        BlendMode blendop;
131        Affine2D transform;
132        Paint fill;
133        Paint stroke;
134        double linewidth;
135        StrokeLineCap linecap;
136        StrokeLineJoin linejoin;
137        double miterlimit;
138        int numClipPaths;
139        Font font;
140        TextAlignment textalign;
141        VPos textbaseline;
142        Effect effect;
143        FillRule fillRule;
144
145        State() {
146            this(1.0, BlendMode.SRC_OVER,
147                 new Affine2D(),
148                 Color.BLACK, Color.BLACK,
149                 1.0, StrokeLineCap.SQUARE, StrokeLineJoin.MITER, 10.0,
150                 0, Font.getDefault(), TextAlignment.LEFT, VPos.BASELINE,
151                 null, FillRule.NON_ZERO);
152        }
153
154        State(State copy) {
155            this(copy.globalAlpha, copy.blendop,
156                 new Affine2D(copy.transform),
157                 copy.fill, copy.stroke,
158                 copy.linewidth, copy.linecap, copy.linejoin, copy.miterlimit,
159                 copy.numClipPaths,
160                 copy.font, copy.textalign, copy.textbaseline,
161                 copy.effect, copy.fillRule);
162        }
163
164        State(double globalAlpha, BlendMode blendop,
165                     Affine2D transform, Paint fill, Paint stroke,
166                     double linewidth, StrokeLineCap linecap,
167                     StrokeLineJoin linejoin, double miterlimit,
168                     int numClipPaths,
169                     Font font, TextAlignment align, VPos baseline,
170                     Effect effect, FillRule fillRule)
171        {
172            this.globalAlpha = globalAlpha;
173            this.blendop = blendop;
174            this.transform = transform;
175            this.fill = fill;
176            this.stroke = stroke;
177            this.linewidth = linewidth;
178            this.linecap = linecap;
179            this.linejoin = linejoin;
180            this.miterlimit = miterlimit;
181            this.numClipPaths = numClipPaths;
182            this.font = font;
183            this.textalign = align;
184            this.textbaseline = baseline;
185            this.effect = effect;
186            this.fillRule = fillRule;
187        }
188
189        State copy() {
190            return new State(this);
191        }
192
193        void restore(GraphicsContext ctx) {
194            ctx.setGlobalAlpha(globalAlpha);
195            ctx.setGlobalBlendMode(blendop);
196            ctx.setTransform(transform.getMxx(), transform.getMyx(),
197                             transform.getMxy(), transform.getMyy(),
198                             transform.getMxt(), transform.getMyt());
199            ctx.setFill(fill);
200            ctx.setStroke(stroke);
201            ctx.setLineWidth(linewidth);
202            ctx.setLineCap(linecap);
203            ctx.setLineJoin(linejoin);
204            ctx.setMiterLimit(miterlimit);
205            GrowableDataBuffer buf = ctx.getBuffer();
206            while (ctx.curState.numClipPaths > numClipPaths) {
207                ctx.curState.numClipPaths--;
208                ctx.clipStack.removeLast();
209                buf.putByte(PGCanvas.POP_CLIP);
210            }
211            ctx.setFillRule(fillRule);
212            ctx.setFont(font);
213            ctx.setTextAlign(textalign);
214            ctx.setTextBaseline(textbaseline);
215            ctx.setEffect(effect);
216        }
217    }
218
219    private GrowableDataBuffer getBuffer() {
220        return theCanvas.getBuffer();
221    }
222
223    private static float coords[] = new float[6];
224    private static final byte pgtype[] = {
225        PGCanvas.MOVETO,
226        PGCanvas.LINETO,
227        PGCanvas.QUADTO,
228        PGCanvas.CUBICTO,
229        PGCanvas.CLOSEPATH,
230    };
231    private static final int numsegs[] = { 2, 2, 4, 6, 0, };
232
233    private void markPathDirty() {
234        pathDirty = true;
235    }
236
237    private void writePath(byte command) {
238        updateTransform();
239        GrowableDataBuffer buf = getBuffer();
240        if (pathDirty) {
241            buf.putByte(PGCanvas.PATHSTART);
242            PathIterator pi = path.getPathIterator(null);
243            while (!pi.isDone()) {
244                int pitype = pi.currentSegment(coords);
245                buf.putByte(pgtype[pitype]);
246                for (int i = 0; i < numsegs[pitype]; i++) {
247                    buf.putFloat(coords[i]);
248                }
249                pi.next();
250            }
251            buf.putByte(PGCanvas.PATHEND);
252            pathDirty = false;
253        }
254        buf.putByte(command);
255    }
256
257    private void writePaint(Paint p, byte command) {
258        GrowableDataBuffer buf = getBuffer();
259        buf.putByte(command);
260        buf.putObject(Toolkit.getPaintAccessor().getPlatformPaint(p));
261    }
262
263    private void writeArcType(ArcType closure) {
264        byte type;
265        switch (closure) {
266            case OPEN:  type = PGCanvas.ARC_OPEN;  break;
267            case CHORD: type = PGCanvas.ARC_CHORD; break;
268            case ROUND: type = PGCanvas.ARC_PIE;   break;
269            default: return;  // ignored for consistency with other attributes
270        }
271        writeParam(type, PGCanvas.ARC_TYPE);
272    }
273
274    private void writeRectParams(GrowableDataBuffer buf,
275                                 double x, double y, double w, double h,
276                                 byte command)
277    {
278        buf.putByte(command);
279        buf.putFloat((float) x);
280        buf.putFloat((float) y);
281        buf.putFloat((float) w);
282        buf.putFloat((float) h);
283    }
284
285    private void writeOp4(double x, double y, double w, double h, byte command) {
286        updateTransform();
287        writeRectParams(getBuffer(), x, y, w, h, command);
288    }
289
290    private void writeOp6(double x, double y, double w, double h,
291                          double v1, double v2, byte command)
292    {
293        updateTransform();
294        GrowableDataBuffer buf = getBuffer();
295        buf.putByte(command);
296        buf.putFloat((float) x);
297        buf.putFloat((float) y);
298        buf.putFloat((float) w);
299        buf.putFloat((float) h);
300        buf.putFloat((float) v1);
301        buf.putFloat((float) v2);
302    }
303
304    private float polybuf[] = new float[512];
305    private void flushPolyBuf(GrowableDataBuffer buf,
306                              float polybuf[], int n, byte command)
307    {
308        curState.transform.deltaTransform(polybuf, 0, polybuf, 0, n/2);
309        for (int i = 0; i < n; i += 2) {
310            buf.putByte(command);
311            buf.putFloat(polybuf[i]);
312            buf.putFloat(polybuf[i+1]);
313            command = PGCanvas.LINETO;
314        }
315    }
316    private void writePoly(double xPoints[], double yPoints[], int nPoints,
317                           boolean close, byte command)
318    {
319        GrowableDataBuffer buf = getBuffer();
320        buf.putByte(PGCanvas.PATHSTART);
321        int pos = 0;
322        byte polycmd = PGCanvas.MOVETO;
323        for (int i = 0; i < nPoints; i++) {
324            if (pos >= polybuf.length) {
325                flushPolyBuf(buf, polybuf, pos, polycmd);
326                polycmd = PGCanvas.LINETO;
327            }
328            polybuf[pos++] = (float) xPoints[i];
329            polybuf[pos++] = (float) yPoints[i];
330        }
331        flushPolyBuf(buf, polybuf, pos, polycmd);
332        if (close) {
333            buf.putByte(PGCanvas.CLOSEPATH);
334        }
335        buf.putByte(PGCanvas.PATHEND);
336        buf.putByte(command);
337        // Now that we have changed the PG layer path, we need to mark our path dirty.
338        markPathDirty();
339    }
340
341    private void writeImage(Image img,
342                            double dx, double dy, double dw, double dh)
343    {
344        if (img.getProgress() < 1.0) return;
345        Object platformImg = img.impl_getPlatformImage();
346        if (platformImg == null) return;
347        updateTransform();
348        GrowableDataBuffer buf = getBuffer();
349        writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_IMAGE);
350        buf.putObject(platformImg);
351    }
352
353    private void writeImage(Image img,
354                            double dx, double dy, double dw, double dh,
355                            double sx, double sy, double sw, double sh)
356    {
357        if (img.getProgress() < 1.0) return;
358        Object platformImg = img.impl_getPlatformImage();
359        if (platformImg == null) return;
360        updateTransform();
361        GrowableDataBuffer buf = getBuffer();
362        writeRectParams(buf, dx, dy, dw, dh, PGCanvas.DRAW_SUBIMAGE);
363        buf.putFloat((float) sx);
364        buf.putFloat((float) sy);
365        buf.putFloat((float) sw);
366        buf.putFloat((float) sh);
367        buf.putObject(platformImg);
368    }
369
370    private void writeText(String text, double x, double y, double maxWidth,
371                           byte command)
372    {
373        updateTransform();
374        GrowableDataBuffer buf = getBuffer();
375        buf.putByte(command);
376        buf.putFloat((float) x);
377        buf.putFloat((float) y);
378        buf.putFloat((float) maxWidth);
379        buf.putObject(text);
380    }
381
382    private void writeParam(double v, byte command) {
383        GrowableDataBuffer buf = getBuffer();
384        buf.putByte(command);
385        buf.putFloat((float) v);
386    }
387
388    private void writeParam(byte v, byte command) {
389        GrowableDataBuffer buf = getBuffer();
390        buf.putByte(command);
391        buf.putByte(v);
392    }
393
394    private boolean txdirty;
395    private void updateTransform() {
396        if (txdirty) {
397            txdirty = false;
398            GrowableDataBuffer buf = getBuffer();
399            buf.putByte(PGCanvas.TRANSFORM);
400            buf.putDouble(curState.transform.getMxx());
401            buf.putDouble(curState.transform.getMxy());
402            buf.putDouble(curState.transform.getMxt());
403            buf.putDouble(curState.transform.getMyx());
404            buf.putDouble(curState.transform.getMyy());
405            buf.putDouble(curState.transform.getMyt());
406        }
407    }
408    
409   /**
410    * Gets the {@code Canvas} that the {@code GraphicsContext} is issuing draw
411    * commands to. There is only ever one {@code Canvas} for a 
412    * {@code GraphicsContext}.
413    *
414    * @return Canvas the canvas that this {@code GraphicsContext} is issuing draw
415    * commands to.
416    */
417    public Canvas getCanvas() {
418        return theCanvas;
419    } 
420
421    /**
422     * Saves the following attributes onto a stack.
423     * <ul>
424     *     <li>Global Alpha</li>
425     *     <li>Global Blend Operation</li>
426     *     <li>Transform</li>
427     *     <li>Fill Paint</li>
428     *     <li>Stroke Paint</li>
429     *     <li>Line Width</li>
430     *     <li>Line Cap</li>
431     *     <li>Line Join</li>
432     *     <li>Miter Limit</li>
433     *     <li>Number of Clip Paths</li>
434     *     <li>Font</li>
435     *     <li>Text Align</li>
436     *     <li>Text Baseline</li>
437     *     <li>Effect</li>
438     *     <li>Fill Rule</li>
439     * </ul>
440     * This method does NOT alter the current state in any way. Also, not that
441     * the current path is not saved.
442     */
443    public void save() {
444        stateStack.push(curState.copy());
445    }
446
447    /**
448     * Pops the state off of the stack, setting the following attributes to their 
449     * value at the time when that state was pushed onto the stack. If the stack
450     * is empty then nothing is changed.
451     * 
452     * <ul>
453     *     <li>Global Alpha</li>
454     *     <li>Global Blend Operation</li>
455     *     <li>Transform</li>
456     *     <li>Fill Paint</li>
457     *     <li>Stroke Paint</li>
458     *     <li>Line Width</li>
459     *     <li>Line Cap</li>
460     *     <li>Line Join</li>
461     *     <li>Miter Limit</li>
462     *     <li>Number of Clip Paths</li>
463     *     <li>Font</li>
464     *     <li>Text Align</li>
465     *     <li>Text Baseline</li>
466     *     <li>Effect</li>
467     *     <li>Fill Rule</li>
468     * </ul>
469     */
470    public void restore() {
471        if (!stateStack.isEmpty()) {
472            State savedState = stateStack.pop();
473            savedState.restore(this);
474            txdirty = true;
475        }
476    }
477
478    /**
479     * Translates the current transform by x, y.
480     * @param x value to translate along the x axis.
481     * @param y value to translate along the y axis.
482     */
483    public void translate(double x, double y) {
484        curState.transform.translate(x, y);
485        txdirty = true;
486    }
487
488    /**
489     * Scales the current transform by x, y.
490     * @param x value to scale in the x axis.
491     * @param y value to scale in the y axis.
492     */
493    public void scale(double x, double y) {
494        curState.transform.scale(x, y);
495        txdirty = true;
496    }
497
498    /**
499     * Rotates the current transform in degrees.
500     * @param degrees value in degrees to rotate the current transform.
501     */
502    public void rotate(double degrees) {
503        curState.transform.rotate(Math.toRadians(degrees));
504        txdirty = true;
505    }
506
507    private static Affine2D scratchTX = new Affine2D();
508    /**
509     * Concatenates the input with the current transform.
510     * 
511     * @param mxx - the X coordinate scaling element of the 3x4 matrix
512     * @param myx - the Y coordinate shearing element of the 3x4 matrix
513     * @param mxy - the X coordinate shearing element of the 3x4 matrix
514     * @param myy - the Y coordinate scaling element of the 3x4 matrix
515     * @param mxt - the X coordinate translation element of the 3x4 matrix
516     * @param myt - the Y coordinate translation element of the 3x4 matrix
517     */
518    public void transform(double mxx, double myx,
519                          double mxy, double myy,
520                          double mxt, double myt)
521    {
522        scratchTX.setTransform(mxx, myx,
523                               mxy, myy,
524                               mxt, myt);
525        curState.transform.concatenate(scratchTX);
526        txdirty = true;
527    }
528    
529    /**
530     * Concatenates the input with the current transform. Only 2D transforms are
531     * supported. The only values used are the X and Y scaling, translation, and
532     * shearing components of a transform.
533     * 
534     * @param xform The affine to be concatenated with the current transform.
535     */
536    public void transform(Affine xform) {
537        scratchTX.setTransform(xform.getMxx(), xform.getMyx(),
538                               xform.getMxy(), xform.getMyy(),
539                               xform.getTx(), xform.getTy());
540        curState.transform.concatenate(scratchTX);
541        txdirty = true;
542    }
543
544    /**
545     * Sets the current transform.
546     * @param mxx - the X coordinate scaling element of the 3x4 matrix
547     * @param myx - the Y coordinate shearing element of the 3x4 matrix
548     * @param mxy - the X coordinate shearing element of the 3x4 matrix
549     * @param myy - the Y coordinate scaling element of the 3x4 matrix
550     * @param mxt - the X coordinate translation element of the 3x4 matrix
551     * @param myt - the Y coordinate translation element of the 3x4 matrix 
552     */
553    public void setTransform(double mxx, double myx,
554                             double mxy, double myy,
555                             double mxt, double myt)
556    {
557        curState.transform.setTransform(mxx, myx,
558                                        mxy, myy,
559                                        mxt, myt);
560        txdirty = true;
561    }
562    
563    /**
564     * Sets the current transform. Only 2D transforms are supported. The only 
565     * values used are the X and Y scaling, translation, and shearing components
566     * of a transform.
567     * 
568     * @param xform The affine to be copied and used as the current transform.
569     */
570    public void setTransform(Affine xform) {
571        curState.transform.setTransform(xform.getMxx(), xform.getMyx(),
572                                        xform.getMxy(), xform.getMyy(),
573                                        xform.getTx(), xform.getTy());
574        txdirty = true;
575    }
576    
577    /**
578     * Returns a copy of the current transform. 
579     * 
580     * @param xform A transform object that will be used to hold the result.
581     * If xform is non null, then this method will copy the current transform 
582     * into that object. If xform is null a new transform object will be
583     * constructed. In either case, the return value is a copy of the current 
584     * transform. 
585     *
586     * @return A copy of the current transform.  
587     */
588    public Affine getTransform(Affine xform) {
589        if (xform == null) {
590            xform = new Affine();
591        }
592        
593        xform.setMxx(curState.transform.getMxx());
594        xform.setMxy(curState.transform.getMxy());
595        xform.setMxz(0);
596        xform.setTx(curState.transform.getMxt());
597        xform.setMyx(curState.transform.getMyx());
598        xform.setMyy(curState.transform.getMyy());
599        xform.setMyz(0);
600        xform.setTy(curState.transform.getMyt());
601        xform.setMzx(0);
602        xform.setMzy(0);
603        xform.setMzz(1);
604        xform.setTz(0);
605        
606        return xform;
607    }
608    
609    /**
610     * Returns a copy of the current transform.
611     * 
612     * @return a copy of the transform of the current state. 
613     */
614    public Affine getTransform() {
615        return getTransform(null);
616    }
617
618    /**
619     * Sets the global alpha of the current state.
620     * 
621     * @param alpha value in the range {@code 0.0-1.0}. The value is clamped if it is 
622     * out of range.
623     */
624    public void setGlobalAlpha(double alpha) {
625        if (curState.globalAlpha != alpha) {
626            curState.globalAlpha = alpha;
627            alpha = (alpha > 1.0) ? 1.0 : (alpha < 0.0) ? 0.0 : alpha;
628            writeParam(alpha, PGCanvas.GLOBAL_ALPHA);
629        }
630    }
631    
632    /**
633     * Gets the current global alpha.
634     * 
635     * @return the current global alpha. 
636     */
637    public double getGlobalAlpha() {
638        return curState.globalAlpha;
639    }
640    
641    private static Blend TMP_BLEND = new Blend(BlendMode.SRC_OVER); 
642    /**
643     * Sets the global blend mode.
644     * 
645     * @param op the {@code BlendMode} that will be set.
646     */
647    public void setGlobalBlendMode(BlendMode op) {
648        if (op != null && op != curState.blendop) {
649            GrowableDataBuffer buf = getBuffer();           
650            curState.blendop = op; 
651            TMP_BLEND.setMode(op);
652            TMP_BLEND.impl_sync();
653            buf.putByte(PGCanvas.COMP_MODE);
654            buf.putObject(((com.sun.scenario.effect.Blend)TMP_BLEND.impl_getImpl()).getMode());
655        }
656    }
657    
658    /**
659     * Gets the global blend mode.
660     * 
661     * @return the global {@code BlendMode} of the current state.
662     */
663    public BlendMode getGlobalBlendMode() {
664        return curState.blendop;
665    }
666
667    /**
668     * Sets the current fill attribute. This method affects the paint used for any
669     * method with "fill" in it. For Example, fillRect(...), fillOval(...).
670     * 
671     * @param p The {@code Paint} to be used as the fill {@code Paint}.
672     */
673    public void setFill(Paint p) {
674        if (curState.fill != p) {
675            curState.fill = p;
676            writePaint(p, PGCanvas.FILL_PAINT);
677        }
678    }
679    
680    /**
681     * Gets the current fill attribute. This method affects the paint used for any
682     * method with "fill" in it. For Example, fillRect(...), fillOval(...).
683     * 
684     * @return p The {@code Paint} to be used as the fill {@code Paint}.
685     */
686    public Paint getFill() {
687        return curState.fill;
688    }
689
690    /**
691     * Sets the current stroke.
692     * 
693     * @param p The Paint to be used as the stroke Paint.
694     */
695    public void setStroke(Paint p) {
696        if (curState.stroke != p) {
697            curState.stroke = p;
698            writePaint(p, PGCanvas.STROKE_PAINT);
699        }
700    }
701    
702    /**
703     * Gets the current stroke.
704     * 
705     * @return the {@code Paint} to be used as the stroke {@code Paint}.
706     */
707    public Paint getStroke() {
708        return curState.stroke;
709    }
710
711    /**
712     * Sets the current line width.
713     * 
714     * @param lw value in the range {0-positive infinity}, with any other value 
715     * being ignored and leaving the value unchanged.
716     */
717    public void setLineWidth(double lw) {
718        // Per W3C spec: On setting, zero, negative, infinite, and NaN
719        // values must be ignored, leaving the value unchanged
720        if (lw > 0 && lw < Double.POSITIVE_INFINITY) {
721            if (curState.linewidth != lw) {
722                curState.linewidth = lw;
723                writeParam(lw, PGCanvas.LINE_WIDTH);
724            }
725        }
726    }
727    
728    /**
729     * Gets the current line width.
730     * 
731     * @return value between 0 and infinity.
732     */
733    public double getLineWidth() {
734        return curState.linewidth;
735    }
736
737    /**
738     * Sets the current stroke line cap. 
739     * 
740     * @param cap {@code StrokeLineCap} with a value of Butt, Round, or Square.
741     */
742    public void setLineCap(StrokeLineCap cap) {
743        if (curState.linecap != cap) {
744            byte v;
745            switch (cap) {
746                case BUTT: v = PGCanvas.CAP_BUTT; break;
747                case ROUND: v = PGCanvas.CAP_ROUND; break;
748                case SQUARE: v = PGCanvas.CAP_SQUARE; break;
749                default: return;
750            }
751            curState.linecap = cap;
752            writeParam(v, PGCanvas.LINE_CAP);
753        }
754    }
755    
756    /**
757     * Gets the current stroke line cap. 
758     * 
759     * @return {@code StrokeLineCap} with a value of Butt, Round, or Square.
760     */
761    public StrokeLineCap getLineCap() {
762        return curState.linecap;
763    }
764
765    /**
766     * Sets the current stroke line join.
767     * 
768     * @param join {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
769     */
770    public void setLineJoin(StrokeLineJoin join) {
771        if (curState.linejoin != join) {
772            byte v;
773            switch (join) {
774                case MITER: v = PGCanvas.JOIN_MITER; break;
775                case BEVEL: v = PGCanvas.JOIN_BEVEL; break;
776                case ROUND: v = PGCanvas.JOIN_ROUND; break;
777                default: return;
778            }
779            curState.linejoin = join;
780            writeParam(v, PGCanvas.LINE_JOIN);
781        }
782    }
783    
784    /**
785     * Gets the current stroke line join.
786     * 
787     * @return {@code StrokeLineJoin} with a value of Miter, Bevel, or Round.
788     */
789    public StrokeLineJoin getLineJoin() {
790        return curState.linejoin;
791    }
792
793    /**
794     * Sets the current miter limit.
795     * 
796     * @param ml miter limit value between 0 and positive infinity with 
797     * any other value being ignored and leaving the value unchanged.
798     */
799    public void setMiterLimit(double ml) {
800        // Per W3C spec: On setting, zero, negative, infinite, and NaN
801        // values must be ignored, leaving the value unchanged
802        if (ml > 0.0 && ml < Double.POSITIVE_INFINITY) {
803            if (curState.miterlimit != ml) {
804                curState.miterlimit = ml;
805                writeParam(ml, PGCanvas.MITER_LIMIT);
806            }
807        }
808    }
809    
810    /**
811     * Gets the current miter limit.
812     * 
813     * @return the miter limit value in the range {@code 0.0-positive infinity}
814     */
815    public double getMiterLimit() {
816        return curState.miterlimit;
817    }
818
819    /**
820     * Sets the current Font.
821     * 
822     * @param f the Font 
823     */
824    public void setFont(Font f) {
825        if (curState.font != f) {
826            curState.font = f;
827            GrowableDataBuffer buf = getBuffer();
828            buf.putByte(PGCanvas.FONT);
829            buf.putObject(f.impl_getNativeFont());
830        }
831    }
832    
833    /**
834     * Gets the current Font.
835     * 
836     * @return the Font 
837     */
838    public Font getFont() {
839        return curState.font;
840    }
841
842    /**
843     * Defines horizontal text alignment, relative to the text {@code x} origin.
844     * <p>
845     * Let horizontal bounds represent the logical width of a single line of
846     * text. Where each line of text has a separate horizontal bounds.
847     * <p>
848     * Then TextAlignment is specified as:
849     * <ul>
850     * <li>Left: the left edge of the horizontal bounds will be at {@code x}.
851     * <li>Center: the center, halfway between left and right edge, of the 
852     * horizontal bounds will be at {@code x}.
853     * <li>Right: the right edge of the horizontal bounds will be at {@code x}.
854     * </ul>
855     * <p>
856     *
857     * Note: Canvas does not support line wrapping, therefore the text
858     * alignment Justify is identical to left aligned text.
859     * <p>
860     * 
861     * @param align {@code TextAlignment} with values of Left, Center, Right.
862     */
863    public void setTextAlign(TextAlignment align) {
864        if (curState.textalign != align) {
865            byte a;
866            switch (align) {
867                case LEFT: a = PGCanvas.ALIGN_LEFT; break;
868                case CENTER: a = PGCanvas.ALIGN_CENTER; break;
869                case RIGHT: a = PGCanvas.ALIGN_RIGHT; break;
870                case JUSTIFY: a = PGCanvas.ALIGN_JUSTIFY; break;
871                default: return;
872            }
873            curState.textalign = align;
874            writeParam(a, PGCanvas.TEXT_ALIGN);
875        }
876    }
877    
878    /**
879     * Gets the current {@code TextAlignment}.
880     * 
881     * @return {@code TextAlignment} with values of Left, Center, Right, or
882     * Justify.
883     */
884    public TextAlignment getTextAlign() {
885        return curState.textalign;
886    }
887
888    /**
889     * Sets the current Text Baseline.
890     * 
891     * @param baseline {@code VPos} with values of Top, Center, Baseline, or Bottom
892     */
893    public void setTextBaseline(VPos baseline) {
894        if (curState.textbaseline != baseline) {
895            byte b;
896            switch (baseline) {
897                case TOP: b = PGCanvas.BASE_TOP; break;
898                case CENTER: b = PGCanvas.BASE_MIDDLE; break;
899                case BASELINE: b = PGCanvas.BASE_ALPHABETIC; break;
900                case BOTTOM: b = PGCanvas.BASE_BOTTOM; break;
901                default: return;
902            }
903            curState.textbaseline = baseline;
904            writeParam(b, PGCanvas.TEXT_BASELINE);
905        }
906    }
907    
908    /**
909     * Gets the current Text Baseline.
910     * 
911     * @return {@code VPos} with values of Top, Center, Baseline, or Bottom
912     */
913    public VPos getTextBaseline() {
914        return curState.textbaseline;
915    }
916
917    /**
918     * Fills the given string of text at position x, y (0,0 at top left)
919     * with the current fill paint attribute.
920     * 
921     * @param text the string of text.
922     * @param x position on the x axis.
923     * @param y position on the y axis.
924     */
925    public void fillText(String text, double x, double y) {
926        writeText(text, x, y, 0, PGCanvas.FILL_TEXT);
927    }
928
929    /**
930     * draws the given string of text at position x, y (0,0 at top left)
931     * with the current stroke paint attribute.
932     * 
933     * @param text the string of text.
934     * @param x position on the x axis.
935     * @param y position on the y axis.
936     */
937    public void strokeText(String text, double x, double y) {
938        writeText(text, x, y, 0, PGCanvas.STROKE_TEXT);
939    }
940
941    /**
942     * Fills text and includes a maximum width of the string. 
943     * 
944     * If the width of the text extends past max width, then it will be sized
945     * to fit.
946     * 
947     * @param text the string of text.
948     * @param x position on the x axis.
949     * @param y position on the y axis.
950     * @param maxWidth  maximum width the text string can have.
951     */
952    public void fillText(String text, double x, double y, double maxWidth) {
953        if (maxWidth <= 0) return;
954        writeText(text, x, y, maxWidth, PGCanvas.FILL_TEXT);
955    }
956
957    /**
958     * Draws text with stroke paint and includes a maximum width of the string. 
959     * 
960     * If the width of the text extends past max width, then it will be sized
961     * to fit.
962     * 
963     * @param text the string of text.
964     * @param x position on the x axis.
965     * @param y position on the y axis.
966     * @param maxWidth  maximum width the text string can have.
967     */
968    public void strokeText(String text, double x, double y, double maxWidth) {
969        if (maxWidth <= 0) return;
970        writeText(text, x, y, maxWidth, PGCanvas.STROKE_TEXT);
971    }
972
973
974    /**
975     * Set the filling rule constant for determining the interior of the path.
976     * The value must be one of the following constants:
977     * {@code FillRile.EVEN_ODD} or {@code FillRule.NON_ZERO}.
978     * The default value is {@code FillRule.NON_ZERO}.
979     *
980     * @defaultValue FillRule.NON_ZERO
981     */
982     public void setFillRule(FillRule fillRule) {
983         if (curState.fillRule != fillRule) {
984            byte b;
985            if (fillRule == FillRule.EVEN_ODD) {
986                b = PGCanvas.FILL_RULE_EVEN_ODD;
987            } else { 
988                b = PGCanvas.FILL_RULE_NON_ZERO;
989            }
990            curState.fillRule = fillRule;
991            writeParam(b, PGCanvas.FILL_RULE);
992        }
993     }
994    
995    /**
996     * Get the filling rule constant for determining the interior of the path.
997     * The default value is {@code FillRule.NON_ZERO}.
998     *
999     * @return current fill rule.
1000     */
1001     public FillRule getFillRule() {
1002         return curState.fillRule;
1003     }
1004    
1005    /**
1006     * Starts a Path 
1007     */
1008    public void beginPath() {
1009        path.reset();
1010        markPathDirty();
1011    }
1012
1013    /**
1014     * Issues a move command for the current path to the given x,y coordinate.
1015     * 
1016     * @param x0 the X position for the move to command.
1017     * @param y0 the Y position for the move to command.
1018     */
1019    public void moveTo(double x0, double y0) {
1020        coords[0] = (float) x0;
1021        coords[1] = (float) y0;
1022        curState.transform.transform(coords, 0, coords, 0, 1);
1023        path.moveTo(coords[0], coords[1]);
1024        markPathDirty();
1025    }
1026
1027    /**
1028     * Adds segments to the current path to make a line at the given x,y
1029     * coordinate.
1030     * 
1031     * @param x1 the X coordinate of the ending point of the line.
1032     * @param y1 the Y coordinate of the ending point of the line.
1033     */
1034    public void lineTo(double x1, double y1) {
1035        coords[0] = (float) x1;
1036        coords[1] = (float) y1;
1037        curState.transform.transform(coords, 0, coords, 0, 1);
1038        path.lineTo(coords[0], coords[1]);
1039        markPathDirty();
1040    }
1041
1042    /**
1043     * Adds segments to the current path to make a quadratic curve.
1044     * 
1045     * @param xc the X coordinate of the control point
1046     * @param yc the Y coordinate of the control point
1047     * @param x1 the X coordinate of the end point
1048     * @param y1 the Y coordinate of the end point
1049     */
1050    public void quadraticCurveTo(double xc, double yc, double x1, double y1) {
1051        coords[0] = (float) xc;
1052        coords[1] = (float) yc;
1053        coords[2] = (float) x1;
1054        coords[3] = (float) y1;
1055        curState.transform.transform(coords, 0, coords, 0, 2);
1056        path.quadTo(coords[0], coords[1], coords[2], coords[3]);
1057        markPathDirty();
1058    }
1059
1060    /**
1061     * Adds segments to the current path to make a cubic bezier curve.
1062     * 
1063     * @param xc1 the X coordinate of first bezier control point.
1064     * @param yc1 the Y coordinate of the first bezier control point.
1065     * @param xc2 the X coordinate of the second bezier control point.
1066     * @param yc2 the Y coordinate of the second bezier control point.
1067     * @param x1  the X coordinate of the end point.
1068     * @param y1  the Y coordinate of the end point.
1069     */
1070    public void bezierCurveTo(double xc1, double yc1, double xc2, double yc2, double x1, double y1) {
1071        coords[0] = (float) xc1;
1072        coords[1] = (float) yc1;
1073        coords[2] = (float) xc2;
1074        coords[3] = (float) yc2;
1075        coords[4] = (float) x1;
1076        coords[5] = (float) y1;
1077        curState.transform.transform(coords, 0, coords, 0, 3);
1078        path.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]);
1079        markPathDirty();
1080    }
1081
1082    /**
1083     * Adds segments to the current path to make an arc.
1084     * 
1085     * @param x1 the X coordinate of the first point of the arc.
1086     * @param y1 the Y coordinate of the first point of the arc.
1087     * @param x2 the X coordinate of the second point of the arc.
1088     * @param y2 the Y coordinate of the second point of the arc.
1089     * @param radius the radius of the arc in the range {0.0-positive infinity}.
1090     */
1091    public void arcTo(double x1, double y1, double x2, double y2, double radius) {
1092        if (path.getNumCommands() == 0) {
1093            moveTo(x1, y1);
1094            lineTo(x1, y1);
1095        } else if (!tryArcTo((float) x1, (float) y1, (float) x2, (float) y2,
1096                             (float) radius))
1097        {
1098            lineTo(x1, y1);
1099        }
1100    }
1101
1102    private static double lenSq(double x0, double y0, double x1, double y1) {
1103        x1 -= x0;
1104        y1 -= y0;
1105        return x1 * x1 + y1 * y1;
1106    }
1107
1108    private boolean tryArcTo(float x1, float y1, float x2, float y2, float radius) {
1109        float x0, y0;
1110        if (curState.transform.isTranslateOrIdentity()) {
1111            x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
1112            y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
1113        } else {
1114            coords[0] = path.getCurrentX();
1115            coords[1] = path.getCurrentY();
1116            try {
1117                curState.transform.inverseTransform(coords, 0, coords, 0, 1);
1118            } catch (NoninvertibleTransformException e) {
1119                return false;
1120            }
1121            x0 = coords[0];
1122            y0 = coords[1];
1123        }
1124        // call x1,y1 the corner point
1125        // If 2*theta is the angle described by p0->p1->p2
1126        // then theta is the angle described by p0->p1->centerpt and
1127        // centerpt->p1->p2
1128        // We know that the distance from the arc center to the tangent points
1129        // is r, and if A is the distance from the corner to the tangent point
1130        // then we know:
1131        // tan(theta) = r/A
1132        // A = r / sin(theta)
1133        // B = A * cos(theta) = r * (sin/cos) = r * tan
1134        // We use the cosine rule on the triangle to get the 2*theta angle:
1135        // cosB = (a^2 + c^2 - b^2) / (2ac)
1136        // where a and c are the adjacent sides and b is the opposite side
1137        // i.e. a = p0->p1, c=p1->p2, b=p0->p2
1138        // Then we can use the tan^2 identity to compute B:
1139        // tan^2 = (1 - cos(2theta)) / (1 + cos(2theta))
1140        double lsq01 = lenSq(x0, y0, x1, y1);
1141        double lsq12 = lenSq(x1, y1, x2, y2);
1142        double lsq02 = lenSq(x0, y0, x2, y2);
1143        double len01 = Math.sqrt(lsq01);
1144        double len12 = Math.sqrt(lsq12);
1145        double cosnum = lsq01 + lsq12 - lsq02;
1146        double cosden = 2.0 * len01 * len12;
1147        if (cosden == 0.0 || radius <= 0f) {
1148            return false;
1149        }
1150        double cos_2theta = cosnum / cosden;
1151        double tansq_den = (1.0 + cos_2theta);
1152        if (tansq_den == 0.0) {
1153            return false;
1154        }
1155        double tansq_theta = (1.0 - cos_2theta) / tansq_den;
1156        double A = radius / Math.sqrt(tansq_theta);
1157        double tx0 = x1 + (A / len01) * (x0 - x1);
1158        double ty0 = y1 + (A / len01) * (y0 - y1);
1159        double tx1 = x1 + (A / len12) * (x2 - x1);
1160        double ty1 = y1 + (A / len12) * (y2 - y1);
1161        // The midpoint between the two tangent points
1162        double mx = (tx0 + tx1) / 2.0;
1163        double my = (ty0 + ty1) / 2.0;
1164        // similar triangles tell us that:
1165        // len(m,center)/len(m,tangent) = len(m,tangent)/len(corner,m)
1166        // len(m,center) = lensq(m,tangent)/len(corner,m)
1167        // center = m + (m - p1) * len(m,center) / len(corner,m)
1168        //   = m + (m - p1) * (lensq(m,tangent) / lensq(corner,m))
1169        double lenratioden = lenSq(mx, my, x1, y1);
1170        if (lenratioden == 0.0) {
1171            return false;
1172        }
1173        double lenratio = lenSq(mx, my, tx0, ty0) / lenratioden;
1174        double cx = mx + (mx - x1) * lenratio;
1175        double cy = my + (my - y1) * lenratio;
1176        if (!(cx == cx && cy == cy)) {
1177            return false;
1178        }
1179        // Looks like we are good to draw, first we have to get to the
1180        // initial tangent point with a line segment.
1181        if (tx0 != x0 || ty0 != y0) {
1182            lineTo(tx0, ty0);
1183        }
1184        // We need sin(arc/2), cos(arc/2)
1185        // and possibly sin(arc/4), cos(arc/4) if we need 2 cubic beziers
1186        // We have tan(theta) = tan(tri/2)
1187        // arc = 180-tri
1188        // arc/2 = (180-tri)/2 = 90-(tri/2)
1189        // sin(arc/2) = sin(90-(tri/2)) = cos(tri/2)
1190        // cos(arc/2) = cos(90-(tri/2)) = sin(tri/2)
1191        // 2theta = tri, therefore theta = tri/2
1192        // cos(tri/2)^2 = (1+cos(tri)) / 2.0 = (1+cos_2theta)/2.0
1193        // sin(tri/2)^2 = (1-cos(tri)) / 2.0 = (1-cos_2theta)/2.0
1194        // sin(arc/2) = cos(tri/2) = sqrt((1+cos_2theta)/2.0)
1195        // cos(arc/2) = sin(tri/2) = sqrt((1-cos_2theta)/2.0)
1196        // We compute cos(arc/2) here as we need it in either case below
1197        double coshalfarc = Math.sqrt((1.0 - cos_2theta) / 2.0);
1198        boolean ccw = (ty0 - cy) * (tx1 - cx) > (ty1 - cy) * (tx0 - cx);
1199        // If the arc covers more than 90 degrees then we must use 2
1200        // cubic beziers to get a decent approximation.
1201        // arc = 180-tri
1202        // arc = 180-2*theta
1203        // arc > 90 implies 2*theta < 90
1204        // 2*theta < 90 implies cos_2theta > 0
1205        // So, we need 2 cubics if cos_2theta > 0
1206        if (cos_2theta <= 0.0) {
1207            // 1 cubic bezier
1208            double sinhalfarc = Math.sqrt((1.0 + cos_2theta) / 2.0);
1209            double cv = 4.0 / 3.0 * sinhalfarc / (1.0 + coshalfarc);
1210            if (ccw) cv = -cv;
1211            double cpx0 = tx0 - cv * (ty0 - cy);
1212            double cpy0 = ty0 + cv * (tx0 - cx);
1213            double cpx1 = tx1 + cv * (ty1 - cy);
1214            double cpy1 = ty1 - cv * (tx1 - cx);
1215            bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
1216        } else {
1217            // 2 cubic beziers
1218            // We need sin(arc/4) and cos(arc/4)
1219            // We computed cos(arc/2), so we can compute them as follows:
1220            // sin(arc/4) = sqrt((1 - cos(arc/2)) / 2)
1221            // cos(arc/4) = sart((1 + cos(arc/2)) / 2)
1222            double sinqtrarc = Math.sqrt((1.0 - coshalfarc) / 2.0);
1223            double cosqtrarc = Math.sqrt((1.0 + coshalfarc) / 2.0);
1224            double cv = 4.0 / 3.0 * sinqtrarc / (1.0 + cosqtrarc);
1225            if (ccw) cv = -cv;
1226            double midratio = radius / Math.sqrt(lenratioden);
1227            double midarcx = cx + (x1 - mx) * midratio;
1228            double midarcy = cy + (y1 - my) * midratio;
1229            double cpx0 = tx0 - cv * (ty0 - cy);
1230            double cpy0 = ty0 + cv * (tx0 - cx);
1231            double cpx1 = midarcx + cv * (midarcy - cy);
1232            double cpy1 = midarcy - cv * (midarcx - cx);
1233            bezierCurveTo(cpx0, cpy0, cpx1, cpy1, midarcx, midarcy);
1234            cpx0 = midarcx - cv * (midarcy - cy);
1235            cpy0 = midarcy + cv * (midarcx - cx);
1236            cpx1 = tx1 + cv * (ty1 - cy);
1237            cpy1 = ty1 - cv * (tx1 - cx);
1238            bezierCurveTo(cpx0, cpy0, cpx1, cpy1, tx1, ty1);
1239        }
1240        return true;
1241    }
1242
1243    private static final Arc2D TEMP_ARC = new Arc2D();
1244    /**
1245     * Adds path elements to the current path to make an arc that uses Euclidean
1246     * degrees. This Euclidean orientation sweeps from East to North, then West,
1247     * then South, then back to East. 
1248     *
1249     * @param centerX the center x position of the arc.
1250     * @param centerY the center y position of the arc.
1251     * @param radiusX the x radius of the arc.
1252     * @param radiusY the y radius of the arc.
1253     * @param startAngle the starting angle of the arc in the range {@code 0-360.0}
1254     * @param length  the length of the baseline of the arc.
1255     */
1256    public void arc(double centerX, double centerY,
1257                    double radiusX, double radiusY,
1258                    double startAngle, double length)
1259    {
1260        TEMP_ARC.setArc((float)(centerX - radiusX), // x
1261                        (float)(centerY - radiusY), // y
1262                        (float)(radiusX * 2.0), // w
1263                        (float)(radiusY * 2.0), // h
1264                        (float)startAngle,
1265                        (float)length,
1266                        Arc2D.OPEN);
1267        path.append(TEMP_ARC.getPathIterator(curState.transform), true);
1268        markPathDirty();
1269    }
1270
1271    /**
1272     * Adds path elements to the current path to make a rectangle.
1273     * 
1274     * @param x x position of the upper left corner of the rectangle.
1275     * @param y y position of the upper left corner of the rectangle.
1276     * @param w width of the rectangle.
1277     * @param h height of the rectangle.
1278     */
1279    public void rect(double x, double y, double w, double h) {
1280        coords[0] = (float) x;
1281        coords[1] = (float) y;
1282        coords[2] = (float) w;
1283        coords[3] = (float) 0;
1284        coords[4] = (float) 0;
1285        coords[5] = (float) h;
1286        curState.transform.deltaTransform(coords, 0, coords, 0, 3);
1287        float x0 = coords[0] + (float) curState.transform.getMxt();
1288        float y0 = coords[1] + (float) curState.transform.getMyt();
1289        float dx1 = coords[2];
1290        float dy1 = coords[3];
1291        float dx2 = coords[4];
1292        float dy2 = coords[5];
1293        path.moveTo(x0, y0);
1294        path.lineTo(x0+dx1, y0+dy1);
1295        path.lineTo(x0+dx1+dx2, y0+dy1+dy2);
1296        path.lineTo(x0+dx2, y0+dy2);
1297        path.closePath();
1298        markPathDirty();
1299//        path.moveTo(x0, y0); // not needed, closepath leaves pen at moveto
1300    }
1301
1302    /**
1303     * Appends an SVG Path string to the current path. If there is no current 
1304     * path the string must then start with either type of move command.
1305     * 
1306     * @param svgpath the SVG Path string.
1307     */
1308    public void appendSVGPath(String svgpath) {
1309        boolean prependMoveto = true;
1310        boolean skipMoveto = true;
1311        for (int i = 0; i < svgpath.length(); i++) {
1312            switch (svgpath.charAt(i)) {
1313                case ' ':
1314                case '\t':
1315                case '\r':
1316                case '\n':
1317                    continue;
1318                case 'M':
1319                    prependMoveto = skipMoveto = false;
1320                    break;
1321                case 'm':
1322                    if (path.getNumCommands() == 0) {
1323                        // An initial relative moveTo becomes absolute
1324                        prependMoveto = false;
1325                    }
1326                    // Even if we prepend an initial moveTo in the temp
1327                    // path, we do not want to delete the resulting initial
1328                    // moveTo because the relative moveto will be folded
1329                    // into it by an optimization in the Path2D object.
1330                    skipMoveto = false;
1331                    break;
1332            }
1333            break;
1334        }
1335        Path2D p2d = new Path2D();
1336        if (prependMoveto && path.getNumCommands() > 0) {
1337            float x0, y0;
1338            if (curState.transform.isTranslateOrIdentity()) {
1339                x0 = (float) (path.getCurrentX() - curState.transform.getMxt());
1340                y0 = (float) (path.getCurrentY() - curState.transform.getMyt());
1341            } else {
1342                coords[0] = path.getCurrentX();
1343                coords[1] = path.getCurrentY();
1344                try {
1345                    curState.transform.inverseTransform(coords, 0, coords, 0, 1);
1346                } catch (NoninvertibleTransformException e) {
1347                }
1348                x0 = coords[0];
1349                y0 = coords[1];
1350            }
1351            p2d.moveTo(x0, y0);
1352        } else {
1353            skipMoveto = false;
1354        }
1355        p2d.appendSVGPath(svgpath);
1356        PathIterator pi = p2d.getPathIterator(curState.transform);
1357        if (skipMoveto) {
1358            // We need to delete the initial moveto and let the path
1359            // extend from the actual existing geometry.
1360            pi.next();
1361        }
1362        path.append(pi, false);
1363    }
1364
1365    /**
1366     * Closes the path.
1367     */
1368    public void closePath() {
1369        path.closePath();
1370        markPathDirty();
1371    }
1372
1373    /**
1374     * Fills the path with the current fill paint.
1375     */
1376    public void fill() {
1377        writePath(PGCanvas.FILL_PATH);
1378    }
1379
1380    /**
1381     * Strokes the path with the current stroke paint.
1382     */
1383    public void stroke() {
1384        writePath(PGCanvas.STROKE_PATH);
1385    }
1386
1387    /**
1388     * Clips using the current path 
1389     */
1390    public void clip() {
1391        Path2D clip = new Path2D(path);
1392        clipStack.addLast(clip);
1393        curState.numClipPaths++;
1394        GrowableDataBuffer buf = getBuffer();
1395        buf.putByte(PGCanvas.PUSH_CLIP);
1396        buf.putObject(clip);
1397    }
1398
1399    /**
1400     * Returns true if the the given x,y point is inside the path.
1401     * 
1402     * @param x the X coordinate to use for the check.
1403     * @param y the Y coordinate to use for the check.
1404     * @return true if the point given is inside the path, false 
1405     * otherwise.
1406     */
1407    public boolean isPointInPath(double x, double y) {
1408        // TODO: HTML5 considers points on the path to be inside, but we
1409        // implement a halfin-halfout approach...
1410        return path.contains((float) x, (float) y);
1411    }
1412
1413    /**
1414     * Clears a portion of the canvas with a transparent color value.
1415     * 
1416     * @param x X position of the upper left corner of the rectangle.
1417     * @param y Y position of the upper left corner of the rectangle.
1418     * @param w width of the rectangle.
1419     * @param h height of the rectangle.
1420     */
1421    public void clearRect(double x, double y, double w, double h) {
1422        if (w != 0 && h != 0) {
1423            writeOp4(x, y, w, h, PGCanvas.CLEAR_RECT);
1424        }
1425    }
1426
1427    /**
1428     * Fills a rectangle using the current fill paint.
1429     * 
1430     * @param x the X position of the upper left corner of the rectangle.
1431     * @param y the Y position of the upper left corner of the rectangle.
1432     * @param w the width of the rectangle.
1433     * @param h the height of the rectangle.
1434     */
1435    public void fillRect(double x, double y, double w, double h) {
1436        if (w != 0 && h != 0) {
1437            writeOp4(x, y, w, h, PGCanvas.FILL_RECT);
1438        }
1439    }
1440
1441    /**
1442     * Strokes a rectangle using the current stroke paint.
1443     * 
1444     * @param x the X position of the upper left corner of the rectangle.
1445     * @param y the Y position of the upper left corner of the rectangle.
1446     * @param w the width of the rectangle.
1447     * @param h the height of the rectangle.
1448     */
1449    public void strokeRect(double x, double y, double w, double h) {
1450        if (w != 0 || h != 0) {
1451            writeOp4(x, y, w, h, PGCanvas.STROKE_RECT);
1452        }
1453    }
1454
1455    /**
1456     * Fills an oval using the current fill paint.
1457     * 
1458     * @param x the X coordinate of the upper left bound of the oval.
1459     * @param y the Y coordinate of the upper left bound of the oval.
1460     * @param w the width at the center of the oval.
1461     * @param h the height at the center of the oval.
1462     */
1463    public void fillOval(double x, double y, double w, double h) {
1464        if (w != 0 && h != 0) {
1465            writeOp4(x, y, w, h, PGCanvas.FILL_OVAL);
1466        }
1467    }
1468
1469    /**
1470     * Strokes a rectangle using the current stroke paint.
1471     * 
1472     * @param x the X coordinate of the upper left bound of the oval.
1473     * @param y the Y coordinate of the upper left bound of the oval.
1474     * @param w the width at the center of the oval.
1475     * @param h the height at the center of the oval.
1476     */
1477    public void strokeOval(double x, double y, double w, double h) {
1478        if (w != 0 || h != 0) {
1479            writeOp4(x, y, w, h, PGCanvas.STROKE_OVAL);
1480        }
1481    }
1482
1483    /**
1484     * Fills an arc using the current fill paint.
1485     * 
1486     * @param x the X coordinate of the arc.
1487     * @param y the Y coordinate of the arc.
1488     * @param w the width of the arc.
1489     * @param h the height of the arc.
1490     * @param startAngle the starting angle of the arc in degrees.
1491     * @param arcExtent the angular extent of the arc in degrees.
1492     * @param closure closure type (Round, Chord, Open).
1493     */
1494    public void fillArc(double x, double y, double w, double h,
1495                        double startAngle, double arcExtent, ArcType closure)
1496    {
1497        if (w != 0 && h != 0) {
1498            writeArcType(closure);
1499            writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.FILL_ARC);
1500        }
1501    }
1502
1503    /**
1504     * Strokes an Arc using the current stroke paint.
1505     * 
1506     * @param x the X coordinate of the arc.
1507     * @param y the Y coordinate of the arc.
1508     * @param w the width of the arc.
1509     * @param h the height of the arc.
1510     * @param startAngle the starting angle of the arc in degrees.
1511     * @param arcExtent arcExtent the angular extent of the arc in degrees.
1512     * @param closure closure type (Round, Chord, Open).
1513     */
1514    public void strokeArc(double x, double y, double w, double h,
1515                        double startAngle, double arcExtent, ArcType closure)
1516    {
1517        if (w != 0 && h != 0) {
1518            writeArcType(closure);
1519            writeOp6(x, y, w, h, startAngle, arcExtent, PGCanvas.STROKE_ARC);
1520        }
1521    }
1522
1523    /**
1524     * Fills a rounded rectangle using the current fill paint.
1525     * 
1526     * @param x the X coordinate of the upper left bound of the oval.
1527     * @param y the Y coordinate of the upper left bound of the oval.
1528     * @param w the width at the center of the oval.
1529     * @param h the height at the center of the oval.
1530     * @param arcWidth the arc width of the rectangle corners.
1531     * @param arcHeight the arc height of the rectangle corners. 
1532     */
1533    public void fillRoundRect(double x, double y, double w, double h,
1534                              double arcWidth, double arcHeight)
1535    {
1536        if (w != 0 && h != 0) {
1537            writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.FILL_ROUND_RECT);
1538        }
1539    }
1540
1541    /**
1542     * Strokes a rounded rectangle using the current stroke paint.
1543     * 
1544     * @param x the X coordinate of the upper left bound of the oval.
1545     * @param y the Y coordinate of the upper left bound of the oval.
1546     * @param w the width at the center of the oval.
1547     * @param h the height at the center of the oval.
1548     * @param arcWidth the arc width of the rectangle corners.
1549     * @param arcHeight the arc height of the rectangle corners.
1550     */
1551    public void strokeRoundRect(double x, double y, double w, double h,
1552                              double arcWidth, double arcHeight)
1553    {
1554        if (w != 0 && h != 0) {
1555            writeOp6(x, y, w, h, arcWidth, arcHeight, PGCanvas.STROKE_ROUND_RECT);
1556        }
1557    }
1558
1559    /**
1560     * Strokes a line using the current stroke paint.
1561     * 
1562     * @param x1 the X coordinate of the starting point of the line.
1563     * @param y1 the Y coordinate of the starting point of the line.
1564     * @param x2 the X coordinate of the ending point of the line.
1565     * @param y2 the Y coordinate of the ending point of the line.
1566     */
1567    public void strokeLine(double x1, double y1, double x2, double y2) {
1568        writeOp4(x1, y1, x2, y2, PGCanvas.STROKE_LINE);
1569    }
1570
1571    /**
1572     * Fills a polygon with the given points using the currently set fill paint.
1573     * 
1574     * @param xPoints array containing the x coordinates of the polygon's points.
1575     * @param yPoints array containing the y coordinates of the polygon's points.
1576     * @param nPoints the number of points that make the polygon.
1577     */
1578    public void fillPolygon(double xPoints[], double yPoints[], int nPoints) {
1579        if (nPoints >= 3) {
1580            writePoly(xPoints, yPoints, nPoints, true, PGCanvas.FILL_PATH);
1581        }
1582    }
1583
1584    /**
1585     * Strokes a polygon with the given points using the currently set stroke paint.
1586     * 
1587     * @param xPoints array containing the x coordinates of the polygon's points.
1588     * @param yPoints array containing the y coordinates of the polygon's points.
1589     * @param nPoints the number of points that make the polygon.
1590     */
1591    public void strokePolygon(double xPoints[], double yPoints[], int nPoints) {
1592        if (nPoints >= 2) {
1593            writePoly(xPoints, yPoints, nPoints, true, PGCanvas.STROKE_PATH);
1594        }
1595    }
1596
1597    /**
1598     * Draws a polyline with the given points using the currently set stroke 
1599     * paint attribute.
1600     * 
1601     * @param xPoints array containing the x coordinates of the polyline's points.
1602     * @param yPoints array containing the y coordinates of the polyline's points.
1603     * @param nPoints the number of points that make the polyline.
1604     */
1605    public void strokePolyline(double xPoints[], double yPoints[], int nPoints) {
1606        if (nPoints >= 2) {
1607            writePoly(xPoints, yPoints, nPoints, false, PGCanvas.STROKE_PATH);
1608        }
1609    }
1610
1611    /**
1612     * Draws an image at the given x, y position using the width
1613     * and height of the given image.
1614     * 
1615     * @param img the image to be drawn.
1616     * @param x the X coordinate on the destination for the upper left of the image.
1617     * @param y the Y coordinate on the destination for the upper left of the image.
1618     */
1619    public void drawImage(Image img, double x, double y) {
1620        double sw = img.getWidth();
1621        double sh = img.getHeight();
1622        writeImage(img, x, y, sw, sh);
1623    }
1624
1625    /**
1626     * Draws an image into the given destination rectangle of the canvas. The
1627     * Image is scaled to fit into the destination rectagnle.
1628     * 
1629     * @param img the image to be drawn.
1630     * @param x the X coordinate on the destination for the upper left of the image.
1631     * @param y the Y coordinate on the destination for the upper left of the image.
1632     * @param w the width of the destination rectangle. 
1633     * @param h the height of the destination rectangle.
1634     */
1635    public void drawImage(Image img, double x, double y, double w, double h) {
1636        writeImage(img, x, y, w, h);
1637    }
1638
1639    /**
1640     * Draws the current source rectangle of the given image to the given 
1641     * destination rectangle of the Canvas.
1642     * 
1643     * @param img the image to be drawn.
1644     * @param sx the source rectangle's X coordinate position.
1645     * @param sy the source rectangle's Y coordinate position.
1646     * @param sw the source rectangle's width.
1647     * @param sh the source rectangle's height.
1648     * @param dx the destination rectangle's X coordinate position.
1649     * @param dy the destination rectangle's Y coordinate position.
1650     * @param dw the destination rectangle's width.
1651     * @param dh the destination rectangle's height.
1652     */
1653    public void drawImage(Image img,
1654                          double sx, double sy, double sw, double sh,
1655                          double dx, double dy, double dw, double dh)
1656    {
1657        writeImage(img, dx, dy, dw, dh, sx, sy, sw, sh);
1658    }
1659
1660    private PixelWriter writer;
1661    /**
1662     * Returns a {@link PixelWriter} object that can be used to modify
1663     * the pixels of the {@link Canvas} associated with this
1664     * {@code GraphicsContext}.
1665     * All coordinates in the {@code PixelWriter} methods on the returned
1666     * object will be in device space since they refer directly to pixels.
1667     * 
1668     * @return the {@code PixelWriter} for modifying the pixels of this
1669     *         {@code Canvas}
1670     */
1671    public PixelWriter getPixelWriter() {
1672        if (writer == null) {
1673            writer = new PixelWriter() {
1674                @Override
1675                public PixelFormat getPixelFormat() {
1676                    return PixelFormat.getByteBgraPreInstance();
1677                }
1678
1679                private BytePixelSetter getSetter() {
1680                    return ByteBgraPre.setter;
1681                }
1682
1683                @Override
1684                public void setArgb(int x, int y, int argb) {
1685                    GrowableDataBuffer buf = getBuffer();
1686                    buf.putByte(PGCanvas.PUT_ARGB);
1687                    buf.putInt(x);
1688                    buf.putInt(y);
1689                    buf.putInt(argb);
1690                }
1691
1692                @Override
1693                public void setColor(int x, int y, Color c) {
1694                    int a = (int) Math.round(c.getOpacity() * 255.0);
1695                    int r = (int) Math.round(c.getRed() * 255.0);
1696                    int g = (int) Math.round(c.getGreen() * 255.0);
1697                    int b = (int) Math.round(c.getBlue() * 255.0);
1698                    setArgb(x, y, (a << 24) | (r << 16) | (g << 8) | b);
1699                }
1700
1701                private void writePixelBuffer(int x, int y, int w, int h,
1702                                              byte[] pixels)
1703                {
1704                    GrowableDataBuffer buf = getBuffer();
1705                    buf.putByte(PGCanvas.PUT_ARGBPRE_BUF);
1706                    buf.putInt(x);
1707                    buf.putInt(y);
1708                    buf.putInt(w);
1709                    buf.putInt(h);
1710                    buf.putObject(pixels);
1711                }
1712
1713                private int[] checkBounds(int x, int y, int w, int h,
1714                                          PixelFormat pf, int scan)
1715                {
1716                    // assert (w >= 0 && h >= 0) - checked by caller
1717                    int cw = (int) theCanvas.getWidth();
1718                    int ch = (int) theCanvas.getHeight();
1719                    if (x >= 0 && y >= 0 && x+w <= cw && y+h <= ch) {
1720                        return null;
1721                    }
1722                    int offset = 0;
1723                    if (x < 0) {
1724                        w += x;
1725                        if (w < 0) return null;
1726                        if (pf != null) {
1727                            switch (pf.getType()) {
1728                                case BYTE_BGRA:
1729                                case BYTE_BGRA_PRE:
1730                                    offset -= x * 4;
1731                                    break;
1732                                case BYTE_RGB:
1733                                    offset -= x * 3;
1734                                    break;
1735                                case BYTE_INDEXED:
1736                                case INT_ARGB:
1737                                case INT_ARGB_PRE:
1738                                    offset -= x;
1739                                    break;
1740                                default:
1741                                    throw new InternalError("unknown Pixel Format");
1742                            }
1743                        }
1744                        x = 0;
1745                    }
1746                    if (y < 0) {
1747                        h += y;
1748                        if (h < 0) return null;
1749                        offset -= y * scan;
1750                        y = 0;
1751                    }
1752                    if (x + w > cw) {
1753                        w = cw - x;
1754                        if (w < 0) return null;
1755                    }
1756                    if (y + h > ch) {
1757                        h = ch - y;
1758                        if (h < 0) return null;
1759                    }
1760                    return new int[] {
1761                        x, y, w, h, offset
1762                    };
1763                }
1764
1765                @Override
1766                public <T extends Buffer> void
1767                    setPixels(int x, int y, int w, int h,
1768                              PixelFormat<T> pixelformat,
1769                              T buffer, int scan)
1770                {
1771                    if (w <= 0 || h <= 0) return;
1772                    int offset = buffer.position();
1773                    int adjustments[] = checkBounds(x, y, w, h,
1774                                                    pixelformat, scan);
1775                    if (adjustments != null) {
1776                        x = adjustments[0];
1777                        y = adjustments[1];
1778                        w = adjustments[2];
1779                        h = adjustments[3];
1780                        offset += adjustments[4];
1781                    }
1782
1783                    byte pixels[] = new byte[w * h * 4];
1784                    ByteBuffer dst = ByteBuffer.wrap(pixels);
1785
1786                    PixelGetter<T> getter = PixelUtils.getGetter(pixelformat);
1787                    PixelConverter<T, ByteBuffer> converter =
1788                        PixelUtils.getConverter(getter, getSetter());
1789                    converter.convert(buffer, offset, scan,
1790                                      dst, 0, w * 4,
1791                                      w, h);
1792                    writePixelBuffer(x, y, w, h, pixels);
1793                }
1794
1795                @Override
1796                public void setPixels(int x, int y, int w, int h,
1797                                      PixelFormat<ByteBuffer> pixelformat,
1798                                      byte[] buffer, int offset, int scanlineStride)
1799                {
1800                    if (w <= 0 || h <= 0) return;
1801                    int adjustments[] = checkBounds(x, y, w, h,
1802                                                    pixelformat, scanlineStride);
1803                    if (adjustments != null) {
1804                        x = adjustments[0];
1805                        y = adjustments[1];
1806                        w = adjustments[2];
1807                        h = adjustments[3];
1808                        offset += adjustments[4];
1809                    }
1810
1811                    byte pixels[] = new byte[w * h * 4];
1812
1813                    BytePixelGetter getter = PixelUtils.getByteGetter(pixelformat);
1814                    ByteToBytePixelConverter converter =
1815                        PixelUtils.getB2BConverter(getter, getSetter());
1816                    converter.convert(buffer, offset, scanlineStride,
1817                                      pixels, 0, w * 4,
1818                                      w, h);
1819                    writePixelBuffer(x, y, w, h, pixels);
1820                }
1821
1822                @Override
1823                public void setPixels(int x, int y, int w, int h,
1824                                      PixelFormat<IntBuffer> pixelformat,
1825                                      int[] buffer, int offset, int scanlineStride)
1826                {
1827                    if (w <= 0 || h <= 0) return;
1828                    int adjustments[] = checkBounds(x, y, w, h,
1829                                                    pixelformat, scanlineStride);
1830                    if (adjustments != null) {
1831                        x = adjustments[0];
1832                        y = adjustments[1];
1833                        w = adjustments[2];
1834                        h = adjustments[3];
1835                        offset += adjustments[4];
1836                    }
1837
1838                    byte pixels[] = new byte[w * h * 4];
1839
1840                    IntPixelGetter getter = PixelUtils.getIntGetter(pixelformat);
1841                    IntToBytePixelConverter converter =
1842                        PixelUtils.getI2BConverter(getter, getSetter());
1843                    converter.convert(buffer, offset, scanlineStride,
1844                                      pixels, 0, w * 4,
1845                                      w, h);
1846                    writePixelBuffer(x, y, w, h, pixels);
1847                }
1848
1849                @Override
1850                public void setPixels(int dstx, int dsty, int w, int h,
1851                                      PixelReader reader, int srcx, int srcy)
1852                {
1853                    if (w <= 0 || h <= 0) return;
1854                    int adjustments[] = checkBounds(dstx, dsty, w, h, null, 0);
1855                    if (adjustments != null) {
1856                        int newx = adjustments[0];
1857                        int newy = adjustments[1];
1858                        srcx += newx - dstx;
1859                        srcy += newy - dsty;
1860                        dstx = newx;
1861                        dsty = newy;
1862                        w = adjustments[2];
1863                        h = adjustments[3];
1864                    }
1865
1866                    byte pixels[] = new byte[w * h * 4];
1867                    reader.getPixels(srcx, srcy, w, h,
1868                                     PixelFormat.getByteBgraPreInstance(),
1869                                     pixels, 0, w * 4);
1870                    writePixelBuffer(dstx, dsty, w, h, pixels);
1871                }
1872            };
1873        }
1874        return writer;
1875    }
1876
1877    /**
1878     * Sets the effect to be applied after the next draw call, or null to
1879     * disable effects.
1880     * @param e the effect to use, or null to disable effects
1881     */
1882    public void setEffect(Effect e) {
1883        GrowableDataBuffer buf = getBuffer();
1884        buf.putByte(PGCanvas.EFFECT);
1885        if (e == null) {
1886            curState.effect = null;
1887            buf.putObject(null);
1888        } else {
1889            curState.effect = e.impl_copy();
1890            curState.effect.impl_sync();
1891            buf.putObject(curState.effect.impl_getImpl());
1892        }
1893    }
1894    
1895    /**
1896     * Gets a copy of the effect to be applied after the next draw call.
1897     * A null return value means that no effect will be applied after future
1898     * rendering calls.
1899     * @param e an {@code Effect} object that may be used to store the
1900     *        copy of the current effect, if it is of a compatible type
1901     * @return the current effect used for all rendering calls,
1902     *         or null if there is no current effect
1903     */
1904    public Effect getEffect(Effect e) {
1905        return curState.effect == null ? null : curState.effect.impl_copy();
1906    }
1907
1908    /**
1909     * Applies the given effect to the entire canvas.
1910     * @param e the effect to apply onto the entire destination.
1911     */
1912    public void applyEffect(Effect e) {
1913        GrowableDataBuffer buf = getBuffer();
1914        buf.putByte(PGCanvas.FX_APPLY_EFFECT);
1915        Effect effect = e.impl_copy();
1916        effect.impl_sync();
1917        buf.putObject(effect.impl_getImpl());
1918    }
1919}