blob: 757fa3181c0203d2eb2130197d7ab487c2d30e11 [file] [log] [blame]
caryclark@google.com07393ca2013-04-08 11:47:37 +00001/*
2 * Copyright 2012 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
Mike Kleinc0bd9f92019-04-23 12:05:21 -05007#include "src/core/SkGeometry.h"
Chris Dalton957189b2020-05-07 12:47:26 -06008#include "src/core/SkPathPriv.h"
Mike Kleinc0bd9f92019-04-23 12:05:21 -05009#include "src/pathops/SkOpEdgeBuilder.h"
10#include "src/pathops/SkReduceOrder.h"
caryclark@google.com07393ca2013-04-08 11:47:37 +000011
12void SkOpEdgeBuilder::init() {
caryclark@google.com07393ca2013-04-08 11:47:37 +000013 fOperand = false;
Mike Reed7d34dc72019-11-26 12:17:17 -050014 fXorMask[0] = fXorMask[1] = ((int)fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask
caryclark@google.com07393ca2013-04-08 11:47:37 +000015 : kWinding_PathOpsMask;
caryclark@google.com66560ca2013-04-26 19:51:16 +000016 fUnparseable = false;
caryclark@google.com07393ca2013-04-08 11:47:37 +000017 fSecondHalf = preFetch();
18}
19
caryclark27c015d2016-09-23 05:47:20 -070020// very tiny points cause numerical instability : don't allow them
Chris Dalton1fc1bd32020-05-06 11:44:51 -060021static SkPoint force_small_to_zero(const SkPoint& pt) {
22 SkPoint ret = pt;
23 if (SkScalarAbs(ret.fX) < FLT_EPSILON_ORDERABLE_ERR) {
24 ret.fX = 0;
caryclark27c015d2016-09-23 05:47:20 -070025 }
Chris Dalton1fc1bd32020-05-06 11:44:51 -060026 if (SkScalarAbs(ret.fY) < FLT_EPSILON_ORDERABLE_ERR) {
27 ret.fY = 0;
caryclark27c015d2016-09-23 05:47:20 -070028 }
Chris Dalton1fc1bd32020-05-06 11:44:51 -060029 return ret;
caryclark27c015d2016-09-23 05:47:20 -070030}
31
32static bool can_add_curve(SkPath::Verb verb, SkPoint* curve) {
33 if (SkPath::kMove_Verb == verb) {
34 return false;
35 }
caryclarkcc093722016-09-23 09:32:26 -070036 for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) {
Chris Dalton1fc1bd32020-05-06 11:44:51 -060037 curve[index] = force_small_to_zero(curve[index]);
caryclark27c015d2016-09-23 05:47:20 -070038 }
39 return SkPath::kLine_Verb != verb || !SkDPoint::ApproximatelyEqual(curve[0], curve[1]);
40}
41
caryclark@google.com07393ca2013-04-08 11:47:37 +000042void SkOpEdgeBuilder::addOperand(const SkPath& path) {
43 SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb);
caryclark54359292015-03-26 07:52:43 -070044 fPathVerbs.pop();
caryclark@google.com07393ca2013-04-08 11:47:37 +000045 fPath = &path;
Mike Reed7d34dc72019-11-26 12:17:17 -050046 fXorMask[1] = ((int)fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask
caryclark@google.com07393ca2013-04-08 11:47:37 +000047 : kWinding_PathOpsMask;
48 preFetch();
49}
50
caryclark55888e42016-07-18 10:01:36 -070051bool SkOpEdgeBuilder::finish() {
caryclark54359292015-03-26 07:52:43 -070052 fOperand = false;
caryclark55888e42016-07-18 10:01:36 -070053 if (fUnparseable || !walk()) {
caryclark@google.com66560ca2013-04-26 19:51:16 +000054 return false;
55 }
caryclark@google.com07393ca2013-04-08 11:47:37 +000056 complete();
Cary Clarkff114282016-12-14 11:56:16 -050057 SkOpContour* contour = fContourBuilder.contour();
58 if (contour && !contour->count()) {
59 fContoursHead->remove(contour);
caryclark@google.com07393ca2013-04-08 11:47:37 +000060 }
caryclark@google.com66560ca2013-04-26 19:51:16 +000061 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +000062}
63
caryclark@google.com07e97fc2013-07-08 17:17:02 +000064void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) {
caryclark@google.coma2bbc6e2013-11-01 17:36:03 +000065 if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) {
caryclark54359292015-03-26 07:52:43 -070066 *fPathVerbs.append() = SkPath::kLine_Verb;
67 *fPathPts.append() = curveStart;
caryclark@google.com07e97fc2013-07-08 17:17:02 +000068 } else {
Cary Clarkb9ae5372016-10-05 10:40:07 -040069 int verbCount = fPathVerbs.count();
70 int ptsCount = fPathPts.count();
71 if (SkPath::kLine_Verb == fPathVerbs[verbCount - 1]
72 && fPathPts[ptsCount - 2] == curveStart) {
73 fPathVerbs.pop();
74 fPathPts.pop();
75 } else {
76 fPathPts[ptsCount - 1] = curveStart;
77 }
caryclark@google.com07e97fc2013-07-08 17:17:02 +000078 }
caryclark54359292015-03-26 07:52:43 -070079 *fPathVerbs.append() = SkPath::kClose_Verb;
caryclark@google.com07e97fc2013-07-08 17:17:02 +000080}
81
caryclark@google.com07393ca2013-04-08 11:47:37 +000082int SkOpEdgeBuilder::preFetch() {
caryclark@google.com66560ca2013-04-26 19:51:16 +000083 if (!fPath->isFinite()) {
84 fUnparseable = true;
85 return 0;
86 }
caryclark@google.com07e97fc2013-07-08 17:17:02 +000087 SkPoint curveStart;
88 SkPoint curve[4];
caryclark@google.com07e97fc2013-07-08 17:17:02 +000089 bool lastCurve = false;
Chris Dalton957189b2020-05-07 12:47:26 -060090 for (auto [pathVerb, pts, w] : SkPathPriv::Iterate(*fPath)) {
91 auto verb = static_cast<SkPath::Verb>(pathVerb);
caryclark@google.com07e97fc2013-07-08 17:17:02 +000092 switch (verb) {
93 case SkPath::kMove_Verb:
94 if (!fAllowOpenContours && lastCurve) {
95 closeContour(curve[0], curveStart);
96 }
caryclark54359292015-03-26 07:52:43 -070097 *fPathVerbs.append() = verb;
Chris Dalton1fc1bd32020-05-06 11:44:51 -060098 curve[0] = force_small_to_zero(pts[0]);
99 *fPathPts.append() = curve[0];
100 curveStart = curve[0];
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000101 lastCurve = false;
102 continue;
103 case SkPath::kLine_Verb:
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600104 curve[1] = force_small_to_zero(pts[1]);
105 if (SkDPoint::ApproximatelyEqual(curve[0], curve[1])) {
caryclark54359292015-03-26 07:52:43 -0700106 uint8_t lastVerb = fPathVerbs.top();
caryclark@google.coma2bbc6e2013-11-01 17:36:03 +0000107 if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) {
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600108 fPathPts.top() = curve[0] = curve[1];
caryclark@google.com570863f2013-09-16 15:55:01 +0000109 }
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000110 continue; // skip degenerate points
111 }
112 break;
113 case SkPath::kQuad_Verb:
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600114 curve[1] = force_small_to_zero(pts[1]);
115 curve[2] = force_small_to_zero(pts[2]);
116 verb = SkReduceOrder::Quad(curve, curve);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000117 if (verb == SkPath::kMove_Verb) {
118 continue; // skip degenerate points
119 }
120 break;
caryclark1049f122015-04-20 08:31:59 -0700121 case SkPath::kConic_Verb:
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600122 curve[1] = force_small_to_zero(pts[1]);
123 curve[2] = force_small_to_zero(pts[2]);
124 verb = SkReduceOrder::Quad(curve, curve);
Chris Dalton957189b2020-05-07 12:47:26 -0600125 if (SkPath::kQuad_Verb == verb && 1 != *w) {
caryclark27c015d2016-09-23 05:47:20 -0700126 verb = SkPath::kConic_Verb;
127 } else if (verb == SkPath::kMove_Verb) {
caryclark1049f122015-04-20 08:31:59 -0700128 continue; // skip degenerate points
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000129 }
caryclark1049f122015-04-20 08:31:59 -0700130 break;
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000131 case SkPath::kCubic_Verb:
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600132 curve[1] = force_small_to_zero(pts[1]);
133 curve[2] = force_small_to_zero(pts[2]);
134 curve[3] = force_small_to_zero(pts[3]);
135 verb = SkReduceOrder::Cubic(curve, curve);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000136 if (verb == SkPath::kMove_Verb) {
137 continue; // skip degenerate points
138 }
139 break;
140 case SkPath::kClose_Verb:
141 closeContour(curve[0], curveStart);
142 lastCurve = false;
143 continue;
144 case SkPath::kDone_Verb:
145 continue;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000146 }
caryclark54359292015-03-26 07:52:43 -0700147 *fPathVerbs.append() = verb;
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000148 int ptCount = SkPathOpsVerbToPoints(verb);
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600149 fPathPts.append(ptCount, &curve[1]);
caryclark1049f122015-04-20 08:31:59 -0700150 if (verb == SkPath::kConic_Verb) {
Chris Dalton957189b2020-05-07 12:47:26 -0600151 *fWeights.append() = *w;
caryclark1049f122015-04-20 08:31:59 -0700152 }
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600153 curve[0] = curve[ptCount];
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000154 lastCurve = true;
Chris Dalton957189b2020-05-07 12:47:26 -0600155 }
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000156 if (!fAllowOpenContours && lastCurve) {
157 closeContour(curve[0], curveStart);
158 }
caryclark54359292015-03-26 07:52:43 -0700159 *fPathVerbs.append() = SkPath::kDone_Verb;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000160 return fPathVerbs.count() - 1;
161}
162
caryclark@google.com66560ca2013-04-26 19:51:16 +0000163bool SkOpEdgeBuilder::close() {
caryclark@google.com66560ca2013-04-26 19:51:16 +0000164 complete();
165 return true;
166}
167
caryclark55888e42016-07-18 10:01:36 -0700168bool SkOpEdgeBuilder::walk() {
caryclark@google.com07393ca2013-04-08 11:47:37 +0000169 uint8_t* verbPtr = fPathVerbs.begin();
170 uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf];
Cary Clark79aa2f12018-05-31 16:22:02 -0400171 SkPoint* pointsPtr = fPathPts.begin();
caryclark1049f122015-04-20 08:31:59 -0700172 SkScalar* weightPtr = fWeights.begin();
caryclark@google.com07393ca2013-04-08 11:47:37 +0000173 SkPath::Verb verb;
Cary Clarkff114282016-12-14 11:56:16 -0500174 SkOpContour* contour = fContourBuilder.contour();
Cary Clark79aa2f12018-05-31 16:22:02 -0400175 int moveToPtrBump = 0;
caryclark@google.com6dc7df62013-04-25 11:51:54 +0000176 while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) {
177 if (verbPtr == endOfFirstHalf) {
178 fOperand = true;
179 }
180 verbPtr++;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000181 switch (verb) {
182 case SkPath::kMove_Verb:
Cary Clarkff114282016-12-14 11:56:16 -0500183 if (contour && contour->count()) {
caryclark@google.com66560ca2013-04-26 19:51:16 +0000184 if (fAllowOpenContours) {
185 complete();
186 } else if (!close()) {
187 return false;
188 }
189 }
Cary Clarkff114282016-12-14 11:56:16 -0500190 if (!contour) {
191 fContourBuilder.setContour(contour = fContoursHead->appendContour());
caryclark@google.com07393ca2013-04-08 11:47:37 +0000192 }
Cary Clarkff114282016-12-14 11:56:16 -0500193 contour->init(fGlobalState, fOperand,
caryclark54359292015-03-26 07:52:43 -0700194 fXorMask[fOperand] == kEvenOdd_PathOpsMask);
Cary Clark79aa2f12018-05-31 16:22:02 -0400195 pointsPtr += moveToPtrBump;
196 moveToPtrBump = 1;
caryclark@google.com6dc7df62013-04-25 11:51:54 +0000197 continue;
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000198 case SkPath::kLine_Verb:
Cary Clarkff114282016-12-14 11:56:16 -0500199 fContourBuilder.addLine(pointsPtr);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000200 break;
201 case SkPath::kQuad_Verb:
caryclark27c015d2016-09-23 05:47:20 -0700202 {
203 SkVector v1 = pointsPtr[1] - pointsPtr[0];
204 SkVector v2 = pointsPtr[2] - pointsPtr[1];
205 if (v1.dot(v2) < 0) {
206 SkPoint pair[5];
207 if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) {
208 goto addOneQuad;
209 }
210 if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) {
211 return false;
212 }
Cary Clarkafca4d62017-12-01 15:23:00 -0500213 for (unsigned index = 0; index < SK_ARRAY_COUNT(pair); ++index) {
Chris Dalton1fc1bd32020-05-06 11:44:51 -0600214 pair[index] = force_small_to_zero(pair[index]);
Cary Clarkafca4d62017-12-01 15:23:00 -0500215 }
caryclark27c015d2016-09-23 05:47:20 -0700216 SkPoint cStorage[2][2];
217 SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]);
218 SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]);
caryclarkcc093722016-09-23 09:32:26 -0700219 SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0];
220 SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1];
caryclark27c015d2016-09-23 05:47:20 -0700221 if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) {
Cary Clarkff114282016-12-14 11:56:16 -0500222 fContourBuilder.addCurve(v1, curve1);
223 fContourBuilder.addCurve(v2, curve2);
caryclark27c015d2016-09-23 05:47:20 -0700224 break;
225 }
226 }
227 }
228 addOneQuad:
Cary Clarkff114282016-12-14 11:56:16 -0500229 fContourBuilder.addQuad(pointsPtr);
caryclark@google.com07e97fc2013-07-08 17:17:02 +0000230 break;
caryclark27c015d2016-09-23 05:47:20 -0700231 case SkPath::kConic_Verb: {
232 SkVector v1 = pointsPtr[1] - pointsPtr[0];
233 SkVector v2 = pointsPtr[2] - pointsPtr[1];
234 SkScalar weight = *weightPtr++;
235 if (v1.dot(v2) < 0) {
236 // FIXME: max curvature for conics hasn't been implemented; use placeholder
237 SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr);
Chris Dalton1d474dd2018-07-24 01:08:31 -0600238 if (0 < maxCurvature && maxCurvature < 1) {
caryclark27c015d2016-09-23 05:47:20 -0700239 SkConic conic(pointsPtr, weight);
240 SkConic pair[2];
caryclark414c4292016-09-26 11:03:54 -0700241 if (!conic.chopAt(maxCurvature, pair)) {
242 // if result can't be computed, use original
Cary Clarkff114282016-12-14 11:56:16 -0500243 fContourBuilder.addConic(pointsPtr, weight);
caryclark414c4292016-09-26 11:03:54 -0700244 break;
245 }
caryclark27c015d2016-09-23 05:47:20 -0700246 SkPoint cStorage[2][3];
247 SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]);
248 SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]);
caryclarkcc093722016-09-23 09:32:26 -0700249 SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0];
250 SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1];
caryclark27c015d2016-09-23 05:47:20 -0700251 if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) {
Cary Clarkff114282016-12-14 11:56:16 -0500252 fContourBuilder.addCurve(v1, curve1, pair[0].fW);
253 fContourBuilder.addCurve(v2, curve2, pair[1].fW);
caryclark27c015d2016-09-23 05:47:20 -0700254 break;
255 }
caryclark1049f122015-04-20 08:31:59 -0700256 }
caryclark54359292015-03-26 07:52:43 -0700257 }
Cary Clarkff114282016-12-14 11:56:16 -0500258 fContourBuilder.addConic(pointsPtr, weight);
caryclark54359292015-03-26 07:52:43 -0700259 } break;
caryclark27c015d2016-09-23 05:47:20 -0700260 case SkPath::kCubic_Verb:
261 {
262 // Split complex cubics (such as self-intersecting curves or
263 // ones with difficult curvature) in two before proceeding.
264 // This can be required for intersection to succeed.
Cary Clark7eb01e02016-12-08 14:36:32 -0500265 SkScalar splitT[3];
266 int breaks = SkDCubic::ComplexBreak(pointsPtr, splitT);
267 if (!breaks) {
Cary Clarkff114282016-12-14 11:56:16 -0500268 fContourBuilder.addCubic(pointsPtr);
Cary Clark7eb01e02016-12-08 14:36:32 -0500269 break;
270 }
Cary Clarkff114282016-12-14 11:56:16 -0500271 SkASSERT(breaks <= (int) SK_ARRAY_COUNT(splitT));
272 struct Splitsville {
273 double fT[2];
274 SkPoint fPts[4];
275 SkPoint fReduced[4];
276 SkPath::Verb fVerb;
277 bool fCanAdd;
Cary Clark0eb6ed42016-12-16 16:31:11 -0500278 } splits[4];
279 SkASSERT(SK_ARRAY_COUNT(splits) == SK_ARRAY_COUNT(splitT) + 1);
280 SkTQSort(splitT, &splitT[breaks - 1]);
Cary Clark7eb01e02016-12-08 14:36:32 -0500281 for (int index = 0; index <= breaks; ++index) {
Cary Clarkff114282016-12-14 11:56:16 -0500282 Splitsville* split = &splits[index];
283 split->fT[0] = index ? splitT[index - 1] : 0;
284 split->fT[1] = index < breaks ? splitT[index] : 1;
285 SkDCubic part = SkDCubic::SubDivide(pointsPtr, split->fT[0], split->fT[1]);
286 if (!part.toFloatPoints(split->fPts)) {
caryclark27c015d2016-09-23 05:47:20 -0700287 return false;
288 }
Cary Clarkff114282016-12-14 11:56:16 -0500289 split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced);
Kevin Lubick67c905c2020-04-21 12:20:38 -0400290 SkPoint* curve = SkPath::kCubic_Verb == split->fVerb
Cary Clarkff114282016-12-14 11:56:16 -0500291 ? split->fPts : split->fReduced;
292 split->fCanAdd = can_add_curve(split->fVerb, curve);
293 }
294 for (int index = 0; index <= breaks; ++index) {
295 Splitsville* split = &splits[index];
296 if (!split->fCanAdd) {
297 continue;
298 }
299 int prior = index;
300 while (prior > 0 && !splits[prior - 1].fCanAdd) {
301 --prior;
302 }
303 if (prior < index) {
304 split->fT[0] = splits[prior].fT[0];
Cary Clark4efcb7d2018-02-02 15:09:49 -0500305 split->fPts[0] = splits[prior].fPts[0];
Cary Clarkff114282016-12-14 11:56:16 -0500306 }
307 int next = index;
Brian Osman788b9162020-02-07 10:36:46 -0500308 int breakLimit = std::min(breaks, (int) SK_ARRAY_COUNT(splits) - 1);
Eric Boren746e2632017-06-21 13:39:32 -0400309 while (next < breakLimit && !splits[next + 1].fCanAdd) {
Cary Clarkff114282016-12-14 11:56:16 -0500310 ++next;
311 }
312 if (next > index) {
313 split->fT[1] = splits[next].fT[1];
Cary Clark4efcb7d2018-02-02 15:09:49 -0500314 split->fPts[3] = splits[next].fPts[3];
Cary Clarkff114282016-12-14 11:56:16 -0500315 }
316 if (prior < index || next > index) {
Cary Clarkff114282016-12-14 11:56:16 -0500317 split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced);
318 }
319 SkPoint* curve = SkPath::kCubic_Verb == split->fVerb
320 ? split->fPts : split->fReduced;
Cary Clark74b42902018-03-09 07:38:47 -0500321 if (!can_add_curve(split->fVerb, curve)) {
322 return false;
323 }
Cary Clarkff114282016-12-14 11:56:16 -0500324 fContourBuilder.addCurve(split->fVerb, curve);
caryclark27c015d2016-09-23 05:47:20 -0700325 }
326 }
caryclark27c015d2016-09-23 05:47:20 -0700327 break;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000328 case SkPath::kClose_Verb:
Cary Clarkff114282016-12-14 11:56:16 -0500329 SkASSERT(contour);
caryclark@google.com66560ca2013-04-26 19:51:16 +0000330 if (!close()) {
331 return false;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000332 }
Cary Clarkff114282016-12-14 11:56:16 -0500333 contour = nullptr;
caryclark@google.com6dc7df62013-04-25 11:51:54 +0000334 continue;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000335 default:
336 SkDEBUGFAIL("bad verb");
caryclark@google.com66560ca2013-04-26 19:51:16 +0000337 return false;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000338 }
Cary Clarkff114282016-12-14 11:56:16 -0500339 SkASSERT(contour);
340 if (contour->count()) {
341 contour->debugValidate();
342 }
caryclark54359292015-03-26 07:52:43 -0700343 pointsPtr += SkPathOpsVerbToPoints(verb);
caryclark@google.com07393ca2013-04-08 11:47:37 +0000344 }
Cary Clarkff114282016-12-14 11:56:16 -0500345 fContourBuilder.flush();
346 if (contour && contour->count() &&!fAllowOpenContours && !close()) {
347 return false;
348 }
349 return true;
caryclark@google.com07393ca2013-04-08 11:47:37 +0000350}