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