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