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