Path_Delegate.java revision 8a80a8555238cc564f445f902aff5231993a8f96
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
23
24import android.graphics.Path.Direction;
25import android.graphics.Path.FillType;
26
27import java.awt.Shape;
28import java.awt.geom.AffineTransform;
29import java.awt.geom.Arc2D;
30import java.awt.geom.Area;
31import java.awt.geom.GeneralPath;
32import java.awt.geom.PathIterator;
33import java.awt.geom.Point2D;
34import java.awt.geom.Rectangle2D;
35
36/**
37 * Delegate implementing the native methods of android.graphics.Path
38 *
39 * Through the layoutlib_create tool, the original native methods of Path have been replaced
40 * by calls to methods of the same name in this delegate class.
41 *
42 * This class behaves like the original native implementation, but in Java, keeping previously
43 * native data into its own objects and mapping them to int that are sent back and forth between
44 * it and the original Path class.
45 *
46 * @see DelegateManager
47 *
48 */
49public final class Path_Delegate {
50
51    // ---- delegate manager ----
52    private static final DelegateManager<Path_Delegate> sManager =
53            new DelegateManager<Path_Delegate>();
54
55    // ---- delegate data ----
56    private FillType mFillType = FillType.WINDING;
57    private GeneralPath mPath = new GeneralPath();
58
59    private float mLastX = 0;
60    private float mLastY = 0;
61
62    // ---- Public Helper methods ----
63
64    public static Path_Delegate getDelegate(int nPath) {
65        return sManager.getDelegate(nPath);
66    }
67
68    public Shape getJavaShape() {
69        return mPath;
70    }
71
72    public void setJavaShape(Shape shape) {
73        mPath.reset();
74        mPath.append(shape, false /*connect*/);
75    }
76
77    public void reset() {
78        mPath.reset();
79    }
80
81    public void setPathIterator(PathIterator iterator) {
82        mPath.reset();
83        mPath.append(iterator, false /*connect*/);
84    }
85
86    // ---- native methods ----
87
88    @LayoutlibDelegate
89    /*package*/ static int init1() {
90        // create the delegate
91        Path_Delegate newDelegate = new Path_Delegate();
92
93        return sManager.addDelegate(newDelegate);
94    }
95
96    @LayoutlibDelegate
97    /*package*/ static int init2(int nPath) {
98        // create the delegate
99        Path_Delegate newDelegate = new Path_Delegate();
100
101        // get the delegate to copy, which could be null if nPath is 0
102        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
103        if (pathDelegate != null) {
104            newDelegate.set(pathDelegate);
105        }
106
107        return sManager.addDelegate(newDelegate);
108    }
109
110    @LayoutlibDelegate
111    /*package*/ static void native_reset(int nPath) {
112        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
113        if (pathDelegate == null) {
114            return;
115        }
116
117        pathDelegate.mPath.reset();
118    }
119
120    @LayoutlibDelegate
121    /*package*/ static void native_rewind(int nPath) {
122        // call out to reset since there's nothing to optimize in
123        // terms of data structs.
124        native_reset(nPath);
125    }
126
127    @LayoutlibDelegate
128    /*package*/ static void native_set(int native_dst, int native_src) {
129        Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
130        if (pathDstDelegate == null) {
131            return;
132        }
133
134        Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
135        if (pathSrcDelegate == null) {
136            return;
137        }
138
139        pathDstDelegate.set(pathSrcDelegate);
140    }
141
142    @LayoutlibDelegate
143    /*package*/ static int native_getFillType(int nPath) {
144        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
145        if (pathDelegate == null) {
146            return 0;
147        }
148
149        return pathDelegate.mFillType.nativeInt;
150    }
151
152    @LayoutlibDelegate
153    /*package*/ static void native_setFillType(int nPath, int ft) {
154        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
155        if (pathDelegate == null) {
156            return;
157        }
158
159        pathDelegate.mFillType = Path.sFillTypeArray[ft];
160    }
161
162    @LayoutlibDelegate
163    /*package*/ static boolean native_isEmpty(int nPath) {
164        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
165        if (pathDelegate == null) {
166            return true;
167        }
168
169        return pathDelegate.isEmpty();
170    }
171
172    @LayoutlibDelegate
173    /*package*/ static boolean native_isRect(int nPath, RectF rect) {
174        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
175        if (pathDelegate == null) {
176            return false;
177        }
178
179        // create an Area that can test if the path is a rect
180        Area area = new Area(pathDelegate.mPath);
181        if (area.isRectangular()) {
182            if (rect != null) {
183                pathDelegate.fillBounds(rect);
184            }
185
186            return true;
187        }
188
189        return false;
190    }
191
192    @LayoutlibDelegate
193    /*package*/ static void native_computeBounds(int nPath, RectF bounds) {
194        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
195        if (pathDelegate == null) {
196            return;
197        }
198
199        pathDelegate.fillBounds(bounds);
200    }
201
202    @LayoutlibDelegate
203    /*package*/ static void native_incReserve(int nPath, int extraPtCount) {
204        // since we use a java2D path, there's no way to pre-allocate new points,
205        // so we do nothing.
206    }
207
208    @LayoutlibDelegate
209    /*package*/ static void native_moveTo(int nPath, float x, float y) {
210        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
211        if (pathDelegate == null) {
212            return;
213        }
214
215        pathDelegate.moveTo(x, y);
216    }
217
218    @LayoutlibDelegate
219    /*package*/ static void native_rMoveTo(int nPath, float dx, float dy) {
220        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
221        if (pathDelegate == null) {
222            return;
223        }
224
225        pathDelegate.rMoveTo(dx, dy);
226    }
227
228    @LayoutlibDelegate
229    /*package*/ static void native_lineTo(int nPath, float x, float y) {
230        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
231        if (pathDelegate == null) {
232            return;
233        }
234
235        pathDelegate.lineTo(x, y);
236    }
237
238    @LayoutlibDelegate
239    /*package*/ static void native_rLineTo(int nPath, float dx, float dy) {
240        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
241        if (pathDelegate == null) {
242            return;
243        }
244
245        pathDelegate.rLineTo(dx, dy);
246    }
247
248    @LayoutlibDelegate
249    /*package*/ static void native_quadTo(int nPath, float x1, float y1, float x2, float y2) {
250        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
251        if (pathDelegate == null) {
252            return;
253        }
254
255        pathDelegate.quadTo(x1, y1, x2, y2);
256    }
257
258    @LayoutlibDelegate
259    /*package*/ static void native_rQuadTo(int nPath, float dx1, float dy1, float dx2, float dy2) {
260        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
261        if (pathDelegate == null) {
262            return;
263        }
264
265        pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
266    }
267
268    @LayoutlibDelegate
269    /*package*/ static void native_cubicTo(int nPath, float x1, float y1,
270            float x2, float y2, float x3, float y3) {
271        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
272        if (pathDelegate == null) {
273            return;
274        }
275
276        pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
277    }
278
279    @LayoutlibDelegate
280    /*package*/ static void native_rCubicTo(int nPath, float x1, float y1,
281            float x2, float y2, float x3, float y3) {
282        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
283        if (pathDelegate == null) {
284            return;
285        }
286
287        pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
288    }
289
290    @LayoutlibDelegate
291    /*package*/ static void native_arcTo(int nPath, RectF oval,
292                    float startAngle, float sweepAngle, boolean forceMoveTo) {
293        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
294        if (pathDelegate == null) {
295            return;
296        }
297
298        pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo);
299    }
300
301    @LayoutlibDelegate
302    /*package*/ static void native_close(int nPath) {
303        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
304        if (pathDelegate == null) {
305            return;
306        }
307
308        pathDelegate.close();
309    }
310
311    @LayoutlibDelegate
312    /*package*/ static void native_addRect(int nPath, RectF rect, int dir) {
313        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
314        if (pathDelegate == null) {
315            return;
316        }
317
318        pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir);
319    }
320
321    @LayoutlibDelegate
322    /*package*/ static void native_addRect(int nPath,
323            float left, float top, float right, float bottom, int dir) {
324        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
325        if (pathDelegate == null) {
326            return;
327        }
328
329        pathDelegate.addRect(left, top, right, bottom, dir);
330    }
331
332    @LayoutlibDelegate
333    /*package*/ static void native_addOval(int nPath, RectF oval, int dir) {
334        // FIXME
335        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
336                "Path.addOval is not supported.", null, null /*data*/);
337    }
338
339    @LayoutlibDelegate
340    /*package*/ static void native_addCircle(int nPath, float x, float y, float radius, int dir) {
341        // FIXME
342        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
343                "Path.addCircle is not supported.", null, null /*data*/);
344    }
345
346    @LayoutlibDelegate
347    /*package*/ static void native_addArc(int nPath, RectF oval,
348            float startAngle, float sweepAngle) {
349        // FIXME
350        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
351                "Path.addArc is not supported.", null, null /*data*/);
352    }
353
354    @LayoutlibDelegate
355    /*package*/ static void native_addRoundRect(int nPath, RectF rect,
356            float rx, float ry, int dir) {
357        // FIXME
358        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
359                "Path.addRoundRect is not supported.", null, null /*data*/);
360    }
361
362    @LayoutlibDelegate
363    /*package*/ static void native_addRoundRect(int nPath, RectF r, float[] radii, int dir) {
364        // FIXME
365        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
366                "Path.addRoundRect is not supported.", null, null /*data*/);
367    }
368
369    @LayoutlibDelegate
370    /*package*/ static void native_addPath(int nPath, int src, float dx, float dy) {
371        // FIXME
372        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
373                "Path.addPath is not supported.", null, null /*data*/);
374    }
375
376    @LayoutlibDelegate
377    /*package*/ static void native_addPath(int nPath, int src) {
378        native_addPath(nPath, src, 0, 0);
379    }
380
381    @LayoutlibDelegate
382    /*package*/ static void native_addPath(int nPath, int src, int matrix) {
383        // FIXME
384        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
385                "Path.addPath is not supported.", null, null /*data*/);
386    }
387
388    @LayoutlibDelegate
389    /*package*/ static void native_offset(int nPath, float dx, float dy, int dst_path) {
390        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
391        if (pathDelegate == null) {
392            return;
393        }
394
395        // could be null if the int is 0;
396        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
397
398        pathDelegate.offset(dx, dy, dstDelegate);
399    }
400
401    @LayoutlibDelegate
402    /*package*/ static void native_offset(int nPath, float dx, float dy) {
403        native_offset(nPath, dx, dy, 0);
404    }
405
406    @LayoutlibDelegate
407    /*package*/ static void native_setLastPoint(int nPath, float dx, float dy) {
408        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
409        if (pathDelegate == null) {
410            return;
411        }
412
413        pathDelegate.mLastX = dx;
414        pathDelegate.mLastY = dy;
415    }
416
417    @LayoutlibDelegate
418    /*package*/ static void native_transform(int nPath, int matrix,
419                                                int dst_path) {
420        Path_Delegate pathDelegate = sManager.getDelegate(nPath);
421        if (pathDelegate == null) {
422            return;
423        }
424
425        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
426        if (matrixDelegate == null) {
427            return;
428        }
429
430        // this can be null if dst_path is 0
431        Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
432
433        pathDelegate.transform(matrixDelegate, dstDelegate);
434    }
435
436    @LayoutlibDelegate
437    /*package*/ static void native_transform(int nPath, int matrix) {
438        native_transform(nPath, matrix, 0);
439    }
440
441    @LayoutlibDelegate
442    /*package*/ static void finalizer(int nPath) {
443        sManager.removeDelegate(nPath);
444    }
445
446
447    // ---- Private helper methods ----
448
449    private void set(Path_Delegate delegate) {
450        mPath.reset();
451        setFillType(delegate.mFillType);
452        mPath.append(delegate.mPath, false /*connect*/);
453    }
454
455    private void setFillType(FillType fillType) {
456        mFillType = fillType;
457        mPath.setWindingRule(getWindingRule(fillType));
458    }
459
460    /**
461     * Returns the Java2D winding rules matching a given Android {@link FillType}.
462     * @param type the android fill type
463     * @return the matching java2d winding rule.
464     */
465    private static int getWindingRule(FillType type) {
466        switch (type) {
467            case WINDING:
468            case INVERSE_WINDING:
469                return GeneralPath.WIND_NON_ZERO;
470            case EVEN_ODD:
471            case INVERSE_EVEN_ODD:
472                return GeneralPath.WIND_EVEN_ODD;
473        }
474
475        assert false;
476        throw new IllegalArgumentException();
477    }
478
479    private static Direction getDirection(int direction) {
480        for (Direction d : Direction.values()) {
481            if (direction == d.nativeInt) {
482                return d;
483            }
484        }
485
486        assert false;
487        return null;
488    }
489
490    /**
491     * Returns whether the path is empty.
492     * @return true if the path is empty.
493     */
494    private boolean isEmpty() {
495        return mPath.getCurrentPoint() == null;
496    }
497
498    /**
499     * Fills the given {@link RectF} with the path bounds.
500     * @param bounds the RectF to be filled.
501     */
502    private void fillBounds(RectF bounds) {
503        Rectangle2D rect = mPath.getBounds2D();
504        bounds.left = (float)rect.getMinX();
505        bounds.right = (float)rect.getMaxX();
506        bounds.top = (float)rect.getMinY();
507        bounds.bottom = (float)rect.getMaxY();
508    }
509
510    /**
511     * Set the beginning of the next contour to the point (x,y).
512     *
513     * @param x The x-coordinate of the start of a new contour
514     * @param y The y-coordinate of the start of a new contour
515     */
516    private void moveTo(float x, float y) {
517        mPath.moveTo(mLastX = x, mLastY = y);
518    }
519
520    /**
521     * Set the beginning of the next contour relative to the last point on the
522     * previous contour. If there is no previous contour, this is treated the
523     * same as moveTo().
524     *
525     * @param dx The amount to add to the x-coordinate of the end of the
526     *           previous contour, to specify the start of a new contour
527     * @param dy The amount to add to the y-coordinate of the end of the
528     *           previous contour, to specify the start of a new contour
529     */
530    private void rMoveTo(float dx, float dy) {
531        dx += mLastX;
532        dy += mLastY;
533        mPath.moveTo(mLastX = dx, mLastY = dy);
534    }
535
536    /**
537     * Add a line from the last point to the specified point (x,y).
538     * If no moveTo() call has been made for this contour, the first point is
539     * automatically set to (0,0).
540     *
541     * @param x The x-coordinate of the end of a line
542     * @param y The y-coordinate of the end of a line
543     */
544    private void lineTo(float x, float y) {
545        mPath.lineTo(mLastX = x, mLastY = y);
546    }
547
548    /**
549     * Same as lineTo, but the coordinates are considered relative to the last
550     * point on this contour. If there is no previous point, then a moveTo(0,0)
551     * is inserted automatically.
552     *
553     * @param dx The amount to add to the x-coordinate of the previous point on
554     *           this contour, to specify a line
555     * @param dy The amount to add to the y-coordinate of the previous point on
556     *           this contour, to specify a line
557     */
558    private void rLineTo(float dx, float dy) {
559        if (isEmpty()) {
560            mPath.moveTo(mLastX = 0, mLastY = 0);
561        }
562        dx += mLastX;
563        dy += mLastY;
564        mPath.lineTo(mLastX = dx, mLastY = dy);
565    }
566
567    /**
568     * Add a quadratic bezier from the last point, approaching control point
569     * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
570     * this contour, the first point is automatically set to (0,0).
571     *
572     * @param x1 The x-coordinate of the control point on a quadratic curve
573     * @param y1 The y-coordinate of the control point on a quadratic curve
574     * @param x2 The x-coordinate of the end point on a quadratic curve
575     * @param y2 The y-coordinate of the end point on a quadratic curve
576     */
577    private void quadTo(float x1, float y1, float x2, float y2) {
578        mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
579    }
580
581    /**
582     * Same as quadTo, but the coordinates are considered relative to the last
583     * point on this contour. If there is no previous point, then a moveTo(0,0)
584     * is inserted automatically.
585     *
586     * @param dx1 The amount to add to the x-coordinate of the last point on
587     *            this contour, for the control point of a quadratic curve
588     * @param dy1 The amount to add to the y-coordinate of the last point on
589     *            this contour, for the control point of a quadratic curve
590     * @param dx2 The amount to add to the x-coordinate of the last point on
591     *            this contour, for the end point of a quadratic curve
592     * @param dy2 The amount to add to the y-coordinate of the last point on
593     *            this contour, for the end point of a quadratic curve
594     */
595    private void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
596        if (isEmpty()) {
597            mPath.moveTo(mLastX = 0, mLastY = 0);
598        }
599        dx1 += mLastX;
600        dy1 += mLastY;
601        dx2 += mLastX;
602        dy2 += mLastY;
603        mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
604    }
605
606    /**
607     * Add a cubic bezier from the last point, approaching control points
608     * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
609     * made for this contour, the first point is automatically set to (0,0).
610     *
611     * @param x1 The x-coordinate of the 1st control point on a cubic curve
612     * @param y1 The y-coordinate of the 1st control point on a cubic curve
613     * @param x2 The x-coordinate of the 2nd control point on a cubic curve
614     * @param y2 The y-coordinate of the 2nd control point on a cubic curve
615     * @param x3 The x-coordinate of the end point on a cubic curve
616     * @param y3 The y-coordinate of the end point on a cubic curve
617     */
618    private void cubicTo(float x1, float y1, float x2, float y2,
619                        float x3, float y3) {
620        mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
621    }
622
623    /**
624     * Same as cubicTo, but the coordinates are considered relative to the
625     * current point on this contour. If there is no previous point, then a
626     * moveTo(0,0) is inserted automatically.
627     */
628    private void rCubicTo(float dx1, float dy1, float dx2, float dy2,
629                         float dx3, float dy3) {
630        if (isEmpty()) {
631            mPath.moveTo(mLastX = 0, mLastY = 0);
632        }
633        dx1 += mLastX;
634        dy1 += mLastY;
635        dx2 += mLastX;
636        dy2 += mLastY;
637        dx3 += mLastX;
638        dy3 += mLastY;
639        mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
640    }
641
642    /**
643     * Append the specified arc to the path as a new contour. If the start of
644     * the path is different from the path's current last point, then an
645     * automatic lineTo() is added to connect the current contour to the
646     * start of the arc. However, if the path is empty, then we call moveTo()
647     * with the first point of the arc. The sweep angle is tread mod 360.
648     *
649     * @param oval        The bounds of oval defining shape and size of the arc
650     * @param startAngle  Starting angle (in degrees) where the arc begins
651     * @param sweepAngle  Sweep angle (in degrees) measured clockwise, treated
652     *                    mod 360.
653     * @param forceMoveTo If true, always begin a new contour with the arc
654     */
655    private void arcTo(RectF oval, float startAngle, float sweepAngle,
656                      boolean forceMoveTo) {
657        Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), startAngle,
658                sweepAngle, Arc2D.OPEN);
659        mPath.append(arc, true /*connect*/);
660
661        resetLastPointFromPath();
662    }
663
664    /**
665     * Close the current contour. If the current point is not equal to the
666     * first point of the contour, a line segment is automatically added.
667     */
668    private void close() {
669        mPath.closePath();
670    }
671
672    private void resetLastPointFromPath() {
673        Point2D last = mPath.getCurrentPoint();
674        mLastX = (float) last.getX();
675        mLastY = (float) last.getY();
676    }
677
678    /**
679     * Add a closed rectangle contour to the path
680     *
681     * @param left   The left side of a rectangle to add to the path
682     * @param top    The top of a rectangle to add to the path
683     * @param right  The right side of a rectangle to add to the path
684     * @param bottom The bottom of a rectangle to add to the path
685     * @param dir    The direction to wind the rectangle's contour
686     */
687    private void addRect(float left, float top, float right, float bottom,
688                        int dir) {
689        moveTo(left, top);
690
691        Direction direction = getDirection(dir);
692
693        switch (direction) {
694            case CW:
695                lineTo(right, top);
696                lineTo(right, bottom);
697                lineTo(left, bottom);
698                break;
699            case CCW:
700                lineTo(left, bottom);
701                lineTo(right, bottom);
702                lineTo(right, top);
703                break;
704        }
705
706        close();
707
708        resetLastPointFromPath();
709    }
710
711    /**
712     * Offset the path by (dx,dy), returning true on success
713     *
714     * @param dx  The amount in the X direction to offset the entire path
715     * @param dy  The amount in the Y direction to offset the entire path
716     * @param dst The translated path is written here. If this is null, then
717     *            the original path is modified.
718     */
719    public void offset(float dx, float dy, Path_Delegate dst) {
720        GeneralPath newPath = new GeneralPath();
721
722        PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
723
724        newPath.append(iterator, false /*connect*/);
725
726        if (dst != null) {
727            dst.mPath = newPath;
728        } else {
729            mPath = newPath;
730        }
731    }
732
733    /**
734     * Transform the points in this path by matrix, and write the answer
735     * into dst. If dst is null, then the the original path is modified.
736     *
737     * @param matrix The matrix to apply to the path
738     * @param dst    The transformed path is written here. If dst is null,
739     *               then the the original path is modified
740     */
741    public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
742        if (matrix.hasPerspective()) {
743            assert false;
744            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
745                    "android.graphics.Path#transform() only " +
746                    "supports affine transformations.", null, null /*data*/);
747        }
748
749        GeneralPath newPath = new GeneralPath();
750
751        PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
752
753        newPath.append(iterator, false /*connect*/);
754
755        if (dst != null) {
756            dst.mPath = newPath;
757        } else {
758            mPath = newPath;
759        }
760    }
761}
762