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