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