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 
17 package android.graphics;
18 
19 import com.android.ide.common.rendering.api.LayoutLog;
20 import com.android.layoutlib.bridge.Bridge;
21 import com.android.layoutlib.bridge.impl.DelegateManager;
22 import com.android.layoutlib.bridge.util.CachedPathIteratorFactory;
23 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
24 
25 import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;
26 
27 import 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  */
42 public 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 
PathMeasure_Delegate(long native_path, boolean forceClosed)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
native_create(long native_path, boolean forceClosed)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
native_destroy(long native_instance)75     /*package*/ static void native_destroy(long native_instance) {
76         sManager.removeJavaReferenceFor(native_instance);
77     }
78 
79     @LayoutlibDelegate
native_getPosTan(long native_instance, float distance, float pos[], float tan[])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
native_getMatrix(long native_instance, float distance, long native_matrix, int flags)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
native_nextContour(long native_instance)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
native_setPath(long native_instance, long native_path, boolean forceClosed)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
native_getLength(long native_instance)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
native_isClosed(long native_instance)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
native_getSegment(long native_instance, float startD, float stopD, long native_dst_path, boolean startWithMoveTo)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