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