1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.graphics.drawable;
18
19import com.android.layoutlib.bridge.impl.DelegateManager;
20import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
21
22import android.annotation.NonNull;
23import android.content.res.Resources;
24import android.content.res.Resources.Theme;
25import android.content.res.TypedArray;
26import android.graphics.Canvas_Delegate;
27import android.graphics.Color;
28import android.graphics.Matrix;
29import android.graphics.Paint;
30import android.graphics.Paint.Cap;
31import android.graphics.Paint.Join;
32import android.graphics.Paint_Delegate;
33import android.graphics.Path;
34import android.graphics.PathMeasure;
35import android.graphics.Path_Delegate;
36import android.graphics.Rect;
37import android.graphics.Region.Op;
38import android.util.ArrayMap;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.util.MathUtils;
42import android.util.PathParser_Delegate;
43
44import java.nio.ByteBuffer;
45import java.nio.ByteOrder;
46import java.nio.FloatBuffer;
47import java.util.ArrayList;
48import java.util.function.Consumer;
49
50import static android.graphics.Canvas.CLIP_SAVE_FLAG;
51import static android.graphics.Canvas.MATRIX_SAVE_FLAG;
52import static android.graphics.Paint.Cap.BUTT;
53import static android.graphics.Paint.Cap.ROUND;
54import static android.graphics.Paint.Cap.SQUARE;
55import static android.graphics.Paint.Join.BEVEL;
56import static android.graphics.Paint.Join.MITER;
57import static android.graphics.Paint.Style;
58
59/**
60 * Delegate used to provide new implementation of a select few methods of {@link VectorDrawable}
61 * <p>
62 * Through the layoutlib_create tool, the original  methods of VectorDrawable have been replaced by
63 * calls to methods of the same name in this delegate class.
64 */
65@SuppressWarnings("unused")
66public class VectorDrawable_Delegate {
67    private static final String LOGTAG = VectorDrawable_Delegate.class.getSimpleName();
68    private static final boolean DBG_VECTOR_DRAWABLE = false;
69
70    private static final DelegateManager<VNativeObject> sPathManager =
71            new DelegateManager<>(VNativeObject.class);
72
73    /**
74     * Obtains styled attributes from the theme, if available, or unstyled resources if the theme is
75     * null.
76     */
77    private static TypedArray obtainAttributes(
78            Resources res, Theme theme, AttributeSet set, int[] attrs) {
79        if (theme == null) {
80            return res.obtainAttributes(set, attrs);
81        }
82        return theme.obtainStyledAttributes(set, attrs, 0, 0);
83    }
84
85    private static int applyAlpha(int color, float alpha) {
86        int alphaBytes = Color.alpha(color);
87        color &= 0x00FFFFFF;
88        color |= ((int) (alphaBytes * alpha)) << 24;
89        return color;
90    }
91
92    @LayoutlibDelegate
93    static long nCreateTree(long rootGroupPtr) {
94        VGroup_Delegate rootGroup = VNativeObject.getDelegate(rootGroupPtr);
95        return sPathManager.addNewDelegate(new VPathRenderer_Delegate(rootGroup));
96    }
97
98    @LayoutlibDelegate
99    static void nSetRendererViewportSize(long rendererPtr, float viewportWidth,
100            float viewportHeight) {
101        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
102        nativePathRenderer.mViewportWidth = viewportWidth;
103        nativePathRenderer.mViewportHeight = viewportHeight;
104    }
105
106    @LayoutlibDelegate
107    static boolean nSetRootAlpha(long rendererPtr, float alpha) {
108        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
109        nativePathRenderer.setRootAlpha(alpha);
110
111        return true;
112    }
113
114    @LayoutlibDelegate
115    static float nGetRootAlpha(long rendererPtr) {
116        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
117
118        return nativePathRenderer.getRootAlpha();
119    }
120
121    @LayoutlibDelegate
122    static void nSetAllowCaching(long rendererPtr, boolean allowCaching) {
123        // ignored
124    }
125
126    @LayoutlibDelegate
127    static int nDraw(long rendererPtr, long canvasWrapperPtr,
128            long colorFilterPtr, Rect bounds, boolean needsMirroring, boolean canReuseCache) {
129        VPathRenderer_Delegate nativePathRenderer = VNativeObject.getDelegate(rendererPtr);
130
131        Canvas_Delegate.native_save(canvasWrapperPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
132        Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.left, bounds.top);
133
134        if (needsMirroring) {
135            Canvas_Delegate.native_translate(canvasWrapperPtr, bounds.width(), 0);
136            Canvas_Delegate.native_scale(canvasWrapperPtr, -1.0f, 1.0f);
137        }
138
139        // At this point, canvas has been translated to the right position.
140        // And we use this bound for the destination rect for the drawBitmap, so
141        // we offset to (0, 0);
142        bounds.offsetTo(0, 0);
143        nativePathRenderer.draw(canvasWrapperPtr, colorFilterPtr, bounds.width(), bounds.height());
144
145        Canvas_Delegate.native_restore(canvasWrapperPtr, true);
146
147        return bounds.width() * bounds.height();
148    }
149
150    @LayoutlibDelegate
151    static long nCreateFullPath() {
152        return sPathManager.addNewDelegate(new VFullPath_Delegate());
153    }
154
155    @LayoutlibDelegate
156    static long nCreateFullPath(long nativeFullPathPtr) {
157        VFullPath_Delegate original = VNativeObject.getDelegate(nativeFullPathPtr);
158
159        return sPathManager.addNewDelegate(new VFullPath_Delegate(original));
160    }
161
162    @LayoutlibDelegate
163    static boolean nGetFullPathProperties(long pathPtr, byte[] propertiesData,
164            int length) {
165        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
166
167        ByteBuffer properties = ByteBuffer.wrap(propertiesData);
168        properties.order(ByteOrder.nativeOrder());
169
170        properties.putFloat(VFullPath_Delegate.STROKE_WIDTH_INDEX * 4, path.getStrokeWidth());
171        properties.putInt(VFullPath_Delegate.STROKE_COLOR_INDEX * 4, path.getStrokeColor());
172        properties.putFloat(VFullPath_Delegate.STROKE_ALPHA_INDEX * 4, path.getStrokeAlpha());
173        properties.putInt(VFullPath_Delegate.FILL_COLOR_INDEX * 4, path.getFillColor());
174        properties.putFloat(VFullPath_Delegate.FILL_ALPHA_INDEX * 4, path.getStrokeAlpha());
175        properties.putFloat(VFullPath_Delegate.TRIM_PATH_START_INDEX * 4, path.getTrimPathStart());
176        properties.putFloat(VFullPath_Delegate.TRIM_PATH_END_INDEX * 4, path.getTrimPathEnd());
177        properties.putFloat(VFullPath_Delegate.TRIM_PATH_OFFSET_INDEX * 4,
178                path.getTrimPathOffset());
179        properties.putInt(VFullPath_Delegate.STROKE_LINE_CAP_INDEX * 4, path.getStrokeLineCap());
180        properties.putInt(VFullPath_Delegate.STROKE_LINE_JOIN_INDEX * 4, path.getStrokeLineJoin());
181        properties.putFloat(VFullPath_Delegate.STROKE_MITER_LIMIT_INDEX * 4,
182                path.getStrokeMiterlimit());
183        properties.putInt(VFullPath_Delegate.FILL_TYPE_INDEX * 4, path.getFillType());
184
185        return true;
186    }
187
188    @LayoutlibDelegate
189    static void nUpdateFullPathProperties(long pathPtr, float strokeWidth,
190            int strokeColor, float strokeAlpha, int fillColor, float fillAlpha, float trimPathStart,
191            float trimPathEnd, float trimPathOffset, float strokeMiterLimit, int strokeLineCap,
192            int strokeLineJoin, int fillType) {
193        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
194
195        path.setStrokeWidth(strokeWidth);
196        path.setStrokeColor(strokeColor);
197        path.setStrokeAlpha(strokeAlpha);
198        path.setFillColor(fillColor);
199        path.setFillAlpha(fillAlpha);
200        path.setTrimPathStart(trimPathStart);
201        path.setTrimPathEnd(trimPathEnd);
202        path.setTrimPathOffset(trimPathOffset);
203        path.setStrokeMiterlimit(strokeMiterLimit);
204        path.setStrokeLineCap(strokeLineCap);
205        path.setStrokeLineJoin(strokeLineJoin);
206        path.setFillType(fillType);
207    }
208
209    @LayoutlibDelegate
210    static void nUpdateFullPathFillGradient(long pathPtr, long fillGradientPtr) {
211        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
212
213        path.setFillGradient(fillGradientPtr);
214    }
215
216    @LayoutlibDelegate
217    static void nUpdateFullPathStrokeGradient(long pathPtr, long strokeGradientPtr) {
218        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
219
220        path.setStrokeGradient(strokeGradientPtr);
221    }
222
223    @LayoutlibDelegate
224    static long nCreateClipPath() {
225        return sPathManager.addNewDelegate(new VClipPath_Delegate());
226    }
227
228    @LayoutlibDelegate
229    static long nCreateClipPath(long clipPathPtr) {
230        VClipPath_Delegate original = VNativeObject.getDelegate(clipPathPtr);
231        return sPathManager.addNewDelegate(new VClipPath_Delegate(original));
232    }
233
234    @LayoutlibDelegate
235    static long nCreateGroup() {
236        return sPathManager.addNewDelegate(new VGroup_Delegate());
237    }
238
239    @LayoutlibDelegate
240    static long nCreateGroup(long groupPtr) {
241        VGroup_Delegate original = VNativeObject.getDelegate(groupPtr);
242        return sPathManager.addNewDelegate(
243                new VGroup_Delegate(original, new ArrayMap<String, Object>()));
244    }
245
246    @LayoutlibDelegate
247    static void nSetName(long nodePtr, String name) {
248        VNativeObject group = VNativeObject.getDelegate(nodePtr);
249        group.setName(name);
250    }
251
252    @LayoutlibDelegate
253    static boolean nGetGroupProperties(long groupPtr, float[] propertiesData,
254            int length) {
255        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
256
257        FloatBuffer properties = FloatBuffer.wrap(propertiesData);
258
259        properties.put(VGroup_Delegate.ROTATE_INDEX, group.getRotation());
260        properties.put(VGroup_Delegate.PIVOT_X_INDEX, group.getPivotX());
261        properties.put(VGroup_Delegate.PIVOT_Y_INDEX, group.getPivotY());
262        properties.put(VGroup_Delegate.SCALE_X_INDEX, group.getScaleX());
263        properties.put(VGroup_Delegate.SCALE_Y_INDEX, group.getScaleY());
264        properties.put(VGroup_Delegate.TRANSLATE_X_INDEX, group.getTranslateX());
265        properties.put(VGroup_Delegate.TRANSLATE_Y_INDEX, group.getTranslateY());
266
267        return true;
268    }
269    @LayoutlibDelegate
270    static void nUpdateGroupProperties(long groupPtr, float rotate, float pivotX,
271            float pivotY, float scaleX, float scaleY, float translateX, float translateY) {
272        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
273
274        group.setRotation(rotate);
275        group.setPivotX(pivotX);
276        group.setPivotY(pivotY);
277        group.setScaleX(scaleX);
278        group.setScaleY(scaleY);
279        group.setTranslateX(translateX);
280        group.setTranslateY(translateY);
281    }
282
283    @LayoutlibDelegate
284    static void nAddChild(long groupPtr, long nodePtr) {
285        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
286        group.mChildren.add(VNativeObject.getDelegate(nodePtr));
287    }
288
289    @LayoutlibDelegate
290    static void nSetPathString(long pathPtr, String pathString, int length) {
291        VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
292        path.setPathData(PathParser_Delegate.createNodesFromPathData(pathString));
293    }
294
295    /**
296     * The setters and getters below for paths and groups are here temporarily, and will be removed
297     * once the animation in AVD is replaced with RenderNodeAnimator, in which case the animation
298     * will modify these properties in native. By then no JNI hopping would be necessary for VD
299     * during animation, and these setters and getters will be obsolete.
300     */
301    // Setters and getters during animation.
302    @LayoutlibDelegate
303    static float nGetRotation(long groupPtr) {
304        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
305        return group.getRotation();
306    }
307
308    @LayoutlibDelegate
309    static void nSetRotation(long groupPtr, float rotation) {
310        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
311        group.setRotation(rotation);
312    }
313
314    @LayoutlibDelegate
315    static float nGetPivotX(long groupPtr) {
316        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
317        return group.getPivotX();
318    }
319
320    @LayoutlibDelegate
321    static void nSetPivotX(long groupPtr, float pivotX) {
322        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
323        group.setPivotX(pivotX);
324    }
325
326    @LayoutlibDelegate
327    static float nGetPivotY(long groupPtr) {
328        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
329        return group.getPivotY();
330    }
331
332    @LayoutlibDelegate
333    static void nSetPivotY(long groupPtr, float pivotY) {
334        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
335        group.setPivotY(pivotY);
336    }
337
338    @LayoutlibDelegate
339    static float nGetScaleX(long groupPtr) {
340        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
341        return group.getScaleX();
342    }
343
344    @LayoutlibDelegate
345    static void nSetScaleX(long groupPtr, float scaleX) {
346        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
347        group.setScaleX(scaleX);
348    }
349
350    @LayoutlibDelegate
351    static float nGetScaleY(long groupPtr) {
352        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
353        return group.getScaleY();
354    }
355
356    @LayoutlibDelegate
357    static void nSetScaleY(long groupPtr, float scaleY) {
358        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
359        group.setScaleY(scaleY);
360    }
361
362    @LayoutlibDelegate
363    static float nGetTranslateX(long groupPtr) {
364        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
365        return group.getTranslateX();
366    }
367
368    @LayoutlibDelegate
369    static void nSetTranslateX(long groupPtr, float translateX) {
370        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
371        group.setTranslateX(translateX);
372    }
373
374    @LayoutlibDelegate
375    static float nGetTranslateY(long groupPtr) {
376        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
377        return group.getTranslateY();
378    }
379
380    @LayoutlibDelegate
381    static void nSetTranslateY(long groupPtr, float translateY) {
382        VGroup_Delegate group = VNativeObject.getDelegate(groupPtr);
383        group.setTranslateY(translateY);
384    }
385
386    @LayoutlibDelegate
387    static void nSetPathData(long pathPtr, long pathDataPtr) {
388        VPath_Delegate path = VNativeObject.getDelegate(pathPtr);
389        path.setPathData(PathParser_Delegate.getDelegate(pathDataPtr).getPathDataNodes());
390    }
391
392    @LayoutlibDelegate
393    static float nGetStrokeWidth(long pathPtr) {
394        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
395        return path.getStrokeWidth();
396    }
397
398    @LayoutlibDelegate
399    static void nSetStrokeWidth(long pathPtr, float width) {
400        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
401        path.setStrokeWidth(width);
402    }
403
404    @LayoutlibDelegate
405    static int nGetStrokeColor(long pathPtr) {
406        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
407        return path.getStrokeColor();
408    }
409
410    @LayoutlibDelegate
411    static void nSetStrokeColor(long pathPtr, int strokeColor) {
412        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
413        path.setStrokeColor(strokeColor);
414    }
415
416    @LayoutlibDelegate
417    static float nGetStrokeAlpha(long pathPtr) {
418        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
419        return path.getStrokeAlpha();
420    }
421
422    @LayoutlibDelegate
423    static void nSetStrokeAlpha(long pathPtr, float alpha) {
424        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
425        path.setStrokeAlpha(alpha);
426    }
427
428    @LayoutlibDelegate
429    static int nGetFillColor(long pathPtr) {
430        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
431        return path.getFillColor();
432    }
433
434    @LayoutlibDelegate
435    static void nSetFillColor(long pathPtr, int fillColor) {
436        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
437        path.setFillColor(fillColor);
438    }
439
440    @LayoutlibDelegate
441    static float nGetFillAlpha(long pathPtr) {
442        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
443        return path.getFillAlpha();
444    }
445
446    @LayoutlibDelegate
447    static void nSetFillAlpha(long pathPtr, float fillAlpha) {
448        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
449        path.setFillAlpha(fillAlpha);
450    }
451
452    @LayoutlibDelegate
453    static float nGetTrimPathStart(long pathPtr) {
454        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
455        return path.getTrimPathStart();
456    }
457
458    @LayoutlibDelegate
459    static void nSetTrimPathStart(long pathPtr, float trimPathStart) {
460        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
461        path.setTrimPathStart(trimPathStart);
462    }
463
464    @LayoutlibDelegate
465    static float nGetTrimPathEnd(long pathPtr) {
466        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
467        return path.getTrimPathEnd();
468    }
469
470    @LayoutlibDelegate
471    static void nSetTrimPathEnd(long pathPtr, float trimPathEnd) {
472        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
473        path.setTrimPathEnd(trimPathEnd);
474    }
475
476    @LayoutlibDelegate
477    static float nGetTrimPathOffset(long pathPtr) {
478        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
479        return path.getTrimPathOffset();
480    }
481
482    @LayoutlibDelegate
483    static void nSetTrimPathOffset(long pathPtr, float trimPathOffset) {
484        VFullPath_Delegate path = VNativeObject.getDelegate(pathPtr);
485        path.setTrimPathOffset(trimPathOffset);
486    }
487
488    /**
489     * Base class for all the internal Delegates that does two functions:
490     * <ol>
491     *     <li>Serves as base class to store all the delegates in one {@link DelegateManager}
492     *     <li>Provides setName for all the classes. {@link VPathRenderer_Delegate} does actually
493     *     not need it
494     * </ol>
495     */
496    interface VNativeObject {
497        @NonNull
498        static <T> T getDelegate(long nativePtr) {
499            //noinspection unchecked
500            T vNativeObject = (T) sPathManager.getDelegate(nativePtr);
501
502            assert vNativeObject != null;
503            return vNativeObject;
504        }
505
506        void setName(String name);
507    }
508
509    private static class VClipPath_Delegate extends VPath_Delegate {
510        private VClipPath_Delegate() {
511            // Empty constructor.
512        }
513
514        private VClipPath_Delegate(VClipPath_Delegate copy) {
515            super(copy);
516        }
517
518        @Override
519        public boolean isClipPath() {
520            return true;
521        }
522    }
523
524    static class VFullPath_Delegate extends VPath_Delegate {
525        // These constants need to be kept in sync with their values in VectorDrawable.VFullPath
526        private static final int STROKE_WIDTH_INDEX = 0;
527        private static final int STROKE_COLOR_INDEX = 1;
528        private static final int STROKE_ALPHA_INDEX = 2;
529        private static final int FILL_COLOR_INDEX = 3;
530        private static final int FILL_ALPHA_INDEX = 4;
531        private static final int TRIM_PATH_START_INDEX = 5;
532        private static final int TRIM_PATH_END_INDEX = 6;
533        private static final int TRIM_PATH_OFFSET_INDEX = 7;
534        private static final int STROKE_LINE_CAP_INDEX = 8;
535        private static final int STROKE_LINE_JOIN_INDEX = 9;
536        private static final int STROKE_MITER_LIMIT_INDEX = 10;
537        private static final int FILL_TYPE_INDEX = 11;
538
539        private static final int LINECAP_BUTT = 0;
540        private static final int LINECAP_ROUND = 1;
541        private static final int LINECAP_SQUARE = 2;
542
543        private static final int LINEJOIN_MITER = 0;
544        private static final int LINEJOIN_ROUND = 1;
545        private static final int LINEJOIN_BEVEL = 2;
546
547        @NonNull
548        public Consumer<Float> getFloatPropertySetter(int propertyIdx) {
549            switch (propertyIdx) {
550                case STROKE_ALPHA_INDEX:
551                    return this::setStrokeAlpha;
552                case FILL_ALPHA_INDEX:
553                    return this::setFillAlpha;
554                case TRIM_PATH_START_INDEX:
555                    return this::setTrimPathStart;
556                case TRIM_PATH_END_INDEX:
557                    return this::setTrimPathEnd;
558                case TRIM_PATH_OFFSET_INDEX:
559                    return this::setTrimPathOffset;
560            }
561
562            throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
563                    + propertyIdx);
564        }
565
566        @NonNull
567        public Consumer<Integer> getIntPropertySetter(int propertyIdx) {
568            switch (propertyIdx) {
569                case STROKE_COLOR_INDEX:
570                    return this::setStrokeColor;
571                case FILL_COLOR_INDEX:
572                    return this::setFillColor;
573            }
574
575            throw new IllegalArgumentException("Invalid VFullPath_Delegate property index "
576                    + propertyIdx);
577        }
578
579        /////////////////////////////////////////////////////
580        // Variables below need to be copied (deep copy if applicable) for mutation.
581
582        int mStrokeColor = Color.TRANSPARENT;
583        float mStrokeWidth = 0;
584
585        int mFillColor = Color.TRANSPARENT;
586        long mStrokeGradient = 0;
587        long mFillGradient = 0;
588        float mStrokeAlpha = 1.0f;
589        float mFillAlpha = 1.0f;
590        float mTrimPathStart = 0;
591        float mTrimPathEnd = 1;
592        float mTrimPathOffset = 0;
593
594        Cap mStrokeLineCap = BUTT;
595        Join mStrokeLineJoin = MITER;
596        float mStrokeMiterlimit = 4;
597
598        int mFillType = 0; // WINDING(0) is the default value. See Path.FillType
599
600        private VFullPath_Delegate() {
601            // Empty constructor.
602        }
603
604        private VFullPath_Delegate(VFullPath_Delegate copy) {
605            super(copy);
606
607            mStrokeColor = copy.mStrokeColor;
608            mStrokeWidth = copy.mStrokeWidth;
609            mStrokeAlpha = copy.mStrokeAlpha;
610            mFillColor = copy.mFillColor;
611            mFillAlpha = copy.mFillAlpha;
612            mTrimPathStart = copy.mTrimPathStart;
613            mTrimPathEnd = copy.mTrimPathEnd;
614            mTrimPathOffset = copy.mTrimPathOffset;
615
616            mStrokeLineCap = copy.mStrokeLineCap;
617            mStrokeLineJoin = copy.mStrokeLineJoin;
618            mStrokeMiterlimit = copy.mStrokeMiterlimit;
619
620            mStrokeGradient = copy.mStrokeGradient;
621            mFillGradient = copy.mFillGradient;
622            mFillType = copy.mFillType;
623        }
624
625        private int getStrokeLineCap() {
626            switch (mStrokeLineCap) {
627                case BUTT:
628                    return LINECAP_BUTT;
629                case ROUND:
630                    return LINECAP_ROUND;
631                case SQUARE:
632                    return LINECAP_SQUARE;
633                default:
634                    assert false;
635            }
636
637            return -1;
638        }
639
640        private void setStrokeLineCap(int cap) {
641            switch (cap) {
642                case LINECAP_BUTT:
643                    mStrokeLineCap = BUTT;
644                    break;
645                case LINECAP_ROUND:
646                    mStrokeLineCap = ROUND;
647                    break;
648                case LINECAP_SQUARE:
649                    mStrokeLineCap = SQUARE;
650                    break;
651                default:
652                    assert false;
653            }
654        }
655
656        private int getStrokeLineJoin() {
657            switch (mStrokeLineJoin) {
658                case MITER:
659                    return LINEJOIN_MITER;
660                case ROUND:
661                    return LINEJOIN_ROUND;
662                case BEVEL:
663                    return LINEJOIN_BEVEL;
664                default:
665                    assert false;
666            }
667
668            return -1;
669        }
670
671        private void setStrokeLineJoin(int join) {
672            switch (join) {
673                case LINEJOIN_BEVEL:
674                    mStrokeLineJoin = BEVEL;
675                    break;
676                case LINEJOIN_MITER:
677                    mStrokeLineJoin = MITER;
678                    break;
679                case LINEJOIN_ROUND:
680                    mStrokeLineJoin = Join.ROUND;
681                    break;
682                default:
683                    assert false;
684            }
685        }
686
687        private int getStrokeColor() {
688            return mStrokeColor;
689        }
690
691        private void setStrokeColor(int strokeColor) {
692            mStrokeColor = strokeColor;
693        }
694
695        private float getStrokeWidth() {
696            return mStrokeWidth;
697        }
698
699        private void setStrokeWidth(float strokeWidth) {
700            mStrokeWidth = strokeWidth;
701        }
702
703        private float getStrokeAlpha() {
704            return mStrokeAlpha;
705        }
706
707        private void setStrokeAlpha(float strokeAlpha) {
708            mStrokeAlpha = strokeAlpha;
709        }
710
711        private int getFillColor() {
712            return mFillColor;
713        }
714
715        private void setFillColor(int fillColor) {
716            mFillColor = fillColor;
717        }
718
719        private float getFillAlpha() {
720            return mFillAlpha;
721        }
722
723        private void setFillAlpha(float fillAlpha) {
724            mFillAlpha = fillAlpha;
725        }
726
727        private float getTrimPathStart() {
728            return mTrimPathStart;
729        }
730
731        private void setTrimPathStart(float trimPathStart) {
732            mTrimPathStart = trimPathStart;
733        }
734
735        private float getTrimPathEnd() {
736            return mTrimPathEnd;
737        }
738
739        private void setTrimPathEnd(float trimPathEnd) {
740            mTrimPathEnd = trimPathEnd;
741        }
742
743        private float getTrimPathOffset() {
744            return mTrimPathOffset;
745        }
746
747        private void setTrimPathOffset(float trimPathOffset) {
748            mTrimPathOffset = trimPathOffset;
749        }
750
751        private void setStrokeMiterlimit(float limit) {
752            mStrokeMiterlimit = limit;
753        }
754
755        private float getStrokeMiterlimit() {
756            return mStrokeMiterlimit;
757        }
758
759        private void setStrokeGradient(long gradientPtr) {
760            mStrokeGradient = gradientPtr;
761        }
762
763        private void setFillGradient(long gradientPtr) {
764            mFillGradient = gradientPtr;
765        }
766
767        private void setFillType(int fillType) {
768            mFillType = fillType;
769        }
770
771        private int getFillType() {
772            return mFillType;
773        }
774    }
775
776    static class VGroup_Delegate implements VNativeObject {
777        // This constants need to be kept in sync with their definitions in VectorDrawable.Group
778        private static final int ROTATE_INDEX = 0;
779        private static final int PIVOT_X_INDEX = 1;
780        private static final int PIVOT_Y_INDEX = 2;
781        private static final int SCALE_X_INDEX = 3;
782        private static final int SCALE_Y_INDEX = 4;
783        private static final int TRANSLATE_X_INDEX = 5;
784        private static final int TRANSLATE_Y_INDEX = 6;
785
786        public Consumer<Float> getPropertySetter(int propertyIdx) {
787            switch (propertyIdx) {
788                case ROTATE_INDEX:
789                    return this::setRotation;
790                case PIVOT_X_INDEX:
791                    return this::setPivotX;
792                case PIVOT_Y_INDEX:
793                    return this::setPivotY;
794                case SCALE_X_INDEX:
795                    return this::setScaleX;
796                case SCALE_Y_INDEX:
797                    return this::setScaleY;
798                case TRANSLATE_X_INDEX:
799                    return this::setTranslateX;
800                case TRANSLATE_Y_INDEX:
801                    return this::setTranslateY;
802            }
803
804            throw new IllegalArgumentException("Invalid VGroup_Delegate property index "
805                    + propertyIdx);
806        }
807
808        /////////////////////////////////////////////////////
809        // Variables below need to be copied (deep copy if applicable) for mutation.
810        final ArrayList<Object> mChildren = new ArrayList<>();
811        // mStackedMatrix is only used temporarily when drawing, it combines all
812        // the parents' local matrices with the current one.
813        private final Matrix mStackedMatrix = new Matrix();
814        // mLocalMatrix is updated based on the update of transformation information,
815        // either parsed from the XML or by animation.
816        private final Matrix mLocalMatrix = new Matrix();
817        private float mRotate = 0;
818        private float mPivotX = 0;
819        private float mPivotY = 0;
820        private float mScaleX = 1;
821        private float mScaleY = 1;
822        private float mTranslateX = 0;
823        private float mTranslateY = 0;
824        private int mChangingConfigurations;
825        private String mGroupName = null;
826
827        private VGroup_Delegate(VGroup_Delegate copy, ArrayMap<String, Object> targetsMap) {
828            mRotate = copy.mRotate;
829            mPivotX = copy.mPivotX;
830            mPivotY = copy.mPivotY;
831            mScaleX = copy.mScaleX;
832            mScaleY = copy.mScaleY;
833            mTranslateX = copy.mTranslateX;
834            mTranslateY = copy.mTranslateY;
835            mGroupName = copy.mGroupName;
836            mChangingConfigurations = copy.mChangingConfigurations;
837            if (mGroupName != null) {
838                targetsMap.put(mGroupName, this);
839            }
840
841            mLocalMatrix.set(copy.mLocalMatrix);
842
843            final ArrayList<Object> children = copy.mChildren;
844            //noinspection ForLoopReplaceableByForEach
845            for (int i = 0; i < children.size(); i++) {
846                Object copyChild = children.get(i);
847                if (copyChild instanceof VGroup_Delegate) {
848                    VGroup_Delegate copyGroup = (VGroup_Delegate) copyChild;
849                    mChildren.add(new VGroup_Delegate(copyGroup, targetsMap));
850                } else {
851                    VPath_Delegate newPath;
852                    if (copyChild instanceof VFullPath_Delegate) {
853                        newPath = new VFullPath_Delegate((VFullPath_Delegate) copyChild);
854                    } else if (copyChild instanceof VClipPath_Delegate) {
855                        newPath = new VClipPath_Delegate((VClipPath_Delegate) copyChild);
856                    } else {
857                        throw new IllegalStateException("Unknown object in the tree!");
858                    }
859                    mChildren.add(newPath);
860                    if (newPath.mPathName != null) {
861                        targetsMap.put(newPath.mPathName, newPath);
862                    }
863                }
864            }
865        }
866
867        private VGroup_Delegate() {
868        }
869
870        private void updateLocalMatrix() {
871            // The order we apply is the same as the
872            // RenderNode.cpp::applyViewPropertyTransforms().
873            mLocalMatrix.reset();
874            mLocalMatrix.postTranslate(-mPivotX, -mPivotY);
875            mLocalMatrix.postScale(mScaleX, mScaleY);
876            mLocalMatrix.postRotate(mRotate, 0, 0);
877            mLocalMatrix.postTranslate(mTranslateX + mPivotX, mTranslateY + mPivotY);
878        }
879
880        /* Setters and Getters, used by animator from AnimatedVectorDrawable. */
881        private float getRotation() {
882            return mRotate;
883        }
884
885        private void setRotation(float rotation) {
886            if (rotation != mRotate) {
887                mRotate = rotation;
888                updateLocalMatrix();
889            }
890        }
891
892        private float getPivotX() {
893            return mPivotX;
894        }
895
896        private void setPivotX(float pivotX) {
897            if (pivotX != mPivotX) {
898                mPivotX = pivotX;
899                updateLocalMatrix();
900            }
901        }
902
903        private float getPivotY() {
904            return mPivotY;
905        }
906
907        private void setPivotY(float pivotY) {
908            if (pivotY != mPivotY) {
909                mPivotY = pivotY;
910                updateLocalMatrix();
911            }
912        }
913
914        private float getScaleX() {
915            return mScaleX;
916        }
917
918        private void setScaleX(float scaleX) {
919            if (scaleX != mScaleX) {
920                mScaleX = scaleX;
921                updateLocalMatrix();
922            }
923        }
924
925        private float getScaleY() {
926            return mScaleY;
927        }
928
929        private void setScaleY(float scaleY) {
930            if (scaleY != mScaleY) {
931                mScaleY = scaleY;
932                updateLocalMatrix();
933            }
934        }
935
936        private float getTranslateX() {
937            return mTranslateX;
938        }
939
940        private void setTranslateX(float translateX) {
941            if (translateX != mTranslateX) {
942                mTranslateX = translateX;
943                updateLocalMatrix();
944            }
945        }
946
947        private float getTranslateY() {
948            return mTranslateY;
949        }
950
951        private void setTranslateY(float translateY) {
952            if (translateY != mTranslateY) {
953                mTranslateY = translateY;
954                updateLocalMatrix();
955            }
956        }
957
958        @Override
959        public void setName(String name) {
960            mGroupName = name;
961        }
962    }
963
964    public static class VPath_Delegate implements VNativeObject {
965        protected PathParser_Delegate.PathDataNode[] mNodes = null;
966        String mPathName;
967        int mChangingConfigurations;
968
969        public VPath_Delegate() {
970            // Empty constructor.
971        }
972
973        public VPath_Delegate(VPath_Delegate copy) {
974            mPathName = copy.mPathName;
975            mChangingConfigurations = copy.mChangingConfigurations;
976            mNodes = PathParser_Delegate.deepCopyNodes(copy.mNodes);
977        }
978
979        public void toPath(Path path) {
980            path.reset();
981            if (mNodes != null) {
982                PathParser_Delegate.PathDataNode.nodesToPath(mNodes,
983                        Path_Delegate.getDelegate(path.mNativePath));
984            }
985        }
986
987        @Override
988        public void setName(String name) {
989            mPathName = name;
990        }
991
992        public boolean isClipPath() {
993            return false;
994        }
995
996        private void setPathData(PathParser_Delegate.PathDataNode[] nodes) {
997            if (!PathParser_Delegate.canMorph(mNodes, nodes)) {
998                // This should not happen in the middle of animation.
999                mNodes = PathParser_Delegate.deepCopyNodes(nodes);
1000            } else {
1001                PathParser_Delegate.updateNodes(mNodes, nodes);
1002            }
1003        }
1004    }
1005
1006    static class VPathRenderer_Delegate implements VNativeObject {
1007        /* Right now the internal data structure is organized as a tree.
1008         * Each node can be a group node, or a path.
1009         * A group node can have groups or paths as children, but a path node has
1010         * no children.
1011         * One example can be:
1012         *                 Root Group
1013         *                /    |     \
1014         *           Group    Path    Group
1015         *          /     \             |
1016         *         Path   Path         Path
1017         *
1018         */
1019        // Variables that only used temporarily inside the draw() call, so there
1020        // is no need for deep copying.
1021        private final Path mPath;
1022        private final Path mRenderPath;
1023        private final Matrix mFinalPathMatrix = new Matrix();
1024        private final VGroup_Delegate mRootGroup;
1025        private float mViewportWidth = 0;
1026        private float mViewportHeight = 0;
1027        private float mRootAlpha = 1.0f;
1028        private Paint mStrokePaint;
1029        private Paint mFillPaint;
1030        private PathMeasure mPathMeasure;
1031
1032        private VPathRenderer_Delegate(VGroup_Delegate rootGroup) {
1033            mRootGroup = rootGroup;
1034            mPath = new Path();
1035            mRenderPath = new Path();
1036        }
1037
1038        private float getRootAlpha() {
1039            return mRootAlpha;
1040        }
1041
1042        void setRootAlpha(float alpha) {
1043            mRootAlpha = alpha;
1044        }
1045
1046        private void drawGroupTree(VGroup_Delegate currentGroup, Matrix currentMatrix,
1047                long canvasPtr, int w, int h, long filterPtr) {
1048            // Calculate current group's matrix by preConcat the parent's and
1049            // and the current one on the top of the stack.
1050            // Basically the Mfinal = Mviewport * M0 * M1 * M2;
1051            // Mi the local matrix at level i of the group tree.
1052            currentGroup.mStackedMatrix.set(currentMatrix);
1053            currentGroup.mStackedMatrix.preConcat(currentGroup.mLocalMatrix);
1054
1055            // Save the current clip information, which is local to this group.
1056            Canvas_Delegate.native_save(canvasPtr, MATRIX_SAVE_FLAG | CLIP_SAVE_FLAG);
1057            // Draw the group tree in the same order as the XML file.
1058            for (int i = 0; i < currentGroup.mChildren.size(); i++) {
1059                Object child = currentGroup.mChildren.get(i);
1060                if (child instanceof VGroup_Delegate) {
1061                    VGroup_Delegate childGroup = (VGroup_Delegate) child;
1062                    drawGroupTree(childGroup, currentGroup.mStackedMatrix,
1063                            canvasPtr, w, h, filterPtr);
1064                } else if (child instanceof VPath_Delegate) {
1065                    VPath_Delegate childPath = (VPath_Delegate) child;
1066                    drawPath(currentGroup, childPath, canvasPtr, w, h, filterPtr);
1067                }
1068            }
1069            Canvas_Delegate.native_restore(canvasPtr, true);
1070        }
1071
1072        public void draw(long canvasPtr, long filterPtr, int w, int h) {
1073            // Traverse the tree in pre-order to draw.
1074            drawGroupTree(mRootGroup, Matrix.IDENTITY_MATRIX, canvasPtr, w, h, filterPtr);
1075        }
1076
1077        private void drawPath(VGroup_Delegate VGroup, VPath_Delegate VPath, long canvasPtr,
1078                int w,
1079                int h,
1080                long filterPtr) {
1081            final float scaleX = w / mViewportWidth;
1082            final float scaleY = h / mViewportHeight;
1083            final float minScale = Math.min(scaleX, scaleY);
1084            final Matrix groupStackedMatrix = VGroup.mStackedMatrix;
1085
1086            mFinalPathMatrix.set(groupStackedMatrix);
1087            mFinalPathMatrix.postScale(scaleX, scaleY);
1088
1089            final float matrixScale = getMatrixScale(groupStackedMatrix);
1090            if (matrixScale == 0) {
1091                // When either x or y is scaled to 0, we don't need to draw anything.
1092                return;
1093            }
1094            VPath.toPath(mPath);
1095            final Path path = mPath;
1096
1097            mRenderPath.reset();
1098
1099            if (VPath.isClipPath()) {
1100                mRenderPath.addPath(path, mFinalPathMatrix);
1101                Canvas_Delegate.native_clipPath(canvasPtr, mRenderPath.mNativePath, Op
1102                        .INTERSECT.nativeInt);
1103            } else {
1104                VFullPath_Delegate fullPath = (VFullPath_Delegate) VPath;
1105                if (fullPath.mTrimPathStart != 0.0f || fullPath.mTrimPathEnd != 1.0f) {
1106                    float start = (fullPath.mTrimPathStart + fullPath.mTrimPathOffset) % 1.0f;
1107                    float end = (fullPath.mTrimPathEnd + fullPath.mTrimPathOffset) % 1.0f;
1108
1109                    if (mPathMeasure == null) {
1110                        mPathMeasure = new PathMeasure();
1111                    }
1112                    mPathMeasure.setPath(mPath, false);
1113
1114                    float len = mPathMeasure.getLength();
1115                    start = start * len;
1116                    end = end * len;
1117                    path.reset();
1118                    if (start > end) {
1119                        mPathMeasure.getSegment(start, len, path, true);
1120                        mPathMeasure.getSegment(0f, end, path, true);
1121                    } else {
1122                        mPathMeasure.getSegment(start, end, path, true);
1123                    }
1124                    path.rLineTo(0, 0); // fix bug in measure
1125                }
1126                mRenderPath.addPath(path, mFinalPathMatrix);
1127
1128                if (fullPath.mFillColor != Color.TRANSPARENT) {
1129                    if (mFillPaint == null) {
1130                        mFillPaint = new Paint();
1131                        mFillPaint.setStyle(Style.FILL);
1132                        mFillPaint.setAntiAlias(true);
1133                    }
1134
1135                    final Paint fillPaint = mFillPaint;
1136                    fillPaint.setColor(applyAlpha(fullPath.mFillColor, fullPath.mFillAlpha));
1137                    Paint_Delegate fillPaintDelegate = Paint_Delegate.getDelegate(fillPaint
1138                            .getNativeInstance());
1139                    // mFillPaint can not be null at this point so we will have a delegate
1140                    assert fillPaintDelegate != null;
1141                    fillPaintDelegate.setColorFilter(filterPtr);
1142                    fillPaintDelegate.setShader(fullPath.mFillGradient);
1143                    Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType);
1144                    Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
1145                            .getNativeInstance());
1146                }
1147
1148                if (fullPath.mStrokeColor != Color.TRANSPARENT) {
1149                    if (mStrokePaint == null) {
1150                        mStrokePaint = new Paint();
1151                        mStrokePaint.setStyle(Style.STROKE);
1152                        mStrokePaint.setAntiAlias(true);
1153                    }
1154
1155                    final Paint strokePaint = mStrokePaint;
1156                    if (fullPath.mStrokeLineJoin != null) {
1157                        strokePaint.setStrokeJoin(fullPath.mStrokeLineJoin);
1158                    }
1159
1160                    if (fullPath.mStrokeLineCap != null) {
1161                        strokePaint.setStrokeCap(fullPath.mStrokeLineCap);
1162                    }
1163
1164                    strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
1165                    strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
1166                    Paint_Delegate strokePaintDelegate = Paint_Delegate.getDelegate(strokePaint
1167                            .getNativeInstance());
1168                    // mStrokePaint can not be null at this point so we will have a delegate
1169                    assert strokePaintDelegate != null;
1170                    strokePaintDelegate.setColorFilter(filterPtr);
1171                    final float finalStrokeScale = minScale * matrixScale;
1172                    strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
1173                    strokePaintDelegate.setShader(fullPath.mStrokeGradient);
1174                    Canvas_Delegate.native_drawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
1175                            .getNativeInstance());
1176                }
1177            }
1178        }
1179
1180        private float getMatrixScale(Matrix groupStackedMatrix) {
1181            // Given unit vectors A = (0, 1) and B = (1, 0).
1182            // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
1183            // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
1184            // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
1185            // If  max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
1186            //
1187            // For non-skew case, which is most of the cases, matrix scale is computing exactly the
1188            // scale on x and y axis, and take the minimal of these two.
1189            // For skew case, an unit square will mapped to a parallelogram. And this function will
1190            // return the minimal height of the 2 bases.
1191            float[] unitVectors = new float[]{0, 1, 1, 0};
1192            groupStackedMatrix.mapVectors(unitVectors);
1193            float scaleX = MathUtils.mag(unitVectors[0], unitVectors[1]);
1194            float scaleY = MathUtils.mag(unitVectors[2], unitVectors[3]);
1195            float crossProduct = MathUtils.cross(unitVectors[0], unitVectors[1],
1196                    unitVectors[2], unitVectors[3]);
1197            float maxScale = MathUtils.max(scaleX, scaleY);
1198
1199            float matrixScale = 0;
1200            if (maxScale > 0) {
1201                matrixScale = MathUtils.abs(crossProduct) / maxScale;
1202            }
1203            if (DBG_VECTOR_DRAWABLE) {
1204                Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
1205            }
1206            return matrixScale;
1207        }
1208
1209        @Override
1210        public void setName(String name) {
1211        }
1212    }
1213}
1214