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