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