Line data Source code
1 : /*
2 : * Copyright 2006 The Android Open Source Project
3 : *
4 : * Use of this source code is governed by a BSD-style license that can be
5 : * found in the LICENSE file.
6 : */
7 :
8 : #ifndef SkPath_DEFINED
9 : #define SkPath_DEFINED
10 :
11 : #include "SkMatrix.h"
12 : #include "SkPathRef.h"
13 : #include "SkRefCnt.h"
14 :
15 : class SkReader32;
16 : class SkWriter32;
17 : class SkAutoPathBoundsUpdate;
18 : class SkString;
19 : class SkRRect;
20 : class SkWStream;
21 :
22 : /** \class SkPath
23 :
24 : The SkPath class encapsulates compound (multiple contour) geometric paths
25 : consisting of straight line segments, quadratic curves, and cubic curves.
26 :
27 : SkPath is not thread safe unless you've first called SkPath::updateBoundsCache().
28 : */
29 : class SK_API SkPath {
30 : public:
31 : enum Direction {
32 : /** clockwise direction for adding closed contours */
33 : kCW_Direction,
34 : /** counter-clockwise direction for adding closed contours */
35 : kCCW_Direction,
36 : };
37 :
38 : SkPath();
39 : SkPath(const SkPath&);
40 : ~SkPath();
41 :
42 : SkPath& operator=(const SkPath&);
43 : friend SK_API bool operator==(const SkPath&, const SkPath&);
44 : friend bool operator!=(const SkPath& a, const SkPath& b) {
45 : return !(a == b);
46 : }
47 :
48 : /** Return true if the paths contain an equal array of verbs and weights. Paths
49 : * with equal verb counts can be readily interpolated. If the paths contain one
50 : * or more conics, the conics' weights must also match.
51 : *
52 : * @param compare The path to compare.
53 : *
54 : * @return true if the paths have the same verbs and weights.
55 : */
56 : bool isInterpolatable(const SkPath& compare) const;
57 :
58 : /** Interpolate between two paths with same-sized point arrays.
59 : * The out path contains the verbs and weights of this path.
60 : * The out points are a weighted average of this path and the ending path.
61 : *
62 : * @param ending The path to interpolate between.
63 : * @param weight The weight, from 0 to 1. The output points are set to
64 : * (this->points * weight) + ending->points * (1 - weight).
65 : * @return true if the paths could be interpolated.
66 : */
67 : bool interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const;
68 :
69 : #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
70 : /** Returns true if the caller is the only owner of the underlying path data */
71 : bool unique() const { return fPathRef->unique(); }
72 : #endif
73 :
74 : enum FillType {
75 : /** Specifies that "inside" is computed by a non-zero sum of signed
76 : edge crossings
77 : */
78 : kWinding_FillType,
79 : /** Specifies that "inside" is computed by an odd number of edge
80 : crossings
81 : */
82 : kEvenOdd_FillType,
83 : /** Same as Winding, but draws outside of the path, rather than inside
84 : */
85 : kInverseWinding_FillType,
86 : /** Same as EvenOdd, but draws outside of the path, rather than inside
87 : */
88 : kInverseEvenOdd_FillType
89 : };
90 :
91 : /** Return the path's fill type. This is used to define how "inside" is
92 : computed. The default value is kWinding_FillType.
93 :
94 : @return the path's fill type
95 : */
96 74 : FillType getFillType() const { return (FillType)fFillType; }
97 :
98 : /** Set the path's fill type. This is used to define how "inside" is
99 : computed. The default value is kWinding_FillType.
100 :
101 : @param ft The new fill type for this path
102 : */
103 112 : void setFillType(FillType ft) {
104 112 : fFillType = SkToU8(ft);
105 112 : }
106 :
107 : /** Returns true if the filltype is one of the Inverse variants */
108 836 : bool isInverseFillType() const { return IsInverseFillType((FillType)fFillType); }
109 :
110 : /**
111 : * Toggle between inverse and normal filltypes. This reverse the return
112 : * value of isInverseFillType()
113 : */
114 0 : void toggleInverseFillType() {
115 0 : fFillType ^= 2;
116 0 : }
117 :
118 : enum Convexity {
119 : kUnknown_Convexity,
120 : kConvex_Convexity,
121 : kConcave_Convexity
122 : };
123 :
124 : /**
125 : * Return the path's convexity, as stored in the path. If it is currently unknown,
126 : * then this function will attempt to compute the convexity (and cache the result).
127 : */
128 626 : Convexity getConvexity() const {
129 626 : if (kUnknown_Convexity != fConvexity) {
130 513 : return static_cast<Convexity>(fConvexity);
131 : } else {
132 113 : return this->internalGetConvexity();
133 : }
134 : }
135 :
136 : /**
137 : * Return the currently cached value for convexity, even if that is set to
138 : * kUnknown_Convexity. Note: getConvexity() will automatically call
139 : * ComputeConvexity and cache its return value if the current setting is
140 : * kUnknown.
141 : */
142 0 : Convexity getConvexityOrUnknown() const { return (Convexity)fConvexity; }
143 :
144 : /**
145 : * Store a convexity setting in the path. There is no automatic check to
146 : * see if this value actually agrees with the return value that would be
147 : * computed by getConvexity().
148 : *
149 : * Note: even if this is set to a "known" value, if the path is later
150 : * changed (e.g. lineTo(), addRect(), etc.) then the cached value will be
151 : * reset to kUnknown_Convexity.
152 : */
153 : void setConvexity(Convexity);
154 :
155 : /**
156 : * Returns true if the path is flagged as being convex. This is not a
157 : * confirmed by any analysis, it is just the value set earlier.
158 : */
159 626 : bool isConvex() const {
160 626 : return kConvex_Convexity == this->getConvexity();
161 : }
162 :
163 : /**
164 : * Set the isConvex flag to true or false. Convex paths may draw faster if
165 : * this flag is set, though setting this to true on a path that is in fact
166 : * not convex can give undefined results when drawn. Paths default to
167 : * isConvex == false
168 : */
169 : SK_ATTR_DEPRECATED("use setConvexity")
170 : void setIsConvex(bool isConvex) {
171 : this->setConvexity(isConvex ? kConvex_Convexity : kConcave_Convexity);
172 : }
173 :
174 : /** Returns true if the path is an oval.
175 : *
176 : * @param rect returns the bounding rect of this oval. It's a circle
177 : * if the height and width are the same.
178 : * @param dir is the oval CCW (or CW if false).
179 : * @param start indicates where the contour starts on the oval (see
180 : * SkPath::addOval for intepretation of the index).
181 : * @return true if this path is an oval.
182 : * Tracking whether a path is an oval is considered an
183 : * optimization for performance and so some paths that are in
184 : * fact ovals can report false.
185 : */
186 39 : bool isOval(SkRect* rect, Direction* dir = nullptr,
187 : unsigned* start = nullptr) const {
188 39 : bool isCCW = false;
189 39 : bool result = fPathRef->isOval(rect, &isCCW, start);
190 39 : if (dir && result) {
191 0 : *dir = isCCW ? kCCW_Direction : kCW_Direction;
192 : }
193 39 : return result;
194 : }
195 :
196 : /** Returns true if the path is a round rect.
197 : *
198 : * @param rrect Returns the bounding rect and radii of this round rect.
199 : * @param dir is the rrect CCW (or CW if false).
200 : * @param start indicates where the contour starts on the rrect (see
201 : * SkPath::addRRect for intepretation of the index).
202 : *
203 : * @return true if this path is a round rect.
204 : * Tracking whether a path is a round rect is considered an
205 : * optimization for performance and so some paths that are in
206 : * fact round rects can report false.
207 : */
208 39 : bool isRRect(SkRRect* rrect, Direction* dir = nullptr,
209 : unsigned* start = nullptr) const {
210 39 : bool isCCW = false;
211 39 : bool result = fPathRef->isRRect(rrect, &isCCW, start);
212 39 : if (dir && result) {
213 0 : *dir = isCCW ? kCCW_Direction : kCW_Direction;
214 : }
215 39 : return result;
216 : }
217 :
218 : /** Clear any lines and curves from the path, making it empty. This frees up
219 : internal storage associated with those segments.
220 : On Android, does not change fSourcePath.
221 : */
222 : void reset();
223 :
224 : /** Similar to reset(), in that all lines and curves are removed from the
225 : path. However, any internal storage for those lines/curves is retained,
226 : making reuse of the path potentially faster.
227 : On Android, does not change fSourcePath.
228 : */
229 : void rewind();
230 :
231 : /** Returns true if the path is empty (contains no lines or curves)
232 :
233 : @return true if the path is empty (contains no lines or curves)
234 : */
235 113 : bool isEmpty() const {
236 113 : SkDEBUGCODE(this->validate();)
237 113 : return 0 == fPathRef->countVerbs();
238 : }
239 :
240 : /** Return true if the last contour of this path ends with a close verb.
241 : */
242 : bool isLastContourClosed() const;
243 :
244 : /**
245 : * Returns true if all of the points in this path are finite, meaning there
246 : * are no infinities and no NaNs.
247 : */
248 446 : bool isFinite() const {
249 446 : SkDEBUGCODE(this->validate();)
250 446 : return fPathRef->isFinite();
251 : }
252 :
253 : /** Returns true if the path is volatile (i.e. should not be cached by devices.)
254 : */
255 0 : bool isVolatile() const {
256 0 : return SkToBool(fIsVolatile);
257 : }
258 :
259 : /** Specify whether this path is volatile. Paths are not volatile by
260 : default. Temporary paths that are discarded or modified after use should be
261 : marked as volatile. This provides a hint to the device that the path
262 : should not be cached. Providing this hint when appropriate can
263 : improve performance by avoiding unnecessary overhead and resource
264 : consumption on the device.
265 : */
266 131 : void setIsVolatile(bool isVolatile) {
267 131 : fIsVolatile = isVolatile;
268 131 : }
269 :
270 : /** Test a line for zero length
271 :
272 : @return true if the line is of zero length; otherwise false.
273 : */
274 543 : static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) {
275 543 : return exact ? p1 == p2 : p1.equalsWithinTolerance(p2);
276 : }
277 :
278 : /** Test a quad for zero length
279 :
280 : @return true if the quad is of zero length; otherwise false.
281 : */
282 10 : static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
283 : const SkPoint& p3, bool exact) {
284 10 : return exact ? p1 == p2 && p2 == p3 : p1.equalsWithinTolerance(p2) &&
285 10 : p2.equalsWithinTolerance(p3);
286 : }
287 :
288 : /** Test a cubic curve for zero length
289 :
290 : @return true if the cubic is of zero length; otherwise false.
291 : */
292 231 : static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
293 : const SkPoint& p3, const SkPoint& p4, bool exact) {
294 231 : return exact ? p1 == p2 && p2 == p3 && p3 == p4 : p1.equalsWithinTolerance(p2) &&
295 0 : p2.equalsWithinTolerance(p3) &&
296 231 : p3.equalsWithinTolerance(p4);
297 : }
298 :
299 : /**
300 : * Returns true if the path specifies a single line (i.e. it contains just
301 : * a moveTo and a lineTo). If so, and line[] is not null, it sets the 2
302 : * points in line[] to the end-points of the line. If the path is not a
303 : * line, returns false and ignores line[].
304 : */
305 : bool isLine(SkPoint line[2]) const;
306 :
307 : /** Return the number of points in the path
308 : */
309 : int countPoints() const;
310 :
311 : /** Return the point at the specified index. If the index is out of range
312 : (i.e. is not 0 <= index < countPoints()) then the returned coordinates
313 : will be (0,0)
314 : */
315 : SkPoint getPoint(int index) const;
316 :
317 : /** Returns the number of points in the path. Up to max points are copied.
318 :
319 : @param points If not null, receives up to max points
320 : @param max The maximum number of points to copy into points
321 : @return the actual number of points in the path
322 : */
323 : int getPoints(SkPoint points[], int max) const;
324 :
325 : /** Return the number of verbs in the path
326 : */
327 : int countVerbs() const;
328 :
329 : /** Returns the number of verbs in the path. Up to max verbs are copied. The
330 : verbs are copied as one byte per verb.
331 :
332 : @param verbs If not null, receives up to max verbs
333 : @param max The maximum number of verbs to copy into verbs
334 : @return the actual number of verbs in the path
335 : */
336 : int getVerbs(uint8_t verbs[], int max) const;
337 :
338 : //! Swap contents of this and other. Guaranteed not to throw
339 : void swap(SkPath& other);
340 :
341 : /**
342 : * Returns the bounds of the path's points. If the path contains zero points/verbs, this
343 : * will return the "empty" rect [0, 0, 0, 0].
344 : * Note: this bounds may be larger than the actual shape, since curves
345 : * do not extend as far as their control points. Additionally this bound encompases all points,
346 : * even isolated moveTos either preceeding or following the last non-degenerate contour.
347 : */
348 940 : const SkRect& getBounds() const {
349 940 : return fPathRef->getBounds();
350 : }
351 :
352 : /** Calling this will, if the internal cache of the bounds is out of date,
353 : update it so that subsequent calls to getBounds will be instantaneous.
354 : This also means that any copies or simple transformations of the path
355 : will inherit the cached bounds.
356 : */
357 0 : void updateBoundsCache() const {
358 : // for now, just calling getBounds() is sufficient
359 0 : this->getBounds();
360 0 : }
361 :
362 : /**
363 : * Computes a bounds that is conservatively "snug" around the path. This assumes that the
364 : * path will be filled. It does not attempt to collapse away contours that are logically
365 : * empty (e.g. moveTo(x, y) + lineTo(x, y)) but will include them in the calculation.
366 : *
367 : * It differs from getBounds() in that it will look at the snug bounds of curves, whereas
368 : * getBounds() just returns the bounds of the control-points. Thus computing this may be
369 : * slower than just calling getBounds().
370 : *
371 : * If the path is empty (i.e. no points or verbs), it will return SkRect::MakeEmpty().
372 : */
373 : SkRect computeTightBounds() const;
374 :
375 : /**
376 : * Does a conservative test to see whether a rectangle is inside a path. Currently it only
377 : * will ever return true for single convex contour paths. The empty-status of the rect is not
378 : * considered (e.g. a rect that is a point can be inside a path). Points or line segments where
379 : * the rect edge touches the path border are not considered containment violations.
380 : */
381 : bool conservativelyContainsRect(const SkRect& rect) const;
382 :
383 : // Construction methods
384 :
385 : /** Hint to the path to prepare for adding more points. This can allow the
386 : path to more efficiently grow its storage.
387 :
388 : @param extraPtCount The number of extra points the path should
389 : preallocate for.
390 : */
391 : void incReserve(unsigned extraPtCount);
392 :
393 : /** Set the beginning of the next contour to the point (x,y).
394 :
395 : @param x The x-coordinate of the start of a new contour
396 : @param y The y-coordinate of the start of a new contour
397 : */
398 : void moveTo(SkScalar x, SkScalar y);
399 :
400 : /** Set the beginning of the next contour to the point
401 :
402 : @param p The start of a new contour
403 : */
404 126 : void moveTo(const SkPoint& p) {
405 126 : this->moveTo(p.fX, p.fY);
406 126 : }
407 :
408 : /** Set the beginning of the next contour relative to the last point on the
409 : previous contour. If there is no previous contour, this is treated the
410 : same as moveTo().
411 :
412 : @param dx The amount to add to the x-coordinate of the end of the
413 : previous contour, to specify the start of a new contour
414 : @param dy The amount to add to the y-coordinate of the end of the
415 : previous contour, to specify the start of a new contour
416 : */
417 : void rMoveTo(SkScalar dx, SkScalar dy);
418 :
419 : /** Add a line from the last point to the specified point (x,y). If no
420 : moveTo() call has been made for this contour, the first point is
421 : automatically set to (0,0).
422 :
423 : @param x The x-coordinate of the end of a line
424 : @param y The y-coordinate of the end of a line
425 : */
426 : void lineTo(SkScalar x, SkScalar y);
427 :
428 : /** Add a line from the last point to the specified point. If no moveTo()
429 : call has been made for this contour, the first point is automatically
430 : set to (0,0).
431 :
432 : @param p The end of a line
433 : */
434 373 : void lineTo(const SkPoint& p) {
435 373 : this->lineTo(p.fX, p.fY);
436 373 : }
437 :
438 : /** Same as lineTo, but the coordinates are considered relative to the last
439 : point on this contour. If there is no previous point, then a moveTo(0,0)
440 : is inserted automatically.
441 :
442 : @param dx The amount to add to the x-coordinate of the previous point
443 : on this contour, to specify a line
444 : @param dy The amount to add to the y-coordinate of the previous point
445 : on this contour, to specify a line
446 : */
447 : void rLineTo(SkScalar dx, SkScalar dy);
448 :
449 : /** Add a quadratic bezier from the last point, approaching control point
450 : (x1,y1), and ending at (x2,y2). If no moveTo() call has been made for
451 : this contour, the first point is automatically set to (0,0).
452 :
453 : @param x1 The x-coordinate of the control point on a quadratic curve
454 : @param y1 The y-coordinate of the control point on a quadratic curve
455 : @param x2 The x-coordinate of the end point on a quadratic curve
456 : @param y2 The y-coordinate of the end point on a quadratic curve
457 : */
458 : void quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2);
459 :
460 : /** Add a quadratic bezier from the last point, approaching control point
461 : p1, and ending at p2. If no moveTo() call has been made for this
462 : contour, the first point is automatically set to (0,0).
463 :
464 : @param p1 The control point on a quadratic curve
465 : @param p2 The end point on a quadratic curve
466 : */
467 8 : void quadTo(const SkPoint& p1, const SkPoint& p2) {
468 8 : this->quadTo(p1.fX, p1.fY, p2.fX, p2.fY);
469 8 : }
470 :
471 : /** Same as quadTo, but the coordinates are considered relative to the last
472 : point on this contour. If there is no previous point, then a moveTo(0,0)
473 : is inserted automatically.
474 :
475 : @param dx1 The amount to add to the x-coordinate of the last point on
476 : this contour, to specify the control point of a quadratic curve
477 : @param dy1 The amount to add to the y-coordinate of the last point on
478 : this contour, to specify the control point of a quadratic curve
479 : @param dx2 The amount to add to the x-coordinate of the last point on
480 : this contour, to specify the end point of a quadratic curve
481 : @param dy2 The amount to add to the y-coordinate of the last point on
482 : this contour, to specify the end point of a quadratic curve
483 : */
484 : void rQuadTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2);
485 :
486 : void conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
487 : SkScalar w);
488 0 : void conicTo(const SkPoint& p1, const SkPoint& p2, SkScalar w) {
489 0 : this->conicTo(p1.fX, p1.fY, p2.fX, p2.fY, w);
490 0 : }
491 : void rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2,
492 : SkScalar w);
493 :
494 : /** Add a cubic bezier from the last point, approaching control points
495 : (x1,y1) and (x2,y2), and ending at (x3,y3). If no moveTo() call has been
496 : made for this contour, the first point is automatically set to (0,0).
497 :
498 : @param x1 The x-coordinate of the 1st control point on a cubic curve
499 : @param y1 The y-coordinate of the 1st control point on a cubic curve
500 : @param x2 The x-coordinate of the 2nd control point on a cubic curve
501 : @param y2 The y-coordinate of the 2nd control point on a cubic curve
502 : @param x3 The x-coordinate of the end point on a cubic curve
503 : @param y3 The y-coordinate of the end point on a cubic curve
504 : */
505 : void cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
506 : SkScalar x3, SkScalar y3);
507 :
508 : /** Add a cubic bezier from the last point, approaching control points p1
509 : and p2, and ending at p3. If no moveTo() call has been made for this
510 : contour, the first point is automatically set to (0,0).
511 :
512 : @param p1 The 1st control point on a cubic curve
513 : @param p2 The 2nd control point on a cubic curve
514 : @param p3 The end point on a cubic curve
515 : */
516 0 : void cubicTo(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3) {
517 0 : this->cubicTo(p1.fX, p1.fY, p2.fX, p2.fY, p3.fX, p3.fY);
518 0 : }
519 :
520 : /** Same as cubicTo, but the coordinates are considered relative to the
521 : current point on this contour. If there is no previous point, then a
522 : moveTo(0,0) is inserted automatically.
523 :
524 : @param dx1 The amount to add to the x-coordinate of the last point on
525 : this contour, to specify the 1st control point of a cubic curve
526 : @param dy1 The amount to add to the y-coordinate of the last point on
527 : this contour, to specify the 1st control point of a cubic curve
528 : @param dx2 The amount to add to the x-coordinate of the last point on
529 : this contour, to specify the 2nd control point of a cubic curve
530 : @param dy2 The amount to add to the y-coordinate of the last point on
531 : this contour, to specify the 2nd control point of a cubic curve
532 : @param dx3 The amount to add to the x-coordinate of the last point on
533 : this contour, to specify the end point of a cubic curve
534 : @param dy3 The amount to add to the y-coordinate of the last point on
535 : this contour, to specify the end point of a cubic curve
536 : */
537 : void rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2,
538 : SkScalar x3, SkScalar y3);
539 :
540 : /**
541 : * Append the specified arc to the path. If the start of the arc is different from the path's
542 : * current last point, then an automatic lineTo() is added to connect the current contour
543 : * to the start of the arc. However, if the path is empty, then we call moveTo() with
544 : * the first point of the arc. The sweep angle is treated mod 360.
545 : *
546 : * @param oval The bounding oval defining the shape and size of the arc
547 : * @param startAngle Starting angle (in degrees) where the arc begins
548 : * @param sweepAngle Sweep angle (in degrees) measured clockwise. This is treated mod 360.
549 : * @param forceMoveTo If true, always begin a new contour with the arc
550 : */
551 : void arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo);
552 :
553 : /**
554 : * Append a line and arc to the current path. This is the same as the PostScript call "arct".
555 : */
556 : void arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius);
557 :
558 : /** Append a line and arc to the current path. This is the same as the
559 : PostScript call "arct".
560 : */
561 : void arcTo(const SkPoint p1, const SkPoint p2, SkScalar radius) {
562 : this->arcTo(p1.fX, p1.fY, p2.fX, p2.fY, radius);
563 : }
564 :
565 : enum ArcSize {
566 : /** the smaller of the two possible SVG arcs. */
567 : kSmall_ArcSize,
568 : /** the larger of the two possible SVG arcs. */
569 : kLarge_ArcSize,
570 : };
571 :
572 : /**
573 : * Append an elliptical arc from the current point in the format used by SVG.
574 : * The center of the ellipse is computed to satisfy the constraints below.
575 : *
576 : * @param rx,ry The radii in the x and y directions respectively.
577 : * @param xAxisRotate The angle in degrees relative to the x-axis.
578 : * @param largeArc Determines whether the smallest or largest arc possible
579 : * is drawn.
580 : * @param sweep Determines if the arc should be swept in an anti-clockwise or
581 : * clockwise direction. Note that this enum value is opposite the SVG
582 : * arc sweep value.
583 : * @param x,y The destination coordinates.
584 : */
585 : void arcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
586 : Direction sweep, SkScalar x, SkScalar y);
587 :
588 0 : void arcTo(const SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, Direction sweep,
589 : const SkPoint xy) {
590 0 : this->arcTo(r.fX, r.fY, xAxisRotate, largeArc, sweep, xy.fX, xy.fY);
591 0 : }
592 :
593 : /** Same as arcTo format used by SVG, but the destination coordinate is relative to the
594 : * last point on this contour. If there is no previous point, then a
595 : * moveTo(0,0) is inserted automatically.
596 : *
597 : * @param rx,ry The radii in the x and y directions respectively.
598 : * @param xAxisRotate The angle in degrees relative to the x-axis.
599 : * @param largeArc Determines whether the smallest or largest arc possible
600 : * is drawn.
601 : * @param sweep Determines if the arc should be swept in an anti-clockwise or
602 : * clockwise direction. Note that this enum value is opposite the SVG
603 : * arc sweep value.
604 : * @param dx,dy The destination coordinates relative to the last point.
605 : */
606 : void rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, ArcSize largeArc,
607 : Direction sweep, SkScalar dx, SkScalar dy);
608 :
609 : /** Close the current contour. If the current point is not equal to the
610 : first point of the contour, a line segment is automatically added.
611 : */
612 : void close();
613 :
614 : /**
615 : * Returns whether or not a fill type is inverted
616 : *
617 : * kWinding_FillType -> false
618 : * kEvenOdd_FillType -> false
619 : * kInverseWinding_FillType -> true
620 : * kInverseEvenOdd_FillType -> true
621 : */
622 868 : static bool IsInverseFillType(FillType fill) {
623 : static_assert(0 == kWinding_FillType, "fill_type_mismatch");
624 : static_assert(1 == kEvenOdd_FillType, "fill_type_mismatch");
625 : static_assert(2 == kInverseWinding_FillType, "fill_type_mismatch");
626 : static_assert(3 == kInverseEvenOdd_FillType, "fill_type_mismatch");
627 868 : return (fill & 2) != 0;
628 : }
629 :
630 : /**
631 : * Returns the equivalent non-inverted fill type to the given fill type
632 : *
633 : * kWinding_FillType -> kWinding_FillType
634 : * kEvenOdd_FillType -> kEvenOdd_FillType
635 : * kInverseWinding_FillType -> kWinding_FillType
636 : * kInverseEvenOdd_FillType -> kEvenOdd_FillType
637 : */
638 : static FillType ConvertToNonInverseFillType(FillType fill) {
639 : static_assert(0 == kWinding_FillType, "fill_type_mismatch");
640 : static_assert(1 == kEvenOdd_FillType, "fill_type_mismatch");
641 : static_assert(2 == kInverseWinding_FillType, "fill_type_mismatch");
642 : static_assert(3 == kInverseEvenOdd_FillType, "fill_type_mismatch");
643 : return (FillType)(fill & 1);
644 : }
645 :
646 : /**
647 : * Chop a conic into N quads, stored continguously in pts[], where
648 : * N = 1 << pow2. The amount of storage needed is (1 + 2 * N)
649 : */
650 : static int ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2,
651 : SkScalar w, SkPoint pts[], int pow2);
652 :
653 : /**
654 : * Returns true if the path specifies a rectangle.
655 : *
656 : * If this returns false, then all output parameters are ignored, and left
657 : * unchanged. If this returns true, then each of the output parameters
658 : * are checked for NULL. If they are not, they return their value.
659 : *
660 : * @param rect If not null, set to the bounds of the rectangle.
661 : * Note : this bounds may be smaller than the path's bounds, since it is just
662 : * the bounds of the "drawable" parts of the path. e.g. a trailing MoveTo would
663 : * be ignored in this rect, but not by the path's bounds
664 : * @param isClosed If not null, set to true if the path is closed
665 : * @param direction If not null, set to the rectangle's direction
666 : * @return true if the path specifies a rectangle
667 : */
668 : bool isRect(SkRect* rect, bool* isClosed = NULL, Direction* direction = NULL) const;
669 :
670 : /** Returns true if the path specifies a pair of nested rectangles, or would draw a
671 : pair of nested rectangles when filled. If so, and if
672 : rect is not null, set rect[0] to the outer rectangle and rect[1] to the inner
673 : rectangle. If so, and dirs is not null, set dirs[0] to the direction of
674 : the outer rectangle and dirs[1] to the direction of the inner rectangle. If
675 : the path does not specify a pair of nested rectangles, return
676 : false and ignore rect and dirs.
677 :
678 : @param rect If not null, returns the path as a pair of nested rectangles
679 : @param dirs If not null, returns the direction of the rects
680 : @return true if the path describes a pair of nested rectangles
681 : */
682 : bool isNestedFillRects(SkRect rect[2], Direction dirs[2] = NULL) const;
683 :
684 : /**
685 : * Add a closed rectangle contour to the path
686 : * @param rect The rectangle to add as a closed contour to the path
687 : * @param dir The direction to wind the rectangle's contour.
688 : *
689 : * Note: the contour initial point index is 0 (as defined below).
690 : */
691 : void addRect(const SkRect& rect, Direction dir = kCW_Direction);
692 :
693 : /**
694 : * Add a closed rectangle contour to the path
695 : * @param rect The rectangle to add as a closed contour to the path
696 : * @param dir The direction to wind the rectangle's contour.
697 : * @param start Initial point of the contour (initial moveTo), expressed as
698 : * a corner index, starting in the upper-left position, clock-wise:
699 : *
700 : * 0 1
701 : * *-------*
702 : * | |
703 : * *-------*
704 : * 3 2
705 : */
706 : void addRect(const SkRect& rect, Direction dir, unsigned start);
707 :
708 : /**
709 : * Add a closed rectangle contour to the path
710 : *
711 : * @param left The left side of a rectangle to add as a closed contour
712 : * to the path
713 : * @param top The top of a rectangle to add as a closed contour to the
714 : * path
715 : * @param right The right side of a rectangle to add as a closed contour
716 : * to the path
717 : * @param bottom The bottom of a rectangle to add as a closed contour to
718 : * the path
719 : * @param dir The direction to wind the rectangle's contour.
720 : *
721 : * Note: the contour initial point index is 0 (as defined above).
722 : */
723 : void addRect(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom,
724 : Direction dir = kCW_Direction);
725 :
726 : /**
727 : * Add a closed oval contour to the path
728 : *
729 : * @param oval The bounding oval to add as a closed contour to the path
730 : * @param dir The direction to wind the oval's contour.
731 : *
732 : * Note: the contour initial point index is 1 (as defined below).
733 : */
734 : void addOval(const SkRect& oval, Direction dir = kCW_Direction);
735 :
736 : /**
737 : * Add a closed oval contour to the path
738 : *
739 : * @param oval The bounding oval to add as a closed contour to the path
740 : * @param dir The direction to wind the oval's contour.
741 : * @param start Initial point of the contour (initial moveTo), expressed
742 : * as an ellipse vertex index, starting at the top, clock-wise
743 : * (90/0/270/180deg order):
744 : *
745 : * 0
746 : * -*-
747 : * | |
748 : * 3 * * 1
749 : * | |
750 : * -*-
751 : * 2
752 : */
753 : void addOval(const SkRect& oval, Direction dir, unsigned start);
754 :
755 : /**
756 : * Add a closed circle contour to the path. The circle contour begins at
757 : * the right-most point (as though 1 were passed to addOval's 'start' param).
758 : *
759 : * @param x The x-coordinate of the center of a circle to add as a
760 : * closed contour to the path
761 : * @param y The y-coordinate of the center of a circle to add as a
762 : * closed contour to the path
763 : * @param radius The radius of a circle to add as a closed contour to the
764 : * path
765 : * @param dir The direction to wind the circle's contour.
766 : */
767 : void addCircle(SkScalar x, SkScalar y, SkScalar radius,
768 : Direction dir = kCW_Direction);
769 :
770 : /** Add the specified arc to the path as a new contour.
771 :
772 : @param oval The bounds of oval used to define the size of the arc
773 : @param startAngle Starting angle (in degrees) where the arc begins
774 : @param sweepAngle Sweep angle (in degrees) measured clockwise
775 : */
776 : void addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle);
777 :
778 : /**
779 : * Add a closed round-rectangle contour to the path
780 : * @param rect The bounds of a round-rectangle to add as a closed contour
781 : * @param rx The x-radius of the rounded corners on the round-rectangle
782 : * @param ry The y-radius of the rounded corners on the round-rectangle
783 : * @param dir The direction to wind the rectangle's contour.
784 : */
785 : void addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry,
786 : Direction dir = kCW_Direction);
787 :
788 : /**
789 : * Add a closed round-rectangle contour to the path. Each corner receives
790 : * two radius values [X, Y]. The corners are ordered top-left, top-right,
791 : * bottom-right, bottom-left.
792 : * @param rect The bounds of a round-rectangle to add as a closed contour
793 : * @param radii Array of 8 scalars, 4 [X,Y] pairs for each corner
794 : * @param dir The direction to wind the rectangle's contour.
795 : * Note: The radii here now go through the same constraint handling as the
796 : * SkRRect radii (i.e., either radii at a corner being 0 implies a
797 : * sqaure corner and oversized radii are proportionally scaled down).
798 : */
799 : void addRoundRect(const SkRect& rect, const SkScalar radii[],
800 : Direction dir = kCW_Direction);
801 :
802 : /**
803 : * Add an SkRRect contour to the path
804 : * @param rrect The rounded rect to add as a closed contour
805 : * @param dir The winding direction for the new contour.
806 : *
807 : * Note: the contour initial point index is either 6 (for dir == kCW_Direction)
808 : * or 7 (for dir == kCCW_Direction), as defined below.
809 : *
810 : */
811 : void addRRect(const SkRRect& rrect, Direction dir = kCW_Direction);
812 :
813 : /**
814 : * Add an SkRRect contour to the path
815 : * @param rrect The rounded rect to add as a closed contour
816 : * @param dir The winding direction for the new contour.
817 : * @param start Initial point of the contour (initial moveTo), expressed as
818 : * an index of the radii minor/major points, ordered clock-wise:
819 : *
820 : * 0 1
821 : * *----*
822 : * 7 * * 2
823 : * | |
824 : * 6 * * 3
825 : * *----*
826 : * 5 4
827 : */
828 : void addRRect(const SkRRect& rrect, Direction dir, unsigned start);
829 :
830 : /**
831 : * Add a new contour made of just lines. This is just a fast version of
832 : * the following:
833 : * this->moveTo(pts[0]);
834 : * for (int i = 1; i < count; ++i) {
835 : * this->lineTo(pts[i]);
836 : * }
837 : * if (close) {
838 : * this->close();
839 : * }
840 : */
841 : void addPoly(const SkPoint pts[], int count, bool close);
842 :
843 : enum AddPathMode {
844 : /** Source path contours are added as new contours.
845 : */
846 : kAppend_AddPathMode,
847 : /** Path is added by extending the last contour of the destination path
848 : with the first contour of the source path. If the last contour of
849 : the destination path is closed, then it will not be extended.
850 : Instead, the start of source path will be extended by a straight
851 : line to the end point of the destination path.
852 : */
853 : kExtend_AddPathMode
854 : };
855 :
856 : /** Add a copy of src to the path, offset by (dx,dy)
857 : @param src The path to add as a new contour
858 : @param dx The amount to translate the path in X as it is added
859 : @param dx The amount to translate the path in Y as it is added
860 : */
861 : void addPath(const SkPath& src, SkScalar dx, SkScalar dy,
862 : AddPathMode mode = kAppend_AddPathMode);
863 :
864 : /** Add a copy of src to the path
865 : */
866 15 : void addPath(const SkPath& src, AddPathMode mode = kAppend_AddPathMode) {
867 : SkMatrix m;
868 15 : m.reset();
869 15 : this->addPath(src, m, mode);
870 15 : }
871 :
872 : /** Add a copy of src to the path, transformed by matrix
873 : @param src The path to add as a new contour
874 : @param matrix Transform applied to src
875 : @param mode Determines how path is added
876 : */
877 : void addPath(const SkPath& src, const SkMatrix& matrix, AddPathMode mode = kAppend_AddPathMode);
878 :
879 : /**
880 : * Same as addPath(), but reverses the src input
881 : */
882 : void reverseAddPath(const SkPath& src);
883 :
884 : /** Offset the path by (dx,dy), returning true on success
885 :
886 : @param dx The amount in the X direction to offset the entire path
887 : @param dy The amount in the Y direction to offset the entire path
888 : @param dst The translated path is written here
889 : */
890 : void offset(SkScalar dx, SkScalar dy, SkPath* dst) const;
891 :
892 : /** Offset the path by (dx,dy), returning true on success
893 :
894 : @param dx The amount in the X direction to offset the entire path
895 : @param dy The amount in the Y direction to offset the entire path
896 : */
897 0 : void offset(SkScalar dx, SkScalar dy) {
898 0 : this->offset(dx, dy, this);
899 0 : }
900 :
901 : /** Transform the points in this path by matrix, and write the answer into
902 : dst.
903 :
904 : @param matrix The matrix to apply to the path
905 : @param dst The transformed path is written here
906 : */
907 : void transform(const SkMatrix& matrix, SkPath* dst) const;
908 :
909 : /** Transform the points in this path by matrix
910 :
911 : @param matrix The matrix to apply to the path
912 : */
913 0 : void transform(const SkMatrix& matrix) {
914 0 : this->transform(matrix, this);
915 0 : }
916 :
917 : /** Return the last point on the path. If no points have been added, (0,0)
918 : is returned. If there are no points, this returns false, otherwise it
919 : returns true.
920 :
921 : @param lastPt The last point on the path is returned here
922 : */
923 : bool getLastPt(SkPoint* lastPt) const;
924 :
925 : /** Set the last point on the path. If no points have been added,
926 : moveTo(x,y) is automatically called.
927 :
928 : @param x The new x-coordinate for the last point
929 : @param y The new y-coordinate for the last point
930 : */
931 : void setLastPt(SkScalar x, SkScalar y);
932 :
933 : /** Set the last point on the path. If no points have been added, moveTo(p)
934 : is automatically called.
935 :
936 : @param p The new location for the last point
937 : */
938 : void setLastPt(const SkPoint& p) {
939 : this->setLastPt(p.fX, p.fY);
940 : }
941 :
942 : enum SegmentMask {
943 : kLine_SegmentMask = 1 << 0,
944 : kQuad_SegmentMask = 1 << 1,
945 : kConic_SegmentMask = 1 << 2,
946 : kCubic_SegmentMask = 1 << 3,
947 : };
948 :
949 : /**
950 : * Returns a mask, where each bit corresponding to a SegmentMask is
951 : * set if the path contains 1 or more segments of that type.
952 : * Returns 0 for an empty path (no segments).
953 : */
954 324 : uint32_t getSegmentMasks() const { return fPathRef->getSegmentMasks(); }
955 :
956 : enum Verb {
957 : kMove_Verb, //!< iter.next returns 1 point
958 : kLine_Verb, //!< iter.next returns 2 points
959 : kQuad_Verb, //!< iter.next returns 3 points
960 : kConic_Verb, //!< iter.next returns 3 points + iter.conicWeight()
961 : kCubic_Verb, //!< iter.next returns 4 points
962 : kClose_Verb, //!< iter.next returns 0 points
963 : kDone_Verb, //!< iter.next returns 0 points
964 : };
965 :
966 : /** Iterate through all of the segments (lines, quadratics, cubics) of
967 : each contours in a path.
968 :
969 : The iterator cleans up the segments along the way, removing degenerate
970 : segments and adding close verbs where necessary. When the forceClose
971 : argument is provided, each contour (as defined by a new starting
972 : move command) will be completed with a close verb regardless of the
973 : contour's contents.
974 : */
975 : class SK_API Iter {
976 : public:
977 : Iter();
978 : Iter(const SkPath&, bool forceClose);
979 :
980 : void setPath(const SkPath&, bool forceClose);
981 :
982 : /** Return the next verb in this iteration of the path. When all
983 : segments have been visited, return kDone_Verb.
984 :
985 : @param pts The points representing the current verb and/or segment
986 : @param doConsumeDegerates If true, first scan for segments that are
987 : deemed degenerate (too short) and skip those.
988 : @param exact if doConsumeDegenerates is true and exact is true, skip only
989 : degenerate elements with lengths exactly equal to zero. If exact
990 : is false, skip degenerate elements with lengths close to zero. If
991 : doConsumeDegenerates is false, exact has no effect.
992 : @return The verb for the current segment
993 : */
994 3642 : Verb next(SkPoint pts[4], bool doConsumeDegerates = true, bool exact = false) {
995 3642 : if (doConsumeDegerates) {
996 963 : this->consumeDegenerateSegments(exact);
997 : }
998 3642 : return this->doNext(pts);
999 : }
1000 :
1001 : /**
1002 : * Return the weight for the current conic. Only valid if the current
1003 : * segment return by next() was a conic.
1004 : */
1005 0 : SkScalar conicWeight() const { return *fConicWeights; }
1006 :
1007 : /** If next() returns kLine_Verb, then this query returns true if the
1008 : line was the result of a close() command (i.e. the end point is the
1009 : initial moveto for this contour). If next() returned a different
1010 : verb, this returns an undefined value.
1011 :
1012 : @return If the last call to next() returned kLine_Verb, return true
1013 : if it was the result of an explicit close command.
1014 : */
1015 : bool isCloseLine() const { return SkToBool(fCloseLine); }
1016 :
1017 : /** Returns true if the current contour is closed (has a kClose_Verb)
1018 : @return true if the current contour is closed (has a kClose_Verb)
1019 : */
1020 : bool isClosedContour() const;
1021 :
1022 : private:
1023 : const SkPoint* fPts;
1024 : const uint8_t* fVerbs;
1025 : const uint8_t* fVerbStop;
1026 : const SkScalar* fConicWeights;
1027 : SkPoint fMoveTo;
1028 : SkPoint fLastPt;
1029 : SkBool8 fForceClose;
1030 : SkBool8 fNeedClose;
1031 : SkBool8 fCloseLine;
1032 : SkBool8 fSegmentState;
1033 :
1034 : inline const SkPoint& cons_moveTo();
1035 : Verb autoClose(SkPoint pts[2]);
1036 : void consumeDegenerateSegments(bool exact);
1037 : Verb doNext(SkPoint pts[4]);
1038 : };
1039 :
1040 : /** Iterate through the verbs in the path, providing the associated points.
1041 : */
1042 : class SK_API RawIter {
1043 : public:
1044 : RawIter() {}
1045 63 : RawIter(const SkPath& path) {
1046 63 : setPath(path);
1047 63 : }
1048 :
1049 63 : void setPath(const SkPath& path) {
1050 63 : fRawIter.setPathRef(*path.fPathRef.get());
1051 63 : }
1052 :
1053 : /** Return the next verb in this iteration of the path. When all
1054 : segments have been visited, return kDone_Verb.
1055 :
1056 : @param pts The points representing the current verb and/or segment
1057 : This must not be NULL.
1058 : @return The verb for the current segment
1059 : */
1060 1220 : Verb next(SkPoint pts[4]) {
1061 1220 : return (Verb) fRawIter.next(pts);
1062 : }
1063 :
1064 : /** Return what the next verb will be, but do not visit the next segment.
1065 :
1066 : @return The verb for the next segment
1067 : */
1068 0 : Verb peek() const {
1069 0 : return (Verb) fRawIter.peek();
1070 : }
1071 :
1072 0 : SkScalar conicWeight() const {
1073 0 : return fRawIter.conicWeight();
1074 : }
1075 :
1076 : private:
1077 : SkPathRef::Iter fRawIter;
1078 : friend class SkPath;
1079 : };
1080 :
1081 : /**
1082 : * Returns true if the point { x, y } is contained by the path, taking into
1083 : * account the FillType.
1084 : */
1085 : bool contains(SkScalar x, SkScalar y) const;
1086 :
1087 : void dump(SkWStream* , bool forceClose, bool dumpAsHex) const;
1088 : void dump() const;
1089 : void dumpHex() const;
1090 :
1091 : /**
1092 : * Write the path to the buffer, and return the number of bytes written.
1093 : * If buffer is NULL, it still returns the number of bytes.
1094 : */
1095 : size_t writeToMemory(void* buffer) const;
1096 : /**
1097 : * Initializes the path from the buffer
1098 : *
1099 : * @param buffer Memory to read from
1100 : * @param length Amount of memory available in the buffer
1101 : * @return number of bytes read (must be a multiple of 4) or
1102 : * 0 if there was not enough memory available
1103 : */
1104 : size_t readFromMemory(const void* buffer, size_t length);
1105 :
1106 : /** Returns a non-zero, globally unique value corresponding to the set of verbs
1107 : and points in the path (but not the fill type [except on Android skbug.com/1762]).
1108 : Each time the path is modified, a different generation ID will be returned.
1109 : */
1110 : uint32_t getGenerationID() const;
1111 :
1112 : #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK
1113 : static const int kPathRefGenIDBitCnt = 30; // leave room for the fill type (skbug.com/1762)
1114 : #else
1115 : static const int kPathRefGenIDBitCnt = 32;
1116 : #endif
1117 :
1118 : SkDEBUGCODE(void validate() const;)
1119 : SkDEBUGCODE(void experimentalValidateRef() const { fPathRef->validate(); } )
1120 :
1121 : private:
1122 : enum SerializationOffsets {
1123 : // 1 free bit at 29
1124 : kUnused1_SerializationShift = 28, // 1 free bit
1125 : kDirection_SerializationShift = 26, // requires 2 bits
1126 : kIsVolatile_SerializationShift = 25, // requires 1 bit
1127 : // 1 free bit at 24
1128 : kConvexity_SerializationShift = 16, // requires 8 bits
1129 : kFillType_SerializationShift = 8, // requires 8 bits
1130 : // low-8-bits are version
1131 : };
1132 :
1133 : enum SerializationVersions {
1134 : kPathPrivFirstDirection_Version = 1,
1135 : kPathPrivLastMoveToIndex_Version = 2,
1136 : kCurrent_Version = 2
1137 : };
1138 :
1139 : sk_sp<SkPathRef> fPathRef;
1140 : int fLastMoveToIndex;
1141 : uint8_t fFillType;
1142 : mutable uint8_t fConvexity;
1143 : mutable SkAtomic<uint8_t, sk_memory_order_relaxed> fFirstDirection;// SkPathPriv::FirstDirection
1144 : SkBool8 fIsVolatile;
1145 :
1146 : /** Resets all fields other than fPathRef to their initial 'empty' values.
1147 : * Assumes the caller has already emptied fPathRef.
1148 : * On Android increments fGenerationID without reseting it.
1149 : */
1150 : void resetFields();
1151 :
1152 : /** Sets all fields other than fPathRef to the values in 'that'.
1153 : * Assumes the caller has already set fPathRef.
1154 : * Doesn't change fGenerationID or fSourcePath on Android.
1155 : */
1156 : void copyFields(const SkPath& that);
1157 :
1158 : friend class Iter;
1159 : friend class SkPathPriv;
1160 : friend class SkPathStroker;
1161 :
1162 : /* Append, in reverse order, the first contour of path, ignoring path's
1163 : last point. If no moveTo() call has been made for this contour, the
1164 : first point is automatically set to (0,0).
1165 : */
1166 : void reversePathTo(const SkPath&);
1167 :
1168 : // called before we add points for lineTo, quadTo, cubicTo, checking to see
1169 : // if we need to inject a leading moveTo first
1170 : //
1171 : // SkPath path; path.lineTo(...); <--- need a leading moveTo(0, 0)
1172 : // SkPath path; ... path.close(); path.lineTo(...) <-- need a moveTo(previous moveTo)
1173 : //
1174 : inline void injectMoveToIfNeeded();
1175 :
1176 : inline bool hasOnlyMoveTos() const;
1177 :
1178 : Convexity internalGetConvexity() const;
1179 :
1180 : bool isRectContour(bool allowPartial, int* currVerb, const SkPoint** pts,
1181 : bool* isClosed, Direction* direction) const;
1182 :
1183 : // called by stroker to see if all points are equal and worthy of a cap
1184 : // equivalent to a short-circuit version of getBounds().isEmpty()
1185 : bool isZeroLength() const;
1186 :
1187 : /** Returns if the path can return a bound at no cost (true) or will have to
1188 : perform some computation (false).
1189 : */
1190 113 : bool hasComputedBounds() const {
1191 113 : SkDEBUGCODE(this->validate();)
1192 113 : return fPathRef->hasComputedBounds();
1193 : }
1194 :
1195 :
1196 : // 'rect' needs to be sorted
1197 113 : void setBounds(const SkRect& rect) {
1198 226 : SkPathRef::Editor ed(&fPathRef);
1199 :
1200 113 : ed.setBounds(rect);
1201 113 : }
1202 :
1203 : void setPt(int index, SkScalar x, SkScalar y);
1204 :
1205 : friend class SkAutoPathBoundsUpdate;
1206 : friend class SkAutoDisableOvalCheck;
1207 : friend class SkAutoDisableDirectionCheck;
1208 : friend class SkPathWriter;
1209 : friend class SkOpBuilder;
1210 : friend class SkBench_AddPathTest; // perf test reversePathTo
1211 : friend class PathTest_Private; // unit test reversePathTo
1212 : friend class ForceIsRRect_Private; // unit test isRRect
1213 : };
1214 :
1215 : #endif
|