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