VectorDrawable.java revision b3c56086d802ae28888dd97ba1f49bd6cee0b673
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14
15package android.graphics.drawable;
16
17import android.content.res.ColorStateList;
18import android.content.res.Resources;
19import android.content.res.Resources.Theme;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.ColorFilter;
24import android.graphics.Matrix;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.PathMeasure;
28import android.graphics.PixelFormat;
29import android.graphics.PorterDuffColorFilter;
30import android.graphics.Rect;
31import android.graphics.Region;
32import android.graphics.PorterDuff.Mode;
33import android.util.ArrayMap;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.util.Xml;
37
38import com.android.internal.R;
39
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlPullParserFactory;
43
44import java.io.IOException;
45import java.util.ArrayList;
46import java.util.Arrays;
47import java.util.Stack;
48
49/**
50 * This lets you create a drawable based on an XML vector graphic It can be
51 * defined in an XML file with the <code>&lt;vector></code> element.
52 * <p/>
53 * The vector drawable has the following elements:
54 * <p/>
55 * <dl>
56 * <dt><code>&lt;vector></code></dt>
57 * <dd>Used to defined a vector drawable</dd>
58 * <dt><code>&lt;size></code></dt>
59 * <dd>Used to defined the intrinsic Width Height size of the drawable using
60 * <code>android:width</code> and <code>android:height</code></dd>
61 * <dt><code>&lt;viewport></code></dt>
62 * <dd>Used to defined the size of the virtual canvas the paths are drawn on.
63 * The size is defined using the attributes <code>android:viewportHeight</code>
64 * <code>android:viewportWidth</code></dd>
65 * <dt><code>&lt;group></code></dt>
66 * <dd>Defines a group of paths or subgroups, plus transformation information.
67 * The transformations are defined in the same coordinates as the viewport.
68 * And the transformations are applied in the order of scale, rotate then translate. </dd>
69 * <dt><code>android:rotation</code>
70 * <dd>The degrees of rotation of the group.</dd></dt>
71 * <dt><code>android:pivotX</code>
72 * <dd>The X coordinate of the pivot for the scale and rotation of the group</dd></dt>
73 * <dt><code>android:pivotY</code>
74 * <dd>The Y coordinate of the pivot for the scale and rotation of the group</dd></dt>
75 * <dt><code>android:scaleX</code>
76 * <dd>The amount of scale on the X Coordinate</dd></dt>
77 * <dt><code>android:scaleY</code>
78 * <dd>The amount of scale on the Y coordinate</dd></dt>
79 * <dt><code>android:translateX</code>
80 * <dd>The amount of translation on the X coordinate</dd></dt>
81 * <dt><code>android:translateY</code>
82 * <dd>The amount of translation on the Y coordinate</dd></dt>
83 * <dt><code>&lt;path></code></dt>
84 * <dd>Defines paths to be drawn.
85 * <dl>
86 * <dt><code>android:name</code>
87 * <dd>Defines the name of the path.</dd></dt>
88 * <dt><code>android:pathData</code>
89 * <dd>Defines path string. This is using exactly same format as "d" attribute
90 * in the SVG's path data</dd></dt>
91 * <dt><code>android:fill</code>
92 * <dd>Defines the color to fill the path (none if not present).</dd></dt>
93 * <dt><code>android:stroke</code>
94 * <dd>Defines the color to draw the path outline (none if not present).</dd>
95 * </dt>
96 * <dt><code>android:strokeWidth</code>
97 * <dd>The width a path stroke</dd></dt>
98 * <dt><code>android:strokeOpacity</code>
99 * <dd>The opacity of a path stroke</dd></dt>
100 * <dt><code>android:fillOpacity</code>
101 * <dd>The opacity to fill the path with</dd></dt>
102 * <dt><code>android:trimPathStart</code>
103 * <dd>The fraction of the path to trim from the start from 0 to 1</dd></dt>
104 * <dt><code>android:trimPathEnd</code>
105 * <dd>The fraction of the path to trim from the end from 0 to 1</dd></dt>
106 * <dt><code>android:trimPathOffset</code>
107 * <dd>Shift trim region (allows showed region to include the start and end)
108 * from 0 to 1</dd></dt>
109 * <dt><code>android:clipToPath</code>
110 * <dd>Path will set the clip path</dd></dt>
111 * <dt><code>android:strokeLineCap</code>
112 * <dd>Sets the linecap for a stroked path: butt, round, square</dd></dt>
113 * <dt><code>android:strokeLineJoin</code>
114 * <dd>Sets the lineJoin for a stroked path: miter,round,bevel</dd></dt>
115 * <dt><code>android:strokeMiterLimit</code>
116 * <dd>Sets the Miter limit for a stroked path</dd></dt>
117 * </dl>
118 * </dd>
119 */
120public class VectorDrawable extends Drawable {
121    private static final String LOGTAG = VectorDrawable.class.getSimpleName();
122
123    private static final String SHAPE_SIZE = "size";
124    private static final String SHAPE_VIEWPORT = "viewport";
125    private static final String SHAPE_GROUP = "group";
126    private static final String SHAPE_PATH = "path";
127    private static final String SHAPE_VECTOR = "vector";
128
129    private static final int LINECAP_BUTT = 0;
130    private static final int LINECAP_ROUND = 1;
131    private static final int LINECAP_SQUARE = 2;
132
133    private static final int LINEJOIN_MITER = 0;
134    private static final int LINEJOIN_ROUND = 1;
135    private static final int LINEJOIN_BEVEL = 2;
136
137    private static final boolean DBG_VECTOR_DRAWABLE = false;
138
139    private final VectorDrawableState mVectorState;
140
141    private final ArrayMap<String, Object> mVGTargetsMap = new ArrayMap<String, Object>();
142
143    private PorterDuffColorFilter mTintFilter;
144
145    public VectorDrawable() {
146        mVectorState = new VectorDrawableState(null);
147    }
148
149    private VectorDrawable(VectorDrawableState state, Resources res, Theme theme) {
150        mVectorState = new VectorDrawableState(state);
151
152        if (theme != null && canApplyTheme()) {
153            applyTheme(theme);
154        }
155
156        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
157        mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
158    }
159
160    Object getTargetByName(String name) {
161        return mVGTargetsMap.get(name);
162    }
163
164    @Override
165    public ConstantState getConstantState() {
166        return mVectorState;
167    }
168
169    @Override
170    public void draw(Canvas canvas) {
171        final int saveCount = canvas.save();
172        final Rect bounds = getBounds();
173        canvas.translate(bounds.left, bounds.top);
174        mVectorState.mVPathRenderer.draw(canvas, bounds.width(), bounds.height());
175        canvas.restoreToCount(saveCount);
176    }
177
178    @Override
179    public int getAlpha() {
180        return mVectorState.mVPathRenderer.getRootAlpha();
181    }
182
183    @Override
184    public void setAlpha(int alpha) {
185        if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
186            mVectorState.mVPathRenderer.setRootAlpha(alpha);
187            invalidateSelf();
188        }
189    }
190
191    @Override
192    public void setColorFilter(ColorFilter colorFilter) {
193        final VectorDrawableState state = mVectorState;
194        if (colorFilter != null) {
195            // Color filter overrides tint.
196            mTintFilter = null;
197        } else if (state.mTint != null && state.mTintMode != null) {
198            // Restore the tint filter, if we need one.
199            final int color = state.mTint.getColorForState(getState(), Color.TRANSPARENT);
200            mTintFilter = new PorterDuffColorFilter(color, state.mTintMode);
201            colorFilter = mTintFilter;
202        }
203
204        state.mVPathRenderer.setColorFilter(colorFilter);
205        invalidateSelf();
206    }
207
208    @Override
209    public void setTint(ColorStateList tint, Mode tintMode) {
210        final VectorDrawableState state = mVectorState;
211        if (state.mTint != tint || state.mTintMode != tintMode) {
212            state.mTint = tint;
213            state.mTintMode = tintMode;
214
215            mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
216            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
217            invalidateSelf();
218        }
219    }
220
221    @Override
222    protected boolean onStateChange(int[] stateSet) {
223        final VectorDrawableState state = mVectorState;
224        if (state.mTint != null && state.mTintMode != null) {
225            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
226            mVectorState.mVPathRenderer.setColorFilter(mTintFilter);
227            return true;
228        }
229        return false;
230    }
231
232    @Override
233    public int getOpacity() {
234        return PixelFormat.TRANSLUCENT;
235    }
236
237    /**
238     * Sets padding for this shape, defined by a Rect object. Define the padding
239     * in the Rect object as: left, top, right, bottom.
240     */
241    public void setPadding(Rect padding) {
242        setPadding(padding.left, padding.top, padding.right, padding.bottom);
243    }
244
245    /**
246     * Sets padding for the shape.
247     *
248     * @param left padding for the left side (in pixels)
249     * @param top padding for the top (in pixels)
250     * @param right padding for the right side (in pixels)
251     * @param bottom padding for the bottom (in pixels)
252     */
253    public void setPadding(int left, int top, int right, int bottom) {
254        if ((left | top | right | bottom) == 0) {
255            mVectorState.mPadding = null;
256        } else {
257            if (mVectorState.mPadding == null) {
258                mVectorState.mPadding = new Rect();
259            }
260            mVectorState.mPadding.set(left, top, right, bottom);
261        }
262        invalidateSelf();
263    }
264
265    @Override
266    public int getIntrinsicWidth() {
267        return (int) mVectorState.mVPathRenderer.mBaseWidth;
268    }
269
270    @Override
271    public int getIntrinsicHeight() {
272        return (int) mVectorState.mVPathRenderer.mBaseHeight;
273    }
274
275    @Override
276    public boolean getPadding(Rect padding) {
277        if (mVectorState.mPadding != null) {
278            padding.set(mVectorState.mPadding);
279            return true;
280        } else {
281            return super.getPadding(padding);
282        }
283    }
284
285    @Override
286    public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
287            throws XmlPullParserException, IOException {
288        final VPathRenderer p = inflateInternal(res, parser, attrs, theme);
289        setPathRenderer(p);
290    }
291
292    @Override
293    public boolean canApplyTheme() {
294        return super.canApplyTheme() || mVectorState != null && mVectorState.canApplyTheme();
295    }
296
297    @Override
298    public void applyTheme(Theme t) {
299        super.applyTheme(t);
300
301        final VectorDrawableState state = mVectorState;
302        final VPathRenderer path = state.mVPathRenderer;
303        if (path != null && path.canApplyTheme()) {
304            path.applyTheme(t);
305        }
306    }
307
308    /** @hide */
309    public static VectorDrawable create(Resources resources, int rid) {
310        try {
311            final XmlPullParser xpp = resources.getXml(rid);
312            final AttributeSet attrs = Xml.asAttributeSet(xpp);
313            final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
314            factory.setNamespaceAware(true);
315
316            final VectorDrawable drawable = new VectorDrawable();
317            drawable.inflate(resources, xpp, attrs);
318
319            return drawable;
320        } catch (XmlPullParserException e) {
321            Log.e(LOGTAG, "parser error", e);
322        } catch (IOException e) {
323            Log.e(LOGTAG, "parser error", e);
324        }
325        return null;
326    }
327
328    private static int applyAlpha(int color, float alpha) {
329        int alphaBytes = Color.alpha(color);
330        color &= 0x00FFFFFF;
331        color |= ((int) (alphaBytes * alpha)) << 24;
332        return color;
333    }
334
335    private VPathRenderer inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
336            Theme theme) throws XmlPullParserException, IOException {
337        final VPathRenderer pathRenderer = new VPathRenderer();
338
339        boolean noSizeTag = true;
340        boolean noViewportTag = true;
341        boolean noGroupTag = true;
342        boolean noPathTag = true;
343
344        // Use a stack to help to build the group tree.
345        // The top of the stack is always the current group.
346        final Stack<VGroup> groupStack = new Stack<VGroup>();
347        groupStack.push(pathRenderer.mRootGroup);
348
349        int eventType = parser.getEventType();
350        while (eventType != XmlPullParser.END_DOCUMENT) {
351            if (eventType == XmlPullParser.START_TAG) {
352                final String tagName = parser.getName();
353                final VGroup currentGroup = groupStack.peek();
354
355                if (SHAPE_PATH.equals(tagName)) {
356                    final VPath path = new VPath();
357                    path.inflate(res, attrs, theme);
358                    currentGroup.add(path);
359                    if (path.getPathName() != null) {
360                        mVGTargetsMap.put(path.getPathName(), path);
361                    }
362                    noPathTag = false;
363                } else if (SHAPE_SIZE.equals(tagName)) {
364                    pathRenderer.parseSize(res, attrs);
365                    noSizeTag = false;
366                } else if (SHAPE_VIEWPORT.equals(tagName)) {
367                    pathRenderer.parseViewport(res, attrs);
368                    noViewportTag = false;
369                } else if (SHAPE_GROUP.equals(tagName)) {
370                    VGroup newChildGroup = new VGroup();
371                    newChildGroup.inflate(res, attrs, theme);
372                    currentGroup.mChildGroupList.add(newChildGroup);
373                    groupStack.push(newChildGroup);
374                    if (newChildGroup.getGroupName() != null) {
375                        mVGTargetsMap.put(newChildGroup.getGroupName(), newChildGroup);
376                    }
377                    noGroupTag = false;
378                }
379            } else if (eventType == XmlPullParser.END_TAG) {
380                final String tagName = parser.getName();
381                if (SHAPE_GROUP.equals(tagName)) {
382                    groupStack.pop();
383                }
384            }
385            eventType = parser.next();
386        }
387
388        // Print the tree out for debug.
389        if (DBG_VECTOR_DRAWABLE) {
390            printGroupTree(pathRenderer.mRootGroup, 0);
391        }
392
393        if (noSizeTag || noViewportTag || noPathTag) {
394            final StringBuffer tag = new StringBuffer();
395
396            if (noSizeTag) {
397                tag.append(SHAPE_SIZE);
398            }
399
400            if (noViewportTag) {
401                if (tag.length() > 0) {
402                    tag.append(" & ");
403                }
404                tag.append(SHAPE_SIZE);
405            }
406
407            if (noPathTag) {
408                if (tag.length() > 0) {
409                    tag.append(" or ");
410                }
411                tag.append(SHAPE_PATH);
412            }
413
414            throw new XmlPullParserException("no " + tag + " defined");
415        }
416
417        return pathRenderer;
418    }
419
420    private void printGroupTree(VGroup currentGroup, int level) {
421        String indent = "";
422        for (int i = 0 ; i < level ; i++) {
423            indent += "    ";
424        }
425        // Print the current node
426        Log.v(LOGTAG, indent + "current group is :" +  currentGroup.getGroupName()
427                + " rotation is " + currentGroup.mRotate);
428        Log.v(LOGTAG, indent + "matrix is :" +  currentGroup.getLocalMatrix().toString());
429        // Then print all the children
430        for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
431            printGroupTree(currentGroup.mChildGroupList.get(i), level + 1);
432        }
433    }
434
435    private void setPathRenderer(VPathRenderer pathRenderer) {
436        mVectorState.mVPathRenderer = pathRenderer;
437    }
438
439    private static class VectorDrawableState extends ConstantState {
440        int mChangingConfigurations;
441        VPathRenderer mVPathRenderer;
442        Rect mPadding;
443        ColorStateList mTint;
444        Mode mTintMode;
445
446        public VectorDrawableState(VectorDrawableState copy) {
447            if (copy != null) {
448                mChangingConfigurations = copy.mChangingConfigurations;
449                // TODO: Make sure the constant state are handled correctly.
450                mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
451                mPadding = new Rect(copy.mPadding);
452                mTint = copy.mTint;
453                mTintMode = copy.mTintMode;
454            }
455        }
456
457        @Override
458        public Drawable newDrawable() {
459            return new VectorDrawable(this, null, null);
460        }
461
462        @Override
463        public Drawable newDrawable(Resources res) {
464            return new VectorDrawable(this, res, null);
465        }
466
467        @Override
468        public Drawable newDrawable(Resources res, Theme theme) {
469            return new VectorDrawable(this, res, theme);
470        }
471
472        @Override
473        public int getChangingConfigurations() {
474            return mChangingConfigurations;
475        }
476    }
477
478    private static class VPathRenderer {
479        /* Right now the internal data structure is organized as a tree.
480         * Each node can be a group node, or a path.
481         * A group node can have groups or paths as children, but a path node has
482         * no children.
483         * One example can be:
484         *                 Root Group
485         *                /    |     \
486         *           Group    Path    Group
487         *          /     \             |
488         *         Path   Path         Path
489         *
490         */
491        private final VGroup mRootGroup;
492
493        private final Path mPath = new Path();
494        private final Path mRenderPath = new Path();
495        private static final Matrix IDENTITY_MATRIX = new Matrix();
496
497        private Paint mStrokePaint;
498        private Paint mFillPaint;
499        private ColorFilter mColorFilter;
500        private PathMeasure mPathMeasure;
501
502        private float mBaseWidth = 0;
503        private float mBaseHeight = 0;
504        private float mViewportWidth = 0;
505        private float mViewportHeight = 0;
506        private int mRootAlpha = 0xFF;
507
508        private final Matrix mFinalPathMatrix = new Matrix();
509
510        public VPathRenderer() {
511            mRootGroup = new VGroup();
512        }
513
514        public void setRootAlpha(int alpha) {
515            mRootAlpha = alpha;
516        }
517
518        public int getRootAlpha() {
519            return mRootAlpha;
520        }
521
522        public VPathRenderer(VPathRenderer copy) {
523            mRootGroup = copy.mRootGroup;
524            mBaseWidth = copy.mBaseWidth;
525            mBaseHeight = copy.mBaseHeight;
526            mViewportWidth = copy.mViewportHeight;
527            mViewportHeight = copy.mViewportHeight;
528        }
529
530        public boolean canApplyTheme() {
531            // If one of the paths can apply theme, then return true;
532            return recursiveCanApplyTheme(mRootGroup);
533        }
534
535        private boolean recursiveCanApplyTheme(VGroup currentGroup) {
536            // We can do a tree traverse here, if there is one path return true,
537            // then we return true for the whole tree.
538            final ArrayList<VPath> paths = currentGroup.mPathList;
539            for (int j = paths.size() - 1; j >= 0; j--) {
540                final VPath path = paths.get(j);
541                if (path.canApplyTheme()) {
542                    return true;
543                }
544            }
545
546            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
547
548            for (int i = 0; i < childGroups.size(); i++) {
549                VGroup childGroup = childGroups.get(i);
550                if (childGroup.canApplyTheme()
551                        || recursiveCanApplyTheme(childGroup)) {
552                    return true;
553                }
554            }
555            return false;
556        }
557
558        public void applyTheme(Theme t) {
559            // Apply theme to every path of the tree.
560            recursiveApplyTheme(mRootGroup, t);
561        }
562
563        private void recursiveApplyTheme(VGroup currentGroup, Theme t) {
564            // We can do a tree traverse here, apply theme to all paths which
565            // can apply theme.
566            final ArrayList<VPath> paths = currentGroup.mPathList;
567            for (int j = paths.size() - 1; j >= 0; j--) {
568                final VPath path = paths.get(j);
569                if (path.canApplyTheme()) {
570                    path.applyTheme(t);
571                }
572            }
573
574            final ArrayList<VGroup> childGroups = currentGroup.mChildGroupList;
575
576            for (int i = 0; i < childGroups.size(); i++) {
577                VGroup childGroup = childGroups.get(i);
578                if (childGroup.canApplyTheme()) {
579                    childGroup.applyTheme(t);
580                }
581                recursiveApplyTheme(childGroup, t);
582            }
583
584        }
585
586        public void setColorFilter(ColorFilter colorFilter) {
587            mColorFilter = colorFilter;
588
589            if (mFillPaint != null) {
590                mFillPaint.setColorFilter(colorFilter);
591            }
592
593            if (mStrokePaint != null) {
594                mStrokePaint.setColorFilter(colorFilter);
595            }
596
597        }
598
599        private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
600                float currentAlpha, Canvas canvas, int w, int h) {
601            // Calculate current group's matrix by preConcat the parent's and
602            // and the current one on the top of the stack.
603            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
604            // Mi the local matrix at level i of the group tree.
605            currentGroup.mStackedMatrix.set(currentMatrix);
606
607            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
608
609            float stackedAlpha = currentAlpha * currentGroup.mGroupAlpha;
610            drawPath(currentGroup, stackedAlpha, canvas, w, h);
611            // Draw the group tree in post order.
612            for (int i = 0 ; i < currentGroup.mChildGroupList.size(); i++) {
613                drawGroupTree(currentGroup.mChildGroupList.get(i),
614                        currentGroup.mStackedMatrix, stackedAlpha, canvas, w, h);
615            }
616        }
617
618        public void draw(Canvas canvas, int w, int h) {
619            // Travese the tree in pre-order to draw.
620            drawGroupTree(mRootGroup, IDENTITY_MATRIX, ((float) mRootAlpha) / 0xFF, canvas, w, h);
621        }
622
623        private void drawPath(VGroup vGroup, float stackedAlpha, Canvas canvas, int w, int h) {
624            final float scale = Math.min(h / mViewportHeight, w / mViewportWidth);
625
626            mFinalPathMatrix.set(vGroup.mStackedMatrix);
627            mFinalPathMatrix.postScale(scale, scale, mViewportWidth / 2f, mViewportHeight / 2f);
628            mFinalPathMatrix.postTranslate(w / 2f - mViewportWidth / 2f, h / 2f - mViewportHeight / 2f);
629
630            ArrayList<VPath> paths = vGroup.getPaths();
631            for (int i = 0; i < paths.size(); i++) {
632                VPath vPath = paths.get(i);
633                vPath.toPath(mPath);
634                final Path path = mPath;
635
636                if (vPath.mTrimPathStart != 0.0f || vPath.mTrimPathEnd != 1.0f) {
637                    float start = (vPath.mTrimPathStart + vPath.mTrimPathOffset) % 1.0f;
638                    float end = (vPath.mTrimPathEnd + vPath.mTrimPathOffset) % 1.0f;
639
640                    if (mPathMeasure == null) {
641                        mPathMeasure = new PathMeasure();
642                    }
643                    mPathMeasure.setPath(mPath, false);
644
645                    float len = mPathMeasure.getLength();
646                    start = start * len;
647                    end = end * len;
648                    path.reset();
649                    if (start > end) {
650                        mPathMeasure.getSegment(start, len, path, true);
651                        mPathMeasure.getSegment(0f, end, path, true);
652                    } else {
653                        mPathMeasure.getSegment(start, end, path, true);
654                    }
655                    path.rLineTo(0, 0); // fix bug in measure
656                }
657
658                mRenderPath.reset();
659
660                mRenderPath.addPath(path, mFinalPathMatrix);
661
662                if (vPath.mClip) {
663                    canvas.clipPath(mRenderPath, Region.Op.REPLACE);
664                } else {
665                   if (vPath.mFillColor != 0) {
666                        if (mFillPaint == null) {
667                            mFillPaint = new Paint();
668                            mFillPaint.setColorFilter(mColorFilter);
669                            mFillPaint.setStyle(Paint.Style.FILL);
670                            mFillPaint.setAntiAlias(true);
671                        }
672                        mFillPaint.setColor(applyAlpha(vPath.mFillColor, stackedAlpha));
673                        canvas.drawPath(mRenderPath, mFillPaint);
674                    }
675
676                    if (vPath.mStrokeColor != 0) {
677                        if (mStrokePaint == null) {
678                            mStrokePaint = new Paint();
679                            mStrokePaint.setColorFilter(mColorFilter);
680                            mStrokePaint.setStyle(Paint.Style.STROKE);
681                            mStrokePaint.setAntiAlias(true);
682                        }
683
684                        final Paint strokePaint = mStrokePaint;
685                        if (vPath.mStrokeLineJoin != null) {
686                            strokePaint.setStrokeJoin(vPath.mStrokeLineJoin);
687                        }
688
689                        if (vPath.mStrokeLineCap != null) {
690                            strokePaint.setStrokeCap(vPath.mStrokeLineCap);
691                        }
692
693                        strokePaint.setStrokeMiter(vPath.mStrokeMiterlimit * scale);
694
695                        strokePaint.setColor(applyAlpha(vPath.mStrokeColor, stackedAlpha));
696                        strokePaint.setStrokeWidth(vPath.mStrokeWidth * scale);
697                        canvas.drawPath(mRenderPath, strokePaint);
698                    }
699                }
700            }
701        }
702
703        private void parseViewport(Resources r, AttributeSet attrs)
704                throws XmlPullParserException {
705            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableViewport);
706            mViewportWidth = a.getFloat(R.styleable.VectorDrawableViewport_viewportWidth, mViewportWidth);
707            mViewportHeight = a.getFloat(R.styleable.VectorDrawableViewport_viewportHeight, mViewportHeight);
708
709            if (mViewportWidth <= 0) {
710                throw new XmlPullParserException(a.getPositionDescription() +
711                        "<viewport> tag requires viewportWidth > 0");
712            } else if (mViewportHeight <= 0) {
713                throw new XmlPullParserException(a.getPositionDescription() +
714                        "<viewport> tag requires viewportHeight > 0");
715            }
716
717            a.recycle();
718        }
719
720        private void parseSize(Resources r, AttributeSet attrs)
721                throws XmlPullParserException  {
722            final TypedArray a = r.obtainAttributes(attrs, R.styleable.VectorDrawableSize);
723            mBaseWidth = a.getDimension(R.styleable.VectorDrawableSize_width, mBaseWidth);
724            mBaseHeight = a.getDimension(R.styleable.VectorDrawableSize_height, mBaseHeight);
725
726            if (mBaseWidth <= 0) {
727                throw new XmlPullParserException(a.getPositionDescription() +
728                        "<size> tag requires width > 0");
729            } else if (mBaseHeight <= 0) {
730                throw new XmlPullParserException(a.getPositionDescription() +
731                        "<size> tag requires height > 0");
732            }
733
734            a.recycle();
735        }
736
737    }
738
739    static class VGroup {
740        private final ArrayList<VPath> mPathList = new ArrayList<VPath>();
741        private final ArrayList<VGroup> mChildGroupList = new ArrayList<VGroup>();
742
743        private float mRotate = 0;
744        private float mPivotX = 0;
745        private float mPivotY = 0;
746        private float mScaleX = 1;
747        private float mScaleY = 1;
748        private float mTranslateX = 0;
749        private float mTranslateY = 0;
750        private float mGroupAlpha = 1;
751
752        // mLocalMatrix is parsed from the XML.
753        private final Matrix mLocalMatrix = new Matrix();
754        // mStackedMatrix is only used when drawing, it combines all the
755        // parents' local matrices with the current one.
756        private final Matrix mStackedMatrix = new Matrix();
757
758        private int[] mThemeAttrs;
759
760        private String mGroupName = null;
761
762        /* Getter and Setter */
763        public float getRotation() {
764            return mRotate;
765        }
766
767        public void setRotation(float rotation) {
768            if (rotation != mRotate) {
769                mRotate = rotation;
770                updateLocalMatrix();
771            }
772        }
773
774        public float getPivotX() {
775            return mPivotX;
776        }
777
778        public void setPivotX(float pivotX) {
779            if (pivotX != mPivotX) {
780                mPivotX = pivotX;
781                updateLocalMatrix();
782            }
783        }
784
785        public float getPivotY() {
786            return mPivotY;
787        }
788
789        public void setPivotY(float pivotY) {
790            if (pivotY != mPivotY) {
791                mPivotY = pivotY;
792                updateLocalMatrix();
793            }
794        }
795
796        public float getScaleX() {
797            return mScaleX;
798        }
799
800        public void setScaleX(float scaleX) {
801            if (scaleX != mScaleX) {
802                mScaleX = scaleX;
803                updateLocalMatrix();
804            }
805        }
806
807        public float getScaleY() {
808            return mScaleY;
809        }
810
811        public void setScaleY(float scaleY) {
812            if (scaleY != mScaleY) {
813                mScaleY = scaleY;
814                updateLocalMatrix();
815            }
816        }
817
818        public float getTranslateX() {
819            return mTranslateX;
820        }
821
822        public void setTranslateX(float translateX) {
823            if (translateX != mTranslateX) {
824                mTranslateX = translateX;
825                updateLocalMatrix();
826            }
827        }
828
829        public float getTranslateY() {
830            return mTranslateY;
831        }
832
833        public void setTranslateY(float translateY) {
834            if (translateY != mTranslateY) {
835                mTranslateY = translateY;
836                updateLocalMatrix();
837            }
838        }
839
840        public float getAlpha() {
841            return mGroupAlpha;
842        }
843
844        public void setAlpha(float groupAlpha) {
845            if (groupAlpha != mGroupAlpha) {
846                mGroupAlpha = groupAlpha;
847            }
848        }
849
850        public String getGroupName() {
851            return mGroupName;
852        }
853
854        public Matrix getLocalMatrix() {
855            return mLocalMatrix;
856        }
857
858        public void add(VPath path) {
859            mPathList.add(path);
860         }
861
862        public boolean canApplyTheme() {
863            return mThemeAttrs != null;
864        }
865
866        public void applyTheme(Theme t) {
867            if (mThemeAttrs == null) {
868                return;
869            }
870
871            final TypedArray a = t.resolveAttributes(
872                    mThemeAttrs, R.styleable.VectorDrawablePath);
873
874            mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
875            mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
876            mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
877            mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
878            mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
879            mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
880            mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
881            mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
882            updateLocalMatrix();
883            if (a.hasValue(R.styleable.VectorDrawableGroup_name)) {
884                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
885            }
886            a.recycle();
887        }
888
889        public void inflate(Resources res, AttributeSet attrs, Theme theme) {
890            final TypedArray a = obtainAttributes(res, theme, attrs, R.styleable.VectorDrawableGroup);
891            final int[] themeAttrs = a.extractThemeAttrs();
892
893            mThemeAttrs = themeAttrs;
894            // NOTE: The set of attributes loaded here MUST match the
895            // set of attributes loaded in applyTheme.
896
897            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_rotation] == 0) {
898                mRotate = a.getFloat(R.styleable.VectorDrawableGroup_rotation, mRotate);
899            }
900
901            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotX] == 0) {
902                mPivotX = a.getFloat(R.styleable.VectorDrawableGroup_pivotX, mPivotX);
903            }
904
905            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_pivotY] == 0) {
906                mPivotY = a.getFloat(R.styleable.VectorDrawableGroup_pivotY, mPivotY);
907            }
908
909            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleX] == 0) {
910                mScaleX = a.getFloat(R.styleable.VectorDrawableGroup_scaleX, mScaleX);
911            }
912
913            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_scaleY] == 0) {
914                mScaleY = a.getFloat(R.styleable.VectorDrawableGroup_scaleY, mScaleY);
915            }
916
917            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateX] == 0) {
918                mTranslateX = a.getFloat(R.styleable.VectorDrawableGroup_translateX, mTranslateX);
919            }
920
921            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_translateY] == 0) {
922                mTranslateY = a.getFloat(R.styleable.VectorDrawableGroup_translateY, mTranslateY);
923            }
924
925            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_name] == 0) {
926                mGroupName = a.getString(R.styleable.VectorDrawableGroup_name);
927            }
928
929            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawableGroup_alpha] == 0) {
930                mGroupAlpha = a.getFloat(R.styleable.VectorDrawableGroup_alpha, mGroupAlpha);
931            }
932
933            updateLocalMatrix();
934            a.recycle();
935        }
936
937        private void updateLocalMatrix() {
938            // The order we apply is the same as the
939            // RenderNode.cpp::applyViewPropertyTransforms().
940            mLocalMatrix.reset();
941            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
942            mLocalMatrix.postScale(mScaleX, mScaleY);
943            mLocalMatrix.postRotate(mRotate, 0, 0);
944            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
945        }
946
947        /**
948         * Must return in order of adding
949         * @return ordered list of paths
950         */
951        public ArrayList<VPath> getPaths() {
952            return mPathList;
953        }
954
955    }
956
957    static class VPath {
958        private int[] mThemeAttrs;
959
960        int mStrokeColor = 0;
961        float mStrokeWidth = 0;
962        float mStrokeOpacity = Float.NaN;
963        int mFillColor = Color.BLACK;
964        int mFillRule;
965        float mFillOpacity = Float.NaN;
966        float mTrimPathStart = 0;
967        float mTrimPathEnd = 1;
968        float mTrimPathOffset = 0;
969
970        boolean mClip = false;
971        Paint.Cap mStrokeLineCap = Paint.Cap.BUTT;
972        Paint.Join mStrokeLineJoin = Paint.Join.MITER;
973        float mStrokeMiterlimit = 4;
974
975        private VNode[] mNode = null;
976        private String mPathName;
977
978        public VPath() {
979            // Empty constructor.
980        }
981
982        public void toPath(Path path) {
983            path.reset();
984            if (mNode != null) {
985                VNode.createPath(mNode, path);
986            }
987        }
988
989        public String getPathName() {
990            return mPathName;
991        }
992
993        private Paint.Cap getStrokeLineCap(int id, Paint.Cap defValue) {
994            switch (id) {
995                case LINECAP_BUTT:
996                    return Paint.Cap.BUTT;
997                case LINECAP_ROUND:
998                    return Paint.Cap.ROUND;
999                case LINECAP_SQUARE:
1000                    return Paint.Cap.SQUARE;
1001                default:
1002                    return defValue;
1003            }
1004        }
1005
1006        private Paint.Join getStrokeLineJoin(int id, Paint.Join defValue) {
1007            switch (id) {
1008                case LINEJOIN_MITER:
1009                    return Paint.Join.MITER;
1010                case LINEJOIN_ROUND:
1011                    return Paint.Join.ROUND;
1012                case LINEJOIN_BEVEL:
1013                    return Paint.Join.BEVEL;
1014                default:
1015                    return defValue;
1016            }
1017        }
1018
1019        /* Setters and Getters */
1020        int getStroke() {
1021            return mStrokeColor;
1022        }
1023
1024        void setStroke(int strokeColor) {
1025            mStrokeColor = strokeColor;
1026        }
1027
1028        float getStrokeWidth() {
1029            return mStrokeWidth;
1030        }
1031
1032        void setStrokeWidth(float strokeWidth) {
1033            mStrokeWidth = strokeWidth;
1034        }
1035
1036        float getStrokeOpacity() {
1037            return mStrokeOpacity;
1038        }
1039
1040        void setStrokeOpacity(float strokeOpacity) {
1041            mStrokeOpacity = strokeOpacity;
1042        }
1043
1044        int getFill() {
1045            return mFillColor;
1046        }
1047
1048        void setFill(int fillColor) {
1049            mFillColor = fillColor;
1050        }
1051
1052        float getFillOpacity() {
1053            return mFillOpacity;
1054        }
1055
1056        void setFillOpacity(float fillOpacity) {
1057            mFillOpacity = fillOpacity;
1058        }
1059
1060        float getTrimPathStart() {
1061            return mTrimPathStart;
1062        }
1063
1064        void setTrimPathStart(float trimPathStart) {
1065            mTrimPathStart = trimPathStart;
1066        }
1067
1068        float getTrimPathEnd() {
1069            return mTrimPathEnd;
1070        }
1071
1072        void setTrimPathEnd(float trimPathEnd) {
1073            mTrimPathEnd = trimPathEnd;
1074        }
1075
1076        float getTrimPathOffset() {
1077            return mTrimPathOffset;
1078        }
1079
1080        void setTrimPathOffset(float trimPathOffset) {
1081            mTrimPathOffset = trimPathOffset;
1082        }
1083
1084        public void inflate(Resources r, AttributeSet attrs, Theme theme) {
1085            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.VectorDrawablePath);
1086            final int[] themeAttrs = a.extractThemeAttrs();
1087            mThemeAttrs = themeAttrs;
1088
1089            // NOTE: The set of attributes loaded here MUST match the
1090            // set of attributes loaded in applyTheme.
1091            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_clipToPath] == 0) {
1092                mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1093            }
1094
1095            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_name] == 0) {
1096                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1097            }
1098
1099            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_pathData] == 0) {
1100                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
1101            }
1102
1103            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fill] == 0) {
1104                mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1105            }
1106
1107            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_fillOpacity] == 0) {
1108                mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1109            }
1110
1111            if (themeAttrs == null
1112                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineCap] == 0) {
1113                mStrokeLineCap = getStrokeLineCap(
1114                        a.getInt(R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1115            }
1116
1117            if (themeAttrs == null
1118                    || themeAttrs[R.styleable.VectorDrawablePath_strokeLineJoin] == 0) {
1119                mStrokeLineJoin = getStrokeLineJoin(
1120                        a.getInt(R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1121            }
1122
1123            if (themeAttrs == null
1124                    || themeAttrs[R.styleable.VectorDrawablePath_strokeMiterLimit] == 0) {
1125                mStrokeMiterlimit = a.getFloat(
1126                        R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1127            }
1128
1129            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_stroke] == 0) {
1130                mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1131            }
1132
1133            if (themeAttrs == null
1134                    || themeAttrs[R.styleable.VectorDrawablePath_strokeOpacity] == 0) {
1135                mStrokeOpacity = a.getFloat(
1136                        R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1137            }
1138
1139            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_strokeWidth] == 0) {
1140                mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1141            }
1142
1143            if (themeAttrs == null || themeAttrs[R.styleable.VectorDrawablePath_trimPathEnd] == 0) {
1144                mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1145            }
1146
1147            if (themeAttrs == null
1148                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathOffset] == 0) {
1149                mTrimPathOffset = a.getFloat(
1150                        R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1151            }
1152
1153            if (themeAttrs == null
1154                    || themeAttrs[R.styleable.VectorDrawablePath_trimPathStart] == 0) {
1155                mTrimPathStart = a.getFloat(
1156                        R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1157            }
1158
1159            updateColorAlphas();
1160
1161            a.recycle();
1162        }
1163
1164        public boolean canApplyTheme() {
1165            return mThemeAttrs != null;
1166        }
1167
1168        public void applyTheme(Theme t) {
1169            if (mThemeAttrs == null) {
1170                return;
1171            }
1172
1173            final TypedArray a = t.resolveAttributes(
1174                    mThemeAttrs, R.styleable.VectorDrawablePath);
1175
1176            mClip = a.getBoolean(R.styleable.VectorDrawablePath_clipToPath, mClip);
1177
1178            if (a.hasValue(R.styleable.VectorDrawablePath_name)) {
1179                mPathName = a.getString(R.styleable.VectorDrawablePath_name);
1180            }
1181
1182            if (a.hasValue(R.styleable.VectorDrawablePath_pathData)) {
1183                mNode = parsePath(a.getString(R.styleable.VectorDrawablePath_pathData));
1184            }
1185
1186            mFillColor = a.getColor(R.styleable.VectorDrawablePath_fill, mFillColor);
1187            mFillOpacity = a.getFloat(R.styleable.VectorDrawablePath_fillOpacity, mFillOpacity);
1188
1189            mStrokeLineCap = getStrokeLineCap(a.getInt(
1190                    R.styleable.VectorDrawablePath_strokeLineCap, -1), mStrokeLineCap);
1191            mStrokeLineJoin = getStrokeLineJoin(a.getInt(
1192                    R.styleable.VectorDrawablePath_strokeLineJoin, -1), mStrokeLineJoin);
1193            mStrokeMiterlimit = a.getFloat(
1194                    R.styleable.VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
1195            mStrokeColor = a.getColor(R.styleable.VectorDrawablePath_stroke, mStrokeColor);
1196            mStrokeOpacity = a.getFloat(
1197                    R.styleable.VectorDrawablePath_strokeOpacity, mStrokeOpacity);
1198            mStrokeWidth = a.getFloat(R.styleable.VectorDrawablePath_strokeWidth, mStrokeWidth);
1199
1200            mTrimPathEnd = a.getFloat(R.styleable.VectorDrawablePath_trimPathEnd, mTrimPathEnd);
1201            mTrimPathOffset = a.getFloat(
1202                    R.styleable.VectorDrawablePath_trimPathOffset, mTrimPathOffset);
1203            mTrimPathStart = a.getFloat(
1204                    R.styleable.VectorDrawablePath_trimPathStart, mTrimPathStart);
1205
1206            updateColorAlphas();
1207            a.recycle();
1208        }
1209
1210        private void updateColorAlphas() {
1211            if (!Float.isNaN(mFillOpacity)) {
1212                mFillColor = applyAlpha(mFillColor, mFillOpacity);
1213            }
1214
1215            if (!Float.isNaN(mStrokeOpacity)) {
1216                mStrokeColor = applyAlpha(mStrokeColor, mStrokeOpacity);
1217            }
1218        }
1219
1220        private static int nextStart(String s, int end) {
1221            char c;
1222
1223            while (end < s.length()) {
1224                c = s.charAt(end);
1225                if (((c - 'A') * (c - 'Z') <= 0) || (((c - 'a') * (c - 'z') <= 0))) {
1226                    return end;
1227                }
1228                end++;
1229            }
1230            return end;
1231        }
1232
1233        private void addNode(ArrayList<VectorDrawable.VNode> list, char cmd, float[] val) {
1234            list.add(new VectorDrawable.VNode(cmd, val));
1235        }
1236
1237        /**
1238         * parse the floats in the string
1239         * this is an optimized version of
1240         * parseFloat(s.split(",|\\s"));
1241         *
1242         * @param s the string containing a command and list of floats
1243         * @return array of floats
1244         */
1245        private static float[] getFloats(String s) {
1246            if (s.charAt(0) == 'z' | s.charAt(0) == 'Z') {
1247                return new float[0];
1248            }
1249            try {
1250                float[] tmp = new float[s.length()];
1251                int count = 0;
1252                int pos = 1, end;
1253                while ((end = extract(s, pos)) >= 0) {
1254                    if (pos < end) {
1255                        tmp[count++] = Float.parseFloat(s.substring(pos, end));
1256                    }
1257                    pos = end + 1;
1258                }
1259                // handle the final float if there is one
1260                if (pos < s.length()) {
1261                    tmp[count++] = Float.parseFloat(s.substring(pos, s.length()));
1262                }
1263                return Arrays.copyOf(tmp, count);
1264            } catch (NumberFormatException e){
1265                Log.e(LOGTAG,"error in parsing \""+s+"\"");
1266                throw e;
1267            }
1268        }
1269
1270        /**
1271         * calculate the position of the next comma or space
1272         * @param s the string to search
1273         * @param start the position to start searching
1274         * @return the position of the next comma or space or -1 if none found
1275         */
1276        private static int extract(String s, int start) {
1277            int space = s.indexOf(' ', start);
1278            int comma = s.indexOf(',', start);
1279            if (space == -1) {
1280                return comma;
1281            }
1282            if (comma == -1) {
1283                return space;
1284            }
1285            return (comma > space) ? space : comma;
1286        }
1287
1288        private VectorDrawable.VNode[] parsePath(String value) {
1289            int start = 0;
1290            int end = 1;
1291
1292            ArrayList<VectorDrawable.VNode> list = new ArrayList<VectorDrawable.VNode>();
1293            while (end < value.length()) {
1294                end = nextStart(value, end);
1295                String s = value.substring(start, end);
1296                float[] val = getFloats(s);
1297                addNode(list, s.charAt(0), val);
1298
1299                start = end;
1300                end++;
1301            }
1302            if ((end - start) == 1 && start < value.length()) {
1303
1304                addNode(list, value.charAt(start), new float[0]);
1305            }
1306            return list.toArray(new VectorDrawable.VNode[list.size()]);
1307        }
1308    }
1309
1310    private static class VNode {
1311        private char mType;
1312        private float[] mParams;
1313
1314        public VNode(char type, float[] params) {
1315            mType = type;
1316            mParams = params;
1317        }
1318
1319        public VNode(VNode n) {
1320            mType = n.mType;
1321            mParams = Arrays.copyOf(n.mParams, n.mParams.length);
1322        }
1323
1324        public static void createPath(VNode[] node, Path path) {
1325            float[] current = new float[4];
1326            char previousCommand = 'm';
1327            for (int i = 0; i < node.length; i++) {
1328                addCommand(path, current, previousCommand, node[i].mType, node[i].mParams);
1329                previousCommand = node[i].mType;
1330            }
1331        }
1332
1333        private static void addCommand(Path path, float[] current,
1334                char previousCmd, char cmd, float[] val) {
1335
1336            int incr = 2;
1337            float currentX = current[0];
1338            float currentY = current[1];
1339            float ctrlPointX = current[2];
1340            float ctrlPointY = current[3];
1341            float reflectiveCtrlPointX;
1342            float reflectiveCtrlPointY;
1343
1344            switch (cmd) {
1345                case 'z':
1346                case 'Z':
1347                    path.close();
1348                    return;
1349                case 'm':
1350                case 'M':
1351                case 'l':
1352                case 'L':
1353                case 't':
1354                case 'T':
1355                    incr = 2;
1356                    break;
1357                case 'h':
1358                case 'H':
1359                case 'v':
1360                case 'V':
1361                    incr = 1;
1362                    break;
1363                case 'c':
1364                case 'C':
1365                    incr = 6;
1366                    break;
1367                case 's':
1368                case 'S':
1369                case 'q':
1370                case 'Q':
1371                    incr = 4;
1372                    break;
1373                case 'a':
1374                case 'A':
1375                    incr = 7;
1376                    break;
1377            }
1378            for (int k = 0; k < val.length; k += incr) {
1379                switch (cmd) {
1380                    case 'm': // moveto - Start a new sub-path (relative)
1381                        path.rMoveTo(val[k + 0], val[k + 1]);
1382                        currentX += val[k + 0];
1383                        currentY += val[k + 1];
1384                        break;
1385                    case 'M': // moveto - Start a new sub-path
1386                        path.moveTo(val[k + 0], val[k + 1]);
1387                        currentX = val[k + 0];
1388                        currentY = val[k + 1];
1389                        break;
1390                    case 'l': // lineto - Draw a line from the current point (relative)
1391                        path.rLineTo(val[k + 0], val[k + 1]);
1392                        currentX += val[k + 0];
1393                        currentY += val[k + 1];
1394                        break;
1395                    case 'L': // lineto - Draw a line from the current point
1396                        path.lineTo(val[k + 0], val[k + 1]);
1397                        currentX = val[k + 0];
1398                        currentY = val[k + 1];
1399                        break;
1400                    case 'z': // closepath - Close the current subpath
1401                    case 'Z': // closepath - Close the current subpath
1402                        path.close();
1403                        break;
1404                    case 'h': // horizontal lineto - Draws a horizontal line (relative)
1405                        path.rLineTo(val[k + 0], 0);
1406                        currentX += val[k + 0];
1407                        break;
1408                    case 'H': // horizontal lineto - Draws a horizontal line
1409                        path.lineTo(val[k + 0], currentY);
1410                        currentX = val[k + 0];
1411                        break;
1412                    case 'v': // vertical lineto - Draws a vertical line from the current point (r)
1413                        path.rLineTo(0, val[k + 0]);
1414                        currentY += val[k + 0];
1415                        break;
1416                    case 'V': // vertical lineto - Draws a vertical line from the current point
1417                        path.lineTo(currentX, val[k + 0]);
1418                        currentY = val[k + 0];
1419                        break;
1420                    case 'c': // curveto - Draws a cubic Bézier curve (relative)
1421                        path.rCubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
1422                                val[k + 4], val[k + 5]);
1423
1424                        ctrlPointX = currentX + val[k + 2];
1425                        ctrlPointY = currentY + val[k + 3];
1426                        currentX += val[k + 4];
1427                        currentY += val[k + 5];
1428
1429                        break;
1430                    case 'C': // curveto - Draws a cubic Bézier curve
1431                        path.cubicTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3],
1432                                val[k + 4], val[k + 5]);
1433                        currentX = val[k + 4];
1434                        currentY = val[k + 5];
1435                        ctrlPointX = val[k + 2];
1436                        ctrlPointY = val[k + 3];
1437                        break;
1438                    case 's': // smooth curveto - Draws a cubic Bézier curve (reflective cp)
1439                        reflectiveCtrlPointX = 0;
1440                        reflectiveCtrlPointY = 0;
1441                        if (previousCmd == 'c' || previousCmd == 's'
1442                                || previousCmd == 'C' || previousCmd == 'S') {
1443                            reflectiveCtrlPointX = currentX - ctrlPointX;
1444                            reflectiveCtrlPointY = currentY - ctrlPointY;
1445                        }
1446                        path.rCubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
1447                                val[k + 0], val[k + 1],
1448                                val[k + 2], val[k + 3]);
1449
1450                        ctrlPointX = currentX + val[k + 0];
1451                        ctrlPointY = currentY + val[k + 1];
1452                        currentX += val[k + 2];
1453                        currentY += val[k + 3];
1454                        break;
1455                    case 'S': // shorthand/smooth curveto Draws a cubic Bézier curve(reflective cp)
1456                        reflectiveCtrlPointX = currentX;
1457                        reflectiveCtrlPointY = currentY;
1458                        if (previousCmd == 'c' || previousCmd == 's'
1459                                || previousCmd == 'C' || previousCmd == 'S') {
1460                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
1461                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
1462                        }
1463                        path.cubicTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
1464                                val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
1465                        ctrlPointX = val[k + 0];
1466                        ctrlPointY = val[k + 1];
1467                        currentX = val[k + 2];
1468                        currentY = val[k + 3];
1469                        break;
1470                    case 'q': // Draws a quadratic Bézier (relative)
1471                        path.rQuadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
1472                        ctrlPointX = currentX + val[k + 0];
1473                        ctrlPointY = currentY + val[k + 1];
1474                        currentX += val[k + 2];
1475                        currentY += val[k + 3];
1476                        break;
1477                    case 'Q': // Draws a quadratic Bézier
1478                        path.quadTo(val[k + 0], val[k + 1], val[k + 2], val[k + 3]);
1479                        ctrlPointX = val[k + 0];
1480                        ctrlPointY = val[k + 1];
1481                        currentX = val[k + 2];
1482                        currentY = val[k + 3];
1483                        break;
1484                    case 't': // Draws a quadratic Bézier curve(reflective control point)(relative)
1485                        reflectiveCtrlPointX = 0;
1486                        reflectiveCtrlPointY = 0;
1487                        if (previousCmd == 'q' || previousCmd == 't'
1488                                || previousCmd == 'Q' || previousCmd == 'T') {
1489                            reflectiveCtrlPointX = currentX - ctrlPointX;
1490                            reflectiveCtrlPointY = currentY - ctrlPointY;
1491                        }
1492                        path.rQuadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
1493                                val[k + 0], val[k + 1]);
1494                        ctrlPointX = currentX + reflectiveCtrlPointX;
1495                        ctrlPointY = currentY + reflectiveCtrlPointY;
1496                        currentX += val[k + 0];
1497                        currentY += val[k + 1];
1498                        break;
1499                    case 'T': // Draws a quadratic Bézier curve (reflective control point)
1500                        reflectiveCtrlPointX = currentX;
1501                        reflectiveCtrlPointY = currentY;
1502                        if (previousCmd == 'q' || previousCmd == 't'
1503                                || previousCmd == 'Q' || previousCmd == 'T') {
1504                            reflectiveCtrlPointX = 2 * currentX - ctrlPointX;
1505                            reflectiveCtrlPointY = 2 * currentY - ctrlPointY;
1506                        }
1507                        path.quadTo(reflectiveCtrlPointX, reflectiveCtrlPointY,
1508                                val[k + 0], val[k + 1]);
1509                        ctrlPointX = reflectiveCtrlPointX;
1510                        ctrlPointY = reflectiveCtrlPointY;
1511                        currentX = val[k + 0];
1512                        currentY = val[k + 1];
1513                        break;
1514                    case 'a': // Draws an elliptical arc
1515                        // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
1516                        drawArc(path,
1517                                currentX,
1518                                currentY,
1519                                val[k + 5] + currentX,
1520                                val[k + 6] + currentY,
1521                                val[k + 0],
1522                                val[k + 1],
1523                                val[k + 2],
1524                                val[k + 3] != 0,
1525                                val[k + 4] != 0);
1526                        currentX += val[k + 5];
1527                        currentY += val[k + 6];
1528                        ctrlPointX = currentX;
1529                        ctrlPointY = currentY;
1530                        break;
1531                    case 'A': // Draws an elliptical arc
1532                        drawArc(path,
1533                                currentX,
1534                                currentY,
1535                                val[k + 5],
1536                                val[k + 6],
1537                                val[k + 0],
1538                                val[k + 1],
1539                                val[k + 2],
1540                                val[k + 3] != 0,
1541                                val[k + 4] != 0);
1542                        currentX = val[k + 5];
1543                        currentY = val[k + 6];
1544                        ctrlPointX = currentX;
1545                        ctrlPointY = currentY;
1546                        break;
1547                }
1548                previousCmd = cmd;
1549            }
1550            current[0] = currentX;
1551            current[1] = currentY;
1552            current[2] = ctrlPointX;
1553            current[3] = ctrlPointY;
1554        }
1555
1556        private static void drawArc(Path p,
1557                float x0,
1558                float y0,
1559                float x1,
1560                float y1,
1561                float a,
1562                float b,
1563                float theta,
1564                boolean isMoreThanHalf,
1565                boolean isPositiveArc) {
1566
1567            /* Convert rotation angle from degrees to radians */
1568            double thetaD = Math.toRadians(theta);
1569            /* Pre-compute rotation matrix entries */
1570            double cosTheta = Math.cos(thetaD);
1571            double sinTheta = Math.sin(thetaD);
1572            /* Transform (x0, y0) and (x1, y1) into unit space */
1573            /* using (inverse) rotation, followed by (inverse) scale */
1574            double x0p = (x0 * cosTheta + y0 * sinTheta) / a;
1575            double y0p = (-x0 * sinTheta + y0 * cosTheta) / b;
1576            double x1p = (x1 * cosTheta + y1 * sinTheta) / a;
1577            double y1p = (-x1 * sinTheta + y1 * cosTheta) / b;
1578
1579            /* Compute differences and averages */
1580            double dx = x0p - x1p;
1581            double dy = y0p - y1p;
1582            double xm = (x0p + x1p) / 2;
1583            double ym = (y0p + y1p) / 2;
1584            /* Solve for intersecting unit circles */
1585            double dsq = dx * dx + dy * dy;
1586            if (dsq == 0.0) {
1587                Log.w(LOGTAG, " Points are coincident");
1588                return; /* Points are coincident */
1589            }
1590            double disc = 1.0 / dsq - 1.0 / 4.0;
1591            if (disc < 0.0) {
1592                Log.w(LOGTAG, "Points are too far apart " + dsq);
1593                float adjust = (float) (Math.sqrt(dsq) / 1.99999);
1594                drawArc(p, x0, y0, x1, y1, a * adjust,
1595                        b * adjust, theta, isMoreThanHalf, isPositiveArc);
1596                return; /* Points are too far apart */
1597            }
1598            double s = Math.sqrt(disc);
1599            double sdx = s * dx;
1600            double sdy = s * dy;
1601            double cx;
1602            double cy;
1603            if (isMoreThanHalf == isPositiveArc) {
1604                cx = xm - sdy;
1605                cy = ym + sdx;
1606            } else {
1607                cx = xm + sdy;
1608                cy = ym - sdx;
1609            }
1610
1611            double eta0 = Math.atan2((y0p - cy), (x0p - cx));
1612
1613            double eta1 = Math.atan2((y1p - cy), (x1p - cx));
1614
1615            double sweep = (eta1 - eta0);
1616            if (isPositiveArc != (sweep >= 0)) {
1617                if (sweep > 0) {
1618                    sweep -= 2 * Math.PI;
1619                } else {
1620                    sweep += 2 * Math.PI;
1621                }
1622            }
1623
1624            cx *= a;
1625            cy *= b;
1626            double tcx = cx;
1627            cx = cx * cosTheta - cy * sinTheta;
1628            cy = tcx * sinTheta + cy * cosTheta;
1629
1630            arcToBezier(p, cx, cy, a, b, x0, y0, thetaD, eta0, sweep);
1631        }
1632
1633        /**
1634         * Converts an arc to cubic Bezier segments and records them in p.
1635         *
1636         * @param p The target for the cubic Bezier segments
1637         * @param cx The x coordinate center of the ellipse
1638         * @param cy The y coordinate center of the ellipse
1639         * @param a The radius of the ellipse in the horizontal direction
1640         * @param b The radius of the ellipse in the vertical direction
1641         * @param e1x E(eta1) x coordinate of the starting point of the arc
1642         * @param e1y E(eta2) y coordinate of the starting point of the arc
1643         * @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
1644         * @param start The start angle of the arc on the ellipse
1645         * @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
1646         */
1647        private static void arcToBezier(Path p,
1648                double cx,
1649                double cy,
1650                double a,
1651                double b,
1652                double e1x,
1653                double e1y,
1654                double theta,
1655                double start,
1656                double sweep) {
1657            // Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
1658            // and http://www.spaceroots.org/documents/ellipse/node22.html
1659
1660            // Maximum of 45 degrees per cubic Bezier segment
1661            int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
1662
1663            double eta1 = start;
1664            double cosTheta = Math.cos(theta);
1665            double sinTheta = Math.sin(theta);
1666            double cosEta1 = Math.cos(eta1);
1667            double sinEta1 = Math.sin(eta1);
1668            double ep1x = (-a * cosTheta * sinEta1) - (b * sinTheta * cosEta1);
1669            double ep1y = (-a * sinTheta * sinEta1) + (b * cosTheta * cosEta1);
1670
1671            double anglePerSegment = sweep / numSegments;
1672            for (int i = 0; i < numSegments; i++) {
1673                double eta2 = eta1 + anglePerSegment;
1674                double sinEta2 = Math.sin(eta2);
1675                double cosEta2 = Math.cos(eta2);
1676                double e2x = cx + (a * cosTheta * cosEta2) - (b * sinTheta * sinEta2);
1677                double e2y = cy + (a * sinTheta * cosEta2) + (b * cosTheta * sinEta2);
1678                double ep2x = -a * cosTheta * sinEta2 - b * sinTheta * cosEta2;
1679                double ep2y = -a * sinTheta * sinEta2 + b * cosTheta * cosEta2;
1680                double tanDiff2 = Math.tan((eta2 - eta1) / 2);
1681                double alpha =
1682                        Math.sin(eta2 - eta1) * (Math.sqrt(4 + (3 * tanDiff2 * tanDiff2)) - 1) / 3;
1683                double q1x = e1x + alpha * ep1x;
1684                double q1y = e1y + alpha * ep1y;
1685                double q2x = e2x - alpha * ep2x;
1686                double q2y = e2y - alpha * ep2y;
1687
1688                p.cubicTo((float) q1x,
1689                        (float) q1y,
1690                        (float) q2x,
1691                        (float) q2y,
1692                        (float) e2x,
1693                        (float) e2y);
1694                eta1 = eta2;
1695                e1x = e2x;
1696                e1y = e2y;
1697                ep1x = ep2x;
1698                ep1y = ep2y;
1699            }
1700        }
1701
1702    }
1703}
1704