blob: 265ebd1755e309b07cb5aa3ca054f1f1910eeae4 [file] [log] [blame]
Adam Lesinski282e1812014-01-23 18:17:42 -08001/*
2 * Copyright (C) 2010 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.tools.layoutlib.annotations.LayoutlibDelegate;
23
Deepanshu Gupta78aa6642015-07-29 14:24:25 -070024import android.annotation.NonNull;
Adam Lesinski282e1812014-01-23 18:17:42 -080025import android.graphics.Path.Direction;
26import android.graphics.Path.FillType;
27
28import java.awt.Shape;
29import java.awt.geom.AffineTransform;
30import java.awt.geom.Arc2D;
31import java.awt.geom.Area;
32import java.awt.geom.Ellipse2D;
33import java.awt.geom.GeneralPath;
Deepanshu Gupta78aa6642015-07-29 14:24:25 -070034import java.awt.geom.Path2D;
Adam Lesinski282e1812014-01-23 18:17:42 -080035import java.awt.geom.PathIterator;
36import java.awt.geom.Point2D;
37import java.awt.geom.Rectangle2D;
38import java.awt.geom.RoundRectangle2D;
Diego Perez081cebf2015-10-07 12:33:44 +010039import java.util.ArrayList;
Adam Lesinski282e1812014-01-23 18:17:42 -080040
41/**
42 * Delegate implementing the native methods of android.graphics.Path
43 *
44 * Through the layoutlib_create tool, the original native methods of Path have been replaced
45 * by calls to methods of the same name in this delegate class.
46 *
47 * This class behaves like the original native implementation, but in Java, keeping previously
48 * native data into its own objects and mapping them to int that are sent back and forth between
49 * it and the original Path class.
50 *
51 * @see DelegateManager
52 *
53 */
54public final class Path_Delegate {
55
56 // ---- delegate manager ----
57 private static final DelegateManager<Path_Delegate> sManager =
58 new DelegateManager<Path_Delegate>(Path_Delegate.class);
59
Diego Perezb9c48d82015-12-18 16:01:24 +000060 private static final float EPSILON = 1e-4f;
61
Adam Lesinski282e1812014-01-23 18:17:42 -080062 // ---- delegate data ----
63 private FillType mFillType = FillType.WINDING;
Deepanshu Gupta78aa6642015-07-29 14:24:25 -070064 private Path2D mPath = new Path2D.Double();
Adam Lesinski282e1812014-01-23 18:17:42 -080065
66 private float mLastX = 0;
67 private float mLastY = 0;
68
Diego Perezb9c48d82015-12-18 16:01:24 +000069 // true if the path contains does not contain a curve or line.
70 private boolean mCachedIsEmpty = true;
71
Adam Lesinski282e1812014-01-23 18:17:42 -080072 // ---- Public Helper methods ----
73
Narayan Kamath88a83642014-01-27 14:24:16 +000074 public static Path_Delegate getDelegate(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -080075 return sManager.getDelegate(nPath);
76 }
77
Deepanshu Gupta491523d2015-10-06 17:56:37 -070078 public Path2D getJavaShape() {
Adam Lesinski282e1812014-01-23 18:17:42 -080079 return mPath;
80 }
81
82 public void setJavaShape(Shape shape) {
Diego Perezb9c48d82015-12-18 16:01:24 +000083 reset();
Adam Lesinski282e1812014-01-23 18:17:42 -080084 mPath.append(shape, false /*connect*/);
85 }
86
87 public void reset() {
88 mPath.reset();
89 }
90
91 public void setPathIterator(PathIterator iterator) {
Diego Perezb9c48d82015-12-18 16:01:24 +000092 reset();
Adam Lesinski282e1812014-01-23 18:17:42 -080093 mPath.append(iterator, false /*connect*/);
94 }
95
96 // ---- native methods ----
97
98 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +000099 /*package*/ static long init1() {
Adam Lesinski282e1812014-01-23 18:17:42 -0800100 // create the delegate
101 Path_Delegate newDelegate = new Path_Delegate();
102
103 return sManager.addNewDelegate(newDelegate);
104 }
105
106 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000107 /*package*/ static long init2(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800108 // create the delegate
109 Path_Delegate newDelegate = new Path_Delegate();
110
111 // get the delegate to copy, which could be null if nPath is 0
112 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
113 if (pathDelegate != null) {
114 newDelegate.set(pathDelegate);
115 }
116
117 return sManager.addNewDelegate(newDelegate);
118 }
119
120 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000121 /*package*/ static void native_reset(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800122 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
123 if (pathDelegate == null) {
124 return;
125 }
126
127 pathDelegate.mPath.reset();
128 }
129
130 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000131 /*package*/ static void native_rewind(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800132 // call out to reset since there's nothing to optimize in
133 // terms of data structs.
134 native_reset(nPath);
135 }
136
137 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000138 /*package*/ static void native_set(long native_dst, long native_src) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800139 Path_Delegate pathDstDelegate = sManager.getDelegate(native_dst);
140 if (pathDstDelegate == null) {
141 return;
142 }
143
144 Path_Delegate pathSrcDelegate = sManager.getDelegate(native_src);
145 if (pathSrcDelegate == null) {
146 return;
147 }
148
149 pathDstDelegate.set(pathSrcDelegate);
150 }
151
152 @LayoutlibDelegate
Deepanshu Gupta103d4092014-03-14 13:24:56 -0700153 /*package*/ static boolean native_isConvex(long nPath) {
154 Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
155 "Path.isConvex is not supported.", null, null);
156 return true;
157 }
158
159 @LayoutlibDelegate
Deepanshu Gupta9be03c42014-02-21 16:22:10 -0800160 /*package*/ static int native_getFillType(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800161 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
162 if (pathDelegate == null) {
163 return 0;
164 }
165
166 return pathDelegate.mFillType.nativeInt;
167 }
168
169 @LayoutlibDelegate
Diego Perez5ceb30f2016-03-22 11:29:47 +0000170 public static void native_setFillType(long nPath, int ft) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800171 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
172 if (pathDelegate == null) {
173 return;
174 }
175
Diego Perez5ceb30f2016-03-22 11:29:47 +0000176 pathDelegate.setFillType(Path.sFillTypeArray[ft]);
Adam Lesinski282e1812014-01-23 18:17:42 -0800177 }
178
179 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000180 /*package*/ static boolean native_isEmpty(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800181 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
Diego Perez081cebf2015-10-07 12:33:44 +0100182 return pathDelegate == null || pathDelegate.isEmpty();
Adam Lesinski282e1812014-01-23 18:17:42 -0800183
Adam Lesinski282e1812014-01-23 18:17:42 -0800184 }
185
186 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000187 /*package*/ static boolean native_isRect(long nPath, RectF rect) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800188 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
189 if (pathDelegate == null) {
190 return false;
191 }
192
193 // create an Area that can test if the path is a rect
194 Area area = new Area(pathDelegate.mPath);
195 if (area.isRectangular()) {
196 if (rect != null) {
197 pathDelegate.fillBounds(rect);
198 }
199
200 return true;
201 }
202
203 return false;
204 }
205
206 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000207 /*package*/ static void native_computeBounds(long nPath, RectF bounds) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800208 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
209 if (pathDelegate == null) {
210 return;
211 }
212
213 pathDelegate.fillBounds(bounds);
214 }
215
216 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000217 /*package*/ static void native_incReserve(long nPath, int extraPtCount) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800218 // since we use a java2D path, there's no way to pre-allocate new points,
219 // so we do nothing.
220 }
221
222 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000223 /*package*/ static void native_moveTo(long nPath, float x, float y) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800224 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
225 if (pathDelegate == null) {
226 return;
227 }
228
229 pathDelegate.moveTo(x, y);
230 }
231
232 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000233 /*package*/ static void native_rMoveTo(long nPath, float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800234 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
235 if (pathDelegate == null) {
236 return;
237 }
238
239 pathDelegate.rMoveTo(dx, dy);
240 }
241
242 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000243 /*package*/ static void native_lineTo(long nPath, float x, float y) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800244 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
245 if (pathDelegate == null) {
246 return;
247 }
248
249 pathDelegate.lineTo(x, y);
250 }
251
252 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000253 /*package*/ static void native_rLineTo(long nPath, float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800254 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
255 if (pathDelegate == null) {
256 return;
257 }
258
259 pathDelegate.rLineTo(dx, dy);
260 }
261
262 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000263 /*package*/ static void native_quadTo(long nPath, float x1, float y1, float x2, float y2) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800264 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
265 if (pathDelegate == null) {
266 return;
267 }
268
269 pathDelegate.quadTo(x1, y1, x2, y2);
270 }
271
272 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000273 /*package*/ static void native_rQuadTo(long nPath, float dx1, float dy1, float dx2, float dy2) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800274 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
275 if (pathDelegate == null) {
276 return;
277 }
278
279 pathDelegate.rQuadTo(dx1, dy1, dx2, dy2);
280 }
281
282 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000283 /*package*/ static void native_cubicTo(long nPath, float x1, float y1,
Adam Lesinski282e1812014-01-23 18:17:42 -0800284 float x2, float y2, float x3, float y3) {
285 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
286 if (pathDelegate == null) {
287 return;
288 }
289
290 pathDelegate.cubicTo(x1, y1, x2, y2, x3, y3);
291 }
292
293 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000294 /*package*/ static void native_rCubicTo(long nPath, float x1, float y1,
Adam Lesinski282e1812014-01-23 18:17:42 -0800295 float x2, float y2, float x3, float y3) {
296 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
297 if (pathDelegate == null) {
298 return;
299 }
300
301 pathDelegate.rCubicTo(x1, y1, x2, y2, x3, y3);
302 }
303
304 @LayoutlibDelegate
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700305 /*package*/ static void native_arcTo(long nPath, float left, float top, float right,
306 float bottom,
Adam Lesinski282e1812014-01-23 18:17:42 -0800307 float startAngle, float sweepAngle, boolean forceMoveTo) {
308 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
309 if (pathDelegate == null) {
310 return;
311 }
312
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700313 pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo);
Adam Lesinski282e1812014-01-23 18:17:42 -0800314 }
315
316 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000317 /*package*/ static void native_close(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800318 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
319 if (pathDelegate == null) {
320 return;
321 }
322
323 pathDelegate.close();
324 }
325
326 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000327 /*package*/ static void native_addRect(long nPath,
Adam Lesinski282e1812014-01-23 18:17:42 -0800328 float left, float top, float right, float bottom, int dir) {
329 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
330 if (pathDelegate == null) {
331 return;
332 }
333
334 pathDelegate.addRect(left, top, right, bottom, dir);
335 }
336
337 @LayoutlibDelegate
Deepanshu Gupta6376c402014-05-15 09:30:11 -0700338 /*package*/ static void native_addOval(long nPath, float left, float top, float right,
339 float bottom, int dir) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800340 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
341 if (pathDelegate == null) {
342 return;
343 }
344
345 pathDelegate.mPath.append(new Ellipse2D.Float(
Deepanshu Gupta6376c402014-05-15 09:30:11 -0700346 left, top, right - left, bottom - top), false);
Adam Lesinski282e1812014-01-23 18:17:42 -0800347 }
348
349 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000350 /*package*/ static void native_addCircle(long nPath, float x, float y, float radius, int dir) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800351 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
352 if (pathDelegate == null) {
353 return;
354 }
355
356 // because x/y is the center of the circle, need to offset this by the radius
357 pathDelegate.mPath.append(new Ellipse2D.Float(
358 x - radius, y - radius, radius * 2, radius * 2), false);
359 }
360
361 @LayoutlibDelegate
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700362 /*package*/ static void native_addArc(long nPath, float left, float top, float right,
363 float bottom, float startAngle, float sweepAngle) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800364 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
365 if (pathDelegate == null) {
366 return;
367 }
368
369 // because x/y is the center of the circle, need to offset this by the radius
370 pathDelegate.mPath.append(new Arc2D.Float(
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700371 left, top, right - left, bottom - top,
Adam Lesinski282e1812014-01-23 18:17:42 -0800372 -startAngle, -sweepAngle, Arc2D.OPEN), false);
373 }
374
375 @LayoutlibDelegate
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700376 /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right,
377 float bottom, float rx, float ry, int dir) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800378
379 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
380 if (pathDelegate == null) {
381 return;
382 }
383
384 pathDelegate.mPath.append(new RoundRectangle2D.Float(
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700385 left, top, right - left, bottom - top, rx * 2, ry * 2), false);
Adam Lesinski282e1812014-01-23 18:17:42 -0800386 }
387
388 @LayoutlibDelegate
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700389 /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right,
390 float bottom, float[] radii, int dir) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800391
Jerome Gaillard3381cde2016-02-04 19:53:25 -0600392 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
393 if (pathDelegate == null) {
394 return;
Adam Lesinski282e1812014-01-23 18:17:42 -0800395 }
Jerome Gaillard3381cde2016-02-04 19:53:25 -0600396
397 float[] cornerDimensions = new float[radii.length];
398 for (int i = 0; i < radii.length; i++) {
399 cornerDimensions[i] = 2 * radii[i];
400 }
401 pathDelegate.mPath.append(new RoundRectangle(left, top, right - left, bottom - top,
402 cornerDimensions), false);
Adam Lesinski282e1812014-01-23 18:17:42 -0800403 }
404
405 @LayoutlibDelegate
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800406 /*package*/ static void native_addPath(long nPath, long src, float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800407 addPath(nPath, src, AffineTransform.getTranslateInstance(dx, dy));
408 }
409
410 @LayoutlibDelegate
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800411 /*package*/ static void native_addPath(long nPath, long src) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800412 addPath(nPath, src, null /*transform*/);
413 }
414
415 @LayoutlibDelegate
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800416 /*package*/ static void native_addPath(long nPath, long src, long matrix) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800417 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
418 if (matrixDelegate == null) {
419 return;
420 }
421
422 addPath(nPath, src, matrixDelegate.getAffineTransform());
423 }
424
425 @LayoutlibDelegate
Diego Perez8f43dfe2016-04-04 10:18:55 +0100426 /*package*/ static void native_offset(long nPath, float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800427 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
428 if (pathDelegate == null) {
429 return;
430 }
431
Diego Perez8f43dfe2016-04-04 10:18:55 +0100432 pathDelegate.offset(dx, dy);
Adam Lesinski282e1812014-01-23 18:17:42 -0800433 }
434
435 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000436 /*package*/ static void native_setLastPoint(long nPath, float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800437 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
438 if (pathDelegate == null) {
439 return;
440 }
441
442 pathDelegate.mLastX = dx;
443 pathDelegate.mLastY = dy;
444 }
445
446 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000447 /*package*/ static void native_transform(long nPath, long matrix,
448 long dst_path) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800449 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
450 if (pathDelegate == null) {
451 return;
452 }
453
454 Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(matrix);
455 if (matrixDelegate == null) {
456 return;
457 }
458
459 // this can be null if dst_path is 0
460 Path_Delegate dstDelegate = sManager.getDelegate(dst_path);
461
462 pathDelegate.transform(matrixDelegate, dstDelegate);
463 }
464
465 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000466 /*package*/ static void native_transform(long nPath, long matrix) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800467 native_transform(nPath, matrix, 0);
468 }
469
470 @LayoutlibDelegate
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800471 /*package*/ static boolean native_op(long nPath1, long nPath2, int op, long result) {
Deepanshu Guptabd28e2d2014-01-27 11:45:47 -0800472 Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.op() not supported", null);
473 return false;
474 }
475
476 @LayoutlibDelegate
Narayan Kamath88a83642014-01-27 14:24:16 +0000477 /*package*/ static void finalizer(long nPath) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800478 sManager.removeJavaReferenceFor(nPath);
479 }
480
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800481 @LayoutlibDelegate
482 /*package*/ static float[] native_approximate(long nPath, float error) {
Deepanshu Gupta78aa6642015-07-29 14:24:25 -0700483 Path_Delegate pathDelegate = sManager.getDelegate(nPath);
484 if (pathDelegate == null) {
485 return null;
486 }
Diego Perez081cebf2015-10-07 12:33:44 +0100487 // Get a FlatteningIterator
488 PathIterator iterator = pathDelegate.getJavaShape().getPathIterator(null, error);
Deepanshu Gupta78aa6642015-07-29 14:24:25 -0700489
Diego Perez081cebf2015-10-07 12:33:44 +0100490 float segment[] = new float[6];
491 float totalLength = 0;
492 ArrayList<Point2D.Float> points = new ArrayList<Point2D.Float>();
493 Point2D.Float previousPoint = null;
494 while (!iterator.isDone()) {
495 int type = iterator.currentSegment(segment);
496 Point2D.Float currentPoint = new Point2D.Float(segment[0], segment[1]);
497 // MoveTo shouldn't affect the length
498 if (previousPoint != null && type != PathIterator.SEG_MOVETO) {
499 totalLength += currentPoint.distance(previousPoint);
500 }
501 previousPoint = currentPoint;
502 points.add(currentPoint);
503 iterator.next();
Deepanshu Gupta78aa6642015-07-29 14:24:25 -0700504 }
Diego Perez081cebf2015-10-07 12:33:44 +0100505
506 int nPoints = points.size();
507 float[] result = new float[nPoints * 3];
508 previousPoint = null;
509 for (int i = 0; i < nPoints; i++) {
510 Point2D.Float point = points.get(i);
511 float distance = previousPoint != null ? (float) previousPoint.distance(point) : .0f;
512 result[i * 3] = distance / totalLength;
513 result[i * 3 + 1] = point.x;
514 result[i * 3 + 2] = point.y;
515
516 totalLength += distance;
517 previousPoint = point;
Deepanshu Gupta78aa6642015-07-29 14:24:25 -0700518 }
Diego Perez081cebf2015-10-07 12:33:44 +0100519
520 return result;
Deepanshu Guptae05bb952014-01-28 18:55:33 -0800521 }
522
Adam Lesinski282e1812014-01-23 18:17:42 -0800523 // ---- Private helper methods ----
524
525 private void set(Path_Delegate delegate) {
526 mPath.reset();
527 setFillType(delegate.mFillType);
528 mPath.append(delegate.mPath, false /*connect*/);
529 }
530
531 private void setFillType(FillType fillType) {
532 mFillType = fillType;
533 mPath.setWindingRule(getWindingRule(fillType));
534 }
535
536 /**
537 * Returns the Java2D winding rules matching a given Android {@link FillType}.
538 * @param type the android fill type
539 * @return the matching java2d winding rule.
540 */
541 private static int getWindingRule(FillType type) {
542 switch (type) {
543 case WINDING:
544 case INVERSE_WINDING:
545 return GeneralPath.WIND_NON_ZERO;
546 case EVEN_ODD:
547 case INVERSE_EVEN_ODD:
548 return GeneralPath.WIND_EVEN_ODD;
549 }
550
551 assert false;
552 throw new IllegalArgumentException();
553 }
554
Deepanshu Gupta78aa6642015-07-29 14:24:25 -0700555 @NonNull
Adam Lesinski282e1812014-01-23 18:17:42 -0800556 private static Direction getDirection(int direction) {
557 for (Direction d : Direction.values()) {
558 if (direction == d.nativeInt) {
559 return d;
560 }
561 }
562
563 assert false;
564 return null;
565 }
566
Diego Perez5d1013c2016-02-23 12:19:11 +0000567 public static void addPath(long destPath, long srcPath, AffineTransform transform) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800568 Path_Delegate destPathDelegate = sManager.getDelegate(destPath);
569 if (destPathDelegate == null) {
570 return;
571 }
572
573 Path_Delegate srcPathDelegate = sManager.getDelegate(srcPath);
574 if (srcPathDelegate == null) {
575 return;
576 }
577
578 if (transform != null) {
579 destPathDelegate.mPath.append(
580 srcPathDelegate.mPath.getPathIterator(transform), false);
581 } else {
582 destPathDelegate.mPath.append(srcPathDelegate.mPath, false);
583 }
584 }
585
586
587 /**
Diego Perezb9c48d82015-12-18 16:01:24 +0000588 * Returns whether the path already contains any points.
589 * Note that this is different to
590 * {@link #isEmpty} because if all elements are {@link PathIterator#SEG_MOVETO},
591 * {@link #isEmpty} will return true while hasPoints will return false.
Adam Lesinski282e1812014-01-23 18:17:42 -0800592 */
Diego Perezb9c48d82015-12-18 16:01:24 +0000593 public boolean hasPoints() {
594 return !mPath.getPathIterator(null).isDone();
595 }
596
597 /**
598 * Returns whether the path is empty (contains no lines or curves).
599 * @see Path#isEmpty
600 */
601 public boolean isEmpty() {
602 if (!mCachedIsEmpty) {
603 return false;
604 }
605
606 float[] coords = new float[6];
607 mCachedIsEmpty = Boolean.TRUE;
608 for (PathIterator it = mPath.getPathIterator(null); !it.isDone(); it.next()) {
609 int type = it.currentSegment(coords);
610 if (type != PathIterator.SEG_MOVETO) {
611 // Once we know that the path is not empty, we do not need to check again unless
612 // Path#reset is called.
613 mCachedIsEmpty = false;
614 return false;
615 }
616 }
617
618 return true;
Adam Lesinski282e1812014-01-23 18:17:42 -0800619 }
620
621 /**
622 * Fills the given {@link RectF} with the path bounds.
623 * @param bounds the RectF to be filled.
624 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000625 public void fillBounds(RectF bounds) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800626 Rectangle2D rect = mPath.getBounds2D();
627 bounds.left = (float)rect.getMinX();
628 bounds.right = (float)rect.getMaxX();
629 bounds.top = (float)rect.getMinY();
630 bounds.bottom = (float)rect.getMaxY();
631 }
632
633 /**
634 * Set the beginning of the next contour to the point (x,y).
635 *
636 * @param x The x-coordinate of the start of a new contour
637 * @param y The y-coordinate of the start of a new contour
638 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000639 public void moveTo(float x, float y) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800640 mPath.moveTo(mLastX = x, mLastY = y);
641 }
642
643 /**
644 * Set the beginning of the next contour relative to the last point on the
645 * previous contour. If there is no previous contour, this is treated the
646 * same as moveTo().
647 *
648 * @param dx The amount to add to the x-coordinate of the end of the
649 * previous contour, to specify the start of a new contour
650 * @param dy The amount to add to the y-coordinate of the end of the
651 * previous contour, to specify the start of a new contour
652 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000653 public void rMoveTo(float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800654 dx += mLastX;
655 dy += mLastY;
656 mPath.moveTo(mLastX = dx, mLastY = dy);
657 }
658
659 /**
660 * Add a line from the last point to the specified point (x,y).
661 * If no moveTo() call has been made for this contour, the first point is
662 * automatically set to (0,0).
663 *
664 * @param x The x-coordinate of the end of a line
665 * @param y The y-coordinate of the end of a line
666 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000667 public void lineTo(float x, float y) {
Diego Perezb9c48d82015-12-18 16:01:24 +0000668 if (!hasPoints()) {
Deepanshu Gupta3b379562014-07-17 09:33:04 -0700669 mPath.moveTo(mLastX = 0, mLastY = 0);
670 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800671 mPath.lineTo(mLastX = x, mLastY = y);
672 }
673
674 /**
675 * Same as lineTo, but the coordinates are considered relative to the last
676 * point on this contour. If there is no previous point, then a moveTo(0,0)
677 * is inserted automatically.
678 *
679 * @param dx The amount to add to the x-coordinate of the previous point on
680 * this contour, to specify a line
681 * @param dy The amount to add to the y-coordinate of the previous point on
682 * this contour, to specify a line
683 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000684 public void rLineTo(float dx, float dy) {
Diego Perezb9c48d82015-12-18 16:01:24 +0000685 if (!hasPoints()) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800686 mPath.moveTo(mLastX = 0, mLastY = 0);
687 }
Diego Perezb9c48d82015-12-18 16:01:24 +0000688
689 if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
690 // The delta is so small that this shouldn't generate a line
691 return;
692 }
693
Adam Lesinski282e1812014-01-23 18:17:42 -0800694 dx += mLastX;
695 dy += mLastY;
696 mPath.lineTo(mLastX = dx, mLastY = dy);
697 }
698
699 /**
700 * Add a quadratic bezier from the last point, approaching control point
701 * (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
702 * this contour, the first point is automatically set to (0,0).
703 *
704 * @param x1 The x-coordinate of the control point on a quadratic curve
705 * @param y1 The y-coordinate of the control point on a quadratic curve
706 * @param x2 The x-coordinate of the end point on a quadratic curve
707 * @param y2 The y-coordinate of the end point on a quadratic curve
708 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000709 public void quadTo(float x1, float y1, float x2, float y2) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800710 mPath.quadTo(x1, y1, mLastX = x2, mLastY = y2);
711 }
712
713 /**
714 * Same as quadTo, but the coordinates are considered relative to the last
715 * point on this contour. If there is no previous point, then a moveTo(0,0)
716 * is inserted automatically.
717 *
718 * @param dx1 The amount to add to the x-coordinate of the last point on
719 * this contour, for the control point of a quadratic curve
720 * @param dy1 The amount to add to the y-coordinate of the last point on
721 * this contour, for the control point of a quadratic curve
722 * @param dx2 The amount to add to the x-coordinate of the last point on
723 * this contour, for the end point of a quadratic curve
724 * @param dy2 The amount to add to the y-coordinate of the last point on
725 * this contour, for the end point of a quadratic curve
726 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000727 public void rQuadTo(float dx1, float dy1, float dx2, float dy2) {
Diego Perezb9c48d82015-12-18 16:01:24 +0000728 if (!hasPoints()) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800729 mPath.moveTo(mLastX = 0, mLastY = 0);
730 }
731 dx1 += mLastX;
732 dy1 += mLastY;
733 dx2 += mLastX;
734 dy2 += mLastY;
735 mPath.quadTo(dx1, dy1, mLastX = dx2, mLastY = dy2);
736 }
737
738 /**
739 * Add a cubic bezier from the last point, approaching control points
740 * (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
741 * made for this contour, the first point is automatically set to (0,0).
742 *
743 * @param x1 The x-coordinate of the 1st control point on a cubic curve
744 * @param y1 The y-coordinate of the 1st control point on a cubic curve
745 * @param x2 The x-coordinate of the 2nd control point on a cubic curve
746 * @param y2 The y-coordinate of the 2nd control point on a cubic curve
747 * @param x3 The x-coordinate of the end point on a cubic curve
748 * @param y3 The y-coordinate of the end point on a cubic curve
749 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000750 public void cubicTo(float x1, float y1, float x2, float y2,
Adam Lesinski282e1812014-01-23 18:17:42 -0800751 float x3, float y3) {
Diego Perezb9c48d82015-12-18 16:01:24 +0000752 if (!hasPoints()) {
Diego Perez081cebf2015-10-07 12:33:44 +0100753 mPath.moveTo(0, 0);
754 }
Adam Lesinski282e1812014-01-23 18:17:42 -0800755 mPath.curveTo(x1, y1, x2, y2, mLastX = x3, mLastY = y3);
756 }
757
758 /**
759 * Same as cubicTo, but the coordinates are considered relative to the
760 * current point on this contour. If there is no previous point, then a
761 * moveTo(0,0) is inserted automatically.
762 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000763 public void rCubicTo(float dx1, float dy1, float dx2, float dy2,
Adam Lesinski282e1812014-01-23 18:17:42 -0800764 float dx3, float dy3) {
Diego Perezb9c48d82015-12-18 16:01:24 +0000765 if (!hasPoints()) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800766 mPath.moveTo(mLastX = 0, mLastY = 0);
767 }
768 dx1 += mLastX;
769 dy1 += mLastY;
770 dx2 += mLastX;
771 dy2 += mLastY;
772 dx3 += mLastX;
773 dy3 += mLastY;
774 mPath.curveTo(dx1, dy1, dx2, dy2, mLastX = dx3, mLastY = dy3);
775 }
776
777 /**
778 * Append the specified arc to the path as a new contour. If the start of
779 * the path is different from the path's current last point, then an
780 * automatic lineTo() is added to connect the current contour to the
781 * start of the arc. However, if the path is empty, then we call moveTo()
782 * with the first point of the arc. The sweep angle is tread mod 360.
783 *
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700784 * @param left The left of oval defining shape and size of the arc
785 * @param top The top of oval defining shape and size of the arc
786 * @param right The right of oval defining shape and size of the arc
787 * @param bottom The bottom of oval defining shape and size of the arc
Adam Lesinski282e1812014-01-23 18:17:42 -0800788 * @param startAngle Starting angle (in degrees) where the arc begins
789 * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated
790 * mod 360.
791 * @param forceMoveTo If true, always begin a new contour with the arc
792 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000793 public void arcTo(float left, float top, float right, float bottom, float startAngle,
Deepanshu Guptabb5d0cc2014-06-25 16:57:03 -0700794 float sweepAngle,
795 boolean forceMoveTo) {
796 Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle,
Adam Lesinski282e1812014-01-23 18:17:42 -0800797 -sweepAngle, Arc2D.OPEN);
798 mPath.append(arc, true /*connect*/);
799
800 resetLastPointFromPath();
801 }
802
803 /**
804 * Close the current contour. If the current point is not equal to the
805 * first point of the contour, a line segment is automatically added.
806 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000807 public void close() {
Adam Lesinski282e1812014-01-23 18:17:42 -0800808 mPath.closePath();
809 }
810
811 private void resetLastPointFromPath() {
812 Point2D last = mPath.getCurrentPoint();
813 mLastX = (float) last.getX();
814 mLastY = (float) last.getY();
815 }
816
817 /**
818 * Add a closed rectangle contour to the path
819 *
820 * @param left The left side of a rectangle to add to the path
821 * @param top The top of a rectangle to add to the path
822 * @param right The right side of a rectangle to add to the path
823 * @param bottom The bottom of a rectangle to add to the path
824 * @param dir The direction to wind the rectangle's contour
825 */
Diego Perez5d1013c2016-02-23 12:19:11 +0000826 public void addRect(float left, float top, float right, float bottom,
Adam Lesinski282e1812014-01-23 18:17:42 -0800827 int dir) {
828 moveTo(left, top);
829
830 Direction direction = getDirection(dir);
831
832 switch (direction) {
833 case CW:
834 lineTo(right, top);
835 lineTo(right, bottom);
836 lineTo(left, bottom);
837 break;
838 case CCW:
839 lineTo(left, bottom);
840 lineTo(right, bottom);
841 lineTo(right, top);
842 break;
843 }
844
845 close();
846
847 resetLastPointFromPath();
848 }
849
850 /**
851 * Offset the path by (dx,dy), returning true on success
852 *
853 * @param dx The amount in the X direction to offset the entire path
854 * @param dy The amount in the Y direction to offset the entire path
Adam Lesinski282e1812014-01-23 18:17:42 -0800855 */
Diego Perez8f43dfe2016-04-04 10:18:55 +0100856 public void offset(float dx, float dy) {
Adam Lesinski282e1812014-01-23 18:17:42 -0800857 GeneralPath newPath = new GeneralPath();
858
859 PathIterator iterator = mPath.getPathIterator(new AffineTransform(0, 0, dx, 0, 0, dy));
860
861 newPath.append(iterator, false /*connect*/);
Diego Perez8f43dfe2016-04-04 10:18:55 +0100862 mPath = newPath;
Adam Lesinski282e1812014-01-23 18:17:42 -0800863 }
864
865 /**
866 * Transform the points in this path by matrix, and write the answer
867 * into dst. If dst is null, then the the original path is modified.
868 *
869 * @param matrix The matrix to apply to the path
870 * @param dst The transformed path is written here. If dst is null,
871 * then the the original path is modified
872 */
873 public void transform(Matrix_Delegate matrix, Path_Delegate dst) {
874 if (matrix.hasPerspective()) {
875 assert false;
876 Bridge.getLog().fidelityWarning(LayoutLog.TAG_MATRIX_AFFINE,
877 "android.graphics.Path#transform() only " +
878 "supports affine transformations.", null, null /*data*/);
879 }
880
881 GeneralPath newPath = new GeneralPath();
882
883 PathIterator iterator = mPath.getPathIterator(matrix.getAffineTransform());
884
885 newPath.append(iterator, false /*connect*/);
886
887 if (dst != null) {
888 dst.mPath = newPath;
889 } else {
890 mPath = newPath;
891 }
892 }
893}