1/*
2 * Copyright (C) 2015 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;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21import com.android.layoutlib.bridge.impl.DelegateManager;
22import com.android.layoutlib.bridge.util.CachedPathIteratorFactory;
23import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
24
25import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;
26
27import java.awt.geom.PathIterator;
28
29/**
30 * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
31 * <p/>
32 * Through the layoutlib_create tool, the original native methods of PathMeasure have been
33 * replaced by
34 * calls to methods of the same name in this delegate class.
35 * <p/>
36 * This class behaves like the original native implementation, but in Java, keeping previously
37 * native data into its own objects and mapping them to int that are sent back and forth between it
38 * and the original PathMeasure class.
39 *
40 * @see DelegateManager
41 */
42public final class PathMeasure_Delegate {
43
44    // ---- delegate manager ----
45    private static final DelegateManager<PathMeasure_Delegate> sManager =
46            new DelegateManager<PathMeasure_Delegate>(PathMeasure_Delegate.class);
47
48    // ---- delegate data ----
49    private CachedPathIteratorFactory mOriginalPathIterator;
50
51    private long mNativePath;
52
53
54    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
55        mNativePath = native_path;
56        if (native_path != 0) {
57            if (forceClosed) {
58                // Copy the path and call close
59                native_path = Path_Delegate.nInit(native_path);
60                Path_Delegate.nClose(native_path);
61            }
62
63            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
64            mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
65                    .getPathIterator(null));
66        }
67    }
68
69    @LayoutlibDelegate
70    /*package*/ static long native_create(long native_path, boolean forceClosed) {
71        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
72    }
73
74    @LayoutlibDelegate
75    /*package*/ static void native_destroy(long native_instance) {
76        sManager.removeJavaReferenceFor(native_instance);
77    }
78
79    @LayoutlibDelegate
80    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
81            float tan[]) {
82        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
83                "PathMeasure.getPostTan is not supported.", null, null);
84        return false;
85    }
86
87    @LayoutlibDelegate
88    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
89            native_matrix, int flags) {
90        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
91                "PathMeasure.getMatrix is not supported.", null, null);
92        return false;
93    }
94
95    @LayoutlibDelegate
96    /*package*/ static boolean native_nextContour(long native_instance) {
97        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
98                "PathMeasure.nextContour is not supported.", null, null);
99        return false;
100    }
101
102    @LayoutlibDelegate
103    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
104            forceClosed) {
105        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
106        assert pathMeasure != null;
107
108        if (native_path != 0) {
109            if (forceClosed) {
110                // Copy the path and call close
111                native_path = Path_Delegate.nInit(native_path);
112                Path_Delegate.nClose(native_path);
113            }
114
115            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
116            pathMeasure.mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
117                    .getPathIterator(null));
118        }
119
120        pathMeasure.mNativePath = native_path;
121    }
122
123    @LayoutlibDelegate
124    /*package*/ static float native_getLength(long native_instance) {
125        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
126        assert pathMeasure != null;
127
128        if (pathMeasure.mOriginalPathIterator == null) {
129            return 0;
130        }
131
132        return pathMeasure.mOriginalPathIterator.iterator().getTotalLength();
133    }
134
135    @LayoutlibDelegate
136    /*package*/ static boolean native_isClosed(long native_instance) {
137        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
138        assert pathMeasure != null;
139
140        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
141        if (path == null) {
142            return false;
143        }
144
145        int type = 0;
146        float segment[] = new float[6];
147        for (PathIterator pi = path.getJavaShape().getPathIterator(null); !pi.isDone(); pi.next()) {
148            type = pi.currentSegment(segment);
149        }
150
151        // A path is a closed path if the last element is SEG_CLOSE
152        return type == PathIterator.SEG_CLOSE;
153    }
154
155    @LayoutlibDelegate
156    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
157            long native_dst_path, boolean startWithMoveTo) {
158        if (startD < 0) {
159            startD = 0;
160        }
161
162        if (startD >= stopD) {
163            return false;
164        }
165
166        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
167        assert pathMeasure != null;
168
169        CachedPathIterator iterator = pathMeasure.mOriginalPathIterator.iterator();
170        float accLength = startD;
171        boolean isZeroLength = true; // Whether the output has zero length or not
172        float[] points = new float[6];
173
174        iterator.jumpToSegment(accLength);
175        while (!iterator.isDone() && (stopD - accLength > 0.1f)) {
176            int type = iterator.currentSegment(points, stopD - accLength);
177
178            if (accLength - iterator.getCurrentSegmentLength() <= stopD) {
179                if (startWithMoveTo) {
180                    startWithMoveTo = false;
181
182                    // If this segment is a MOVETO, then we just use that one. If not, then we issue
183                    // a first moveto
184                    if (type != PathIterator.SEG_MOVETO) {
185                        float[] lastPoint = new float[2];
186                        iterator.getCurrentSegmentEnd(lastPoint);
187                        Path_Delegate.nMoveTo(native_dst_path, lastPoint[0], lastPoint[1]);
188                    }
189                }
190
191                isZeroLength = isZeroLength && iterator.getCurrentSegmentLength() > 0;
192                switch (type) {
193                    case PathIterator.SEG_MOVETO:
194                        Path_Delegate.nMoveTo(native_dst_path, points[0], points[1]);
195                        break;
196                    case PathIterator.SEG_LINETO:
197                        Path_Delegate.nLineTo(native_dst_path, points[0], points[1]);
198                        break;
199                    case PathIterator.SEG_CLOSE:
200                        Path_Delegate.nClose(native_dst_path);
201                        break;
202                    case PathIterator.SEG_CUBICTO:
203                        Path_Delegate.nCubicTo(native_dst_path, points[0], points[1],
204                                points[2], points[3],
205                                points[4], points[5]);
206                        break;
207                    case PathIterator.SEG_QUADTO:
208                        Path_Delegate.nQuadTo(native_dst_path, points[0], points[1],
209                                points[2],
210                                points[3]);
211                        break;
212                    default:
213                        assert false;
214                }
215            }
216
217            accLength += iterator.getCurrentSegmentLength();
218            iterator.next();
219        }
220
221        return !isZeroLength;
222    }
223}
224