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