1 /* 2 * Copyright 2013 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 */ 7 8 #include "SkPathOpsDebug.h" 9 #include "SkPath.h" 10 11 #if defined SK_DEBUG || !FORCE_RELEASE 12 13 const char* SkPathOpsDebug::kLVerbStr[] = {"", "line", "quad", "cubic"}; 14 15 #if defined(SK_DEBUG) || !FORCE_RELEASE 16 int SkPathOpsDebug::gContourID = 0; 17 int SkPathOpsDebug::gSegmentID = 0; 18 #endif 19 20 #if DEBUG_SORT || DEBUG_SWAP_TOP 21 int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; 22 int SkPathOpsDebug::gSortCount; 23 #endif 24 25 #if DEBUG_ACTIVE_OP 26 const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor"}; 27 #endif 28 29 bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpan *>& chaseArray, 30 const SkOpSpan* span) { 31 for (int index = 0; index < chaseArray.count(); ++index) { 32 const SkOpSpan* entry = chaseArray[index]; 33 if (entry == span) { 34 return true; 35 } 36 } 37 return false; 38 } 39 40 void SkPathOpsDebug::MathematicaIze(char* str, size_t bufferLen) { 41 size_t len = strlen(str); 42 bool num = false; 43 for (size_t idx = 0; idx < len; ++idx) { 44 if (num && str[idx] == 'e') { 45 if (len + 2 >= bufferLen) { 46 return; 47 } 48 memmove(&str[idx + 2], &str[idx + 1], len - idx); 49 str[idx] = '*'; 50 str[idx + 1] = '^'; 51 ++len; 52 } 53 num = str[idx] >= '0' && str[idx] <= '9'; 54 } 55 } 56 57 bool SkPathOpsDebug::ValidWind(int wind) { 58 return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF; 59 } 60 61 void SkPathOpsDebug::WindingPrintf(int wind) { 62 if (wind == SK_MinS32) { 63 SkDebugf("?"); 64 } else { 65 SkDebugf("%d", wind); 66 } 67 } 68 69 #if DEBUG_SHOW_TEST_NAME 70 void* SkPathOpsDebug::CreateNameStr() { 71 return SkNEW_ARRAY(char, DEBUG_FILENAME_STRING_LENGTH); 72 } 73 74 void SkPathOpsDebug::DeleteNameStr(void* v) { 75 SkDELETE_ARRAY(reinterpret_cast<char* >(v)); 76 } 77 78 void SkPathOpsDebug::BumpTestName(char* test) { 79 char* num = test + strlen(test); 80 while (num[-1] >= '0' && num[-1] <= '9') { 81 --num; 82 } 83 if (num[0] == '\0') { 84 return; 85 } 86 int dec = atoi(num); 87 if (dec == 0) { 88 return; 89 } 90 ++dec; 91 SK_SNPRINTF(num, DEBUG_FILENAME_STRING_LENGTH - (num - test), "%d", dec); 92 } 93 #endif 94 95 #if !DEBUG_SHOW_TEST_NAME // enable when building without extended test 96 void SkPathOpsDebug::ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name) { 97 } 98 #endif 99 100 #endif // defined SK_DEBUG || !FORCE_RELEASE 101 102 #include "SkOpAngle.h" 103 #include "SkOpSegment.h" 104 105 #if DEBUG_SORT 106 void SkOpAngle::debugLoop() const { 107 const SkOpAngle* first = this; 108 const SkOpAngle* next = this; 109 do { 110 next->dumpOne(true); 111 SkDebugf("\n"); 112 next = next->fNext; 113 } while (next && next != first); 114 } 115 #endif 116 117 #if DEBUG_ANGLE 118 void SkOpAngle::debugSameAs(const SkOpAngle* compare) const { 119 SK_ALWAYSBREAK(fSegment == compare->fSegment); 120 const SkOpSpan& startSpan = fSegment->span(fStart); 121 const SkOpSpan& oStartSpan = fSegment->span(compare->fStart); 122 SK_ALWAYSBREAK(startSpan.fToAngle == oStartSpan.fToAngle); 123 SK_ALWAYSBREAK(startSpan.fFromAngle == oStartSpan.fFromAngle); 124 const SkOpSpan& endSpan = fSegment->span(fEnd); 125 const SkOpSpan& oEndSpan = fSegment->span(compare->fEnd); 126 SK_ALWAYSBREAK(endSpan.fToAngle == oEndSpan.fToAngle); 127 SK_ALWAYSBREAK(endSpan.fFromAngle == oEndSpan.fFromAngle); 128 } 129 #endif 130 131 #if DEBUG_VALIDATE 132 void SkOpAngle::debugValidateNext() const { 133 const SkOpAngle* first = this; 134 const SkOpAngle* next = first; 135 SkTDArray<const SkOpAngle*>(angles); 136 do { 137 // SK_ALWAYSBREAK(next->fSegment->debugContains(next)); 138 angles.push(next); 139 next = next->next(); 140 if (next == first) { 141 break; 142 } 143 SK_ALWAYSBREAK(!angles.contains(next)); 144 if (!next) { 145 return; 146 } 147 } while (true); 148 } 149 150 void SkOpAngle::debugValidateLoop() const { 151 const SkOpAngle* first = this; 152 const SkOpAngle* next = first; 153 SK_ALWAYSBREAK(first->next() != first); 154 int signSum = 0; 155 int oppSum = 0; 156 bool firstOperand = fSegment->operand(); 157 bool unorderable = false; 158 do { 159 unorderable |= next->fUnorderable; 160 const SkOpSegment* segment = next->fSegment; 161 bool operandsMatch = firstOperand == segment->operand(); 162 signSum += operandsMatch ? segment->spanSign(next) : segment->oppSign(next); 163 oppSum += operandsMatch ? segment->oppSign(next) : segment->spanSign(next); 164 const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); 165 if (segment->_xor()) { 166 // SK_ALWAYSBREAK(span.fWindValue == 1); 167 // SK_ALWAYSBREAK(span.fWindSum == SK_MinS32 || span.fWindSum == 1); 168 } 169 if (segment->oppXor()) { 170 SK_ALWAYSBREAK(span.fOppValue == 0 || abs(span.fOppValue) == 1); 171 // SK_ALWAYSBREAK(span.fOppSum == SK_MinS32 || span.fOppSum == 0 || abs(span.fOppSum) == 1); 172 } 173 next = next->next(); 174 if (!next) { 175 return; 176 } 177 } while (next != first); 178 if (unorderable) { 179 return; 180 } 181 SK_ALWAYSBREAK(!signSum || fSegment->_xor()); 182 SK_ALWAYSBREAK(!oppSum || fSegment->oppXor()); 183 int lastWinding; 184 int lastOppWinding; 185 int winding; 186 int oppWinding; 187 do { 188 const SkOpSegment* segment = next->fSegment; 189 const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); 190 winding = span.fWindSum; 191 if (winding != SK_MinS32) { 192 // SK_ALWAYSBREAK(winding != 0); 193 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); 194 lastWinding = winding; 195 int diffWinding = segment->spanSign(next); 196 if (!segment->_xor()) { 197 SK_ALWAYSBREAK(diffWinding != 0); 198 bool sameSign = (winding > 0) == (diffWinding > 0); 199 winding -= sameSign ? diffWinding : -diffWinding; 200 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); 201 SK_ALWAYSBREAK(abs(winding) <= abs(lastWinding)); 202 if (!sameSign) { 203 SkTSwap(winding, lastWinding); 204 } 205 } 206 lastOppWinding = oppWinding = span.fOppSum; 207 if (oppWinding != SK_MinS32 && !segment->oppXor()) { 208 int oppDiffWinding = segment->oppSign(next); 209 // SK_ALWAYSBREAK(abs(oppDiffWinding) <= abs(diffWinding) || segment->_xor()); 210 if (oppDiffWinding) { 211 bool oppSameSign = (oppWinding > 0) == (oppDiffWinding > 0); 212 oppWinding -= oppSameSign ? oppDiffWinding : -oppDiffWinding; 213 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); 214 SK_ALWAYSBREAK(abs(oppWinding) <= abs(lastOppWinding)); 215 if (!oppSameSign) { 216 SkTSwap(oppWinding, lastOppWinding); 217 } 218 } 219 } 220 firstOperand = segment->operand(); 221 break; 222 } 223 SK_ALWAYSBREAK(span.fOppSum == SK_MinS32); 224 next = next->next(); 225 } while (next != first); 226 if (winding == SK_MinS32) { 227 return; 228 } 229 SK_ALWAYSBREAK(oppWinding == SK_MinS32 || SkPathOpsDebug::ValidWind(oppWinding)); 230 first = next; 231 next = next->next(); 232 do { 233 const SkOpSegment* segment = next->fSegment; 234 lastWinding = winding; 235 lastOppWinding = oppWinding; 236 bool operandsMatch = firstOperand == segment->operand(); 237 if (operandsMatch) { 238 if (!segment->_xor()) { 239 winding -= segment->spanSign(next); 240 SK_ALWAYSBREAK(winding != lastWinding); 241 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); 242 } 243 if (!segment->oppXor()) { 244 int oppDiffWinding = segment->oppSign(next); 245 if (oppWinding != SK_MinS32) { 246 oppWinding -= oppDiffWinding; 247 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); 248 } else { 249 SK_ALWAYSBREAK(oppDiffWinding == 0); 250 } 251 } 252 } else { 253 if (!segment->oppXor()) { 254 winding -= segment->oppSign(next); 255 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(winding)); 256 } 257 if (!segment->_xor()) { 258 oppWinding -= segment->spanSign(next); 259 SK_ALWAYSBREAK(oppWinding != lastOppWinding); 260 SK_ALWAYSBREAK(SkPathOpsDebug::ValidWind(oppWinding)); 261 } 262 } 263 bool useInner = SkOpSegment::UseInnerWinding(lastWinding, winding); 264 int sumWinding = useInner ? winding : lastWinding; 265 bool oppUseInner = SkOpSegment::UseInnerWinding(lastOppWinding, oppWinding); 266 int oppSumWinding = oppUseInner ? oppWinding : lastOppWinding; 267 if (!operandsMatch) { 268 SkTSwap(useInner, oppUseInner); 269 SkTSwap(sumWinding, oppSumWinding); 270 } 271 const SkOpSpan& span = segment->span(SkMin32(next->fStart, next->fEnd)); 272 if (winding == -lastWinding) { 273 if (span.fWindSum != SK_MinS32) { 274 SkDebugf("%s useInner=%d spanSign=%d lastWinding=%d winding=%d windSum=%d\n", 275 __FUNCTION__, 276 useInner, segment->spanSign(next), lastWinding, winding, span.fWindSum); 277 } 278 } 279 if (oppWinding != SK_MinS32) { 280 if (span.fOppSum != SK_MinS32) { 281 SK_ALWAYSBREAK(span.fOppSum == oppSumWinding || segment->oppXor() || segment->_xor()); 282 } 283 } else { 284 SK_ALWAYSBREAK(!firstOperand); 285 SK_ALWAYSBREAK(!segment->operand()); 286 SK_ALWAYSBREAK(!span.fOppValue); 287 } 288 next = next->next(); 289 } while (next != first); 290 } 291 #endif 292 293 #if DEBUG_SWAP_TOP 294 bool SkOpSegment::controlsContainedByEnds(int tStart, int tEnd) const { 295 if (fVerb != SkPath::kCubic_Verb) { 296 return false; 297 } 298 SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); 299 return dst.controlsContainedByEnds(); 300 } 301 #endif 302 303 #if DEBUG_CONCIDENT 304 // SK_ALWAYSBREAK if pair has not already been added 305 void SkOpSegment::debugAddTPair(double t, const SkOpSegment& other, double otherT) const { 306 for (int i = 0; i < fTs.count(); ++i) { 307 if (fTs[i].fT == t && fTs[i].fOther == &other && fTs[i].fOtherT == otherT) { 308 return; 309 } 310 } 311 SK_ALWAYSBREAK(0); 312 } 313 #endif 314 315 #if DEBUG_ANGLE 316 void SkOpSegment::debugCheckPointsEqualish(int tStart, int tEnd) const { 317 const SkPoint& basePt = fTs[tStart].fPt; 318 while (++tStart < tEnd) { 319 const SkPoint& cmpPt = fTs[tStart].fPt; 320 SK_ALWAYSBREAK(SkDPoint::ApproximatelyEqual(basePt, cmpPt)); 321 } 322 } 323 #endif 324 325 #if DEBUG_SWAP_TOP 326 int SkOpSegment::debugInflections(int tStart, int tEnd) const { 327 if (fVerb != SkPath::kCubic_Verb) { 328 return false; 329 } 330 SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); 331 double inflections[2]; 332 return dst.findInflections(inflections); 333 } 334 #endif 335 336 const SkOpAngle* SkOpSegment::debugLastAngle() const { 337 const SkOpAngle* result = NULL; 338 for (int index = 0; index < count(); ++index) { 339 const SkOpSpan& span = this->span(index); 340 if (span.fToAngle) { 341 SkASSERT(!result); 342 result = span.fToAngle; 343 } 344 } 345 SkASSERT(result); 346 return result; 347 } 348 349 void SkOpSegment::debugReset() { 350 fTs.reset(); 351 fAngles.reset(); 352 } 353 354 #if DEBUG_CONCIDENT 355 void SkOpSegment::debugShowTs(const char* prefix) const { 356 SkDebugf("%s %s id=%d", __FUNCTION__, prefix, fID); 357 int lastWind = -1; 358 int lastOpp = -1; 359 double lastT = -1; 360 int i; 361 for (i = 0; i < fTs.count(); ++i) { 362 bool change = lastT != fTs[i].fT || lastWind != fTs[i].fWindValue 363 || lastOpp != fTs[i].fOppValue; 364 if (change && lastWind >= 0) { 365 SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", 366 lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); 367 } 368 if (change) { 369 SkDebugf(" [o=%d", fTs[i].fOther->fID); 370 lastWind = fTs[i].fWindValue; 371 lastOpp = fTs[i].fOppValue; 372 lastT = fTs[i].fT; 373 } else { 374 SkDebugf(",%d", fTs[i].fOther->fID); 375 } 376 } 377 if (i <= 0) { 378 return; 379 } 380 SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", 381 lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); 382 if (fOperand) { 383 SkDebugf(" operand"); 384 } 385 if (done()) { 386 SkDebugf(" done"); 387 } 388 SkDebugf("\n"); 389 } 390 #endif 391 392 #if DEBUG_ACTIVE_SPANS || DEBUG_ACTIVE_SPANS_FIRST_ONLY 393 void SkOpSegment::debugShowActiveSpans() const { 394 debugValidate(); 395 if (done()) { 396 return; 397 } 398 #if DEBUG_ACTIVE_SPANS_SHORT_FORM 399 int lastId = -1; 400 double lastT = -1; 401 #endif 402 for (int i = 0; i < fTs.count(); ++i) { 403 if (fTs[i].fDone) { 404 continue; 405 } 406 SK_ALWAYSBREAK(i < fTs.count() - 1); 407 #if DEBUG_ACTIVE_SPANS_SHORT_FORM 408 if (lastId == fID && lastT == fTs[i].fT) { 409 continue; 410 } 411 lastId = fID; 412 lastT = fTs[i].fT; 413 #endif 414 SkDebugf("%s id=%d", __FUNCTION__, fID); 415 SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); 416 for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { 417 SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); 418 } 419 const SkOpSpan* span = &fTs[i]; 420 SkDebugf(") t=%1.9g (%1.9g,%1.9g)", span->fT, xAtT(span), yAtT(span)); 421 int iEnd = i + 1; 422 while (fTs[iEnd].fT < 1 && approximately_equal(fTs[i].fT, fTs[iEnd].fT)) { 423 ++iEnd; 424 } 425 SkDebugf(" tEnd=%1.9g", fTs[iEnd].fT); 426 const SkOpSegment* other = fTs[i].fOther; 427 SkDebugf(" other=%d otherT=%1.9g otherIndex=%d windSum=", 428 other->fID, fTs[i].fOtherT, fTs[i].fOtherIndex); 429 if (fTs[i].fWindSum == SK_MinS32) { 430 SkDebugf("?"); 431 } else { 432 SkDebugf("%d", fTs[i].fWindSum); 433 } 434 SkDebugf(" windValue=%d oppValue=%d\n", fTs[i].fWindValue, fTs[i].fOppValue); 435 } 436 } 437 #endif 438 439 #if DEBUG_MARK_DONE || DEBUG_UNSORTABLE 440 void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding) { 441 const SkPoint& pt = xyAtT(&span); 442 SkDebugf("%s id=%d", fun, fID); 443 SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); 444 for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { 445 SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); 446 } 447 SK_ALWAYSBREAK(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> 448 fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); 449 SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d windSum=", 450 span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, 451 (&span)[1].fT, winding); 452 if (span.fWindSum == SK_MinS32) { 453 SkDebugf("?"); 454 } else { 455 SkDebugf("%d", span.fWindSum); 456 } 457 SkDebugf(" windValue=%d\n", span.fWindValue); 458 } 459 460 void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, 461 int oppWinding) { 462 const SkPoint& pt = xyAtT(&span); 463 SkDebugf("%s id=%d", fun, fID); 464 SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); 465 for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { 466 SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); 467 } 468 SK_ALWAYSBREAK(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> 469 fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); 470 SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d newOppSum=%d oppSum=", 471 span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, 472 (&span)[1].fT, winding, oppWinding); 473 if (span.fOppSum == SK_MinS32) { 474 SkDebugf("?"); 475 } else { 476 SkDebugf("%d", span.fOppSum); 477 } 478 SkDebugf(" windSum="); 479 if (span.fWindSum == SK_MinS32) { 480 SkDebugf("?"); 481 } else { 482 SkDebugf("%d", span.fWindSum); 483 } 484 SkDebugf(" windValue=%d oppValue=%d\n", span.fWindValue, span.fOppValue); 485 } 486 #endif 487 488 #if DEBUG_SHOW_WINDING 489 int SkOpSegment::debugShowWindingValues(int slotCount, int ofInterest) const { 490 if (!(1 << fID & ofInterest)) { 491 return 0; 492 } 493 int sum = 0; 494 SkTArray<char, true> slots(slotCount * 2); 495 memset(slots.begin(), ' ', slotCount * 2); 496 for (int i = 0; i < fTs.count(); ++i) { 497 // if (!(1 << fTs[i].fOther->fID & ofInterest)) { 498 // continue; 499 // } 500 sum += fTs[i].fWindValue; 501 slots[fTs[i].fOther->fID - 1] = as_digit(fTs[i].fWindValue); 502 sum += fTs[i].fOppValue; 503 slots[slotCount + fTs[i].fOther->fID - 1] = as_digit(fTs[i].fOppValue); 504 } 505 SkDebugf("%s id=%2d %.*s | %.*s\n", __FUNCTION__, fID, slotCount, slots.begin(), slotCount, 506 slots.begin() + slotCount); 507 return sum; 508 } 509 #endif 510 511 void SkOpSegment::debugValidate() const { 512 #if DEBUG_VALIDATE 513 int count = fTs.count(); 514 SK_ALWAYSBREAK(count >= 2); 515 SK_ALWAYSBREAK(fTs[0].fT == 0); 516 SK_ALWAYSBREAK(fTs[count - 1].fT == 1); 517 int done = 0; 518 double t = -1; 519 const SkOpSpan* last = NULL; 520 bool tinyTFound = false; 521 bool hasLoop = false; 522 for (int i = 0; i < count; ++i) { 523 const SkOpSpan& span = fTs[i]; 524 SK_ALWAYSBREAK(t <= span.fT); 525 t = span.fT; 526 int otherIndex = span.fOtherIndex; 527 const SkOpSegment* other = span.fOther; 528 SK_ALWAYSBREAK(other != this || fVerb == SkPath::kCubic_Verb); 529 const SkOpSpan& otherSpan = other->fTs[otherIndex]; 530 SK_ALWAYSBREAK(otherSpan.fPt == span.fPt); 531 SK_ALWAYSBREAK(otherSpan.fOtherT == t); 532 SK_ALWAYSBREAK(&fTs[i] == &otherSpan.fOther->fTs[otherSpan.fOtherIndex]); 533 done += span.fDone; 534 if (last) { 535 SK_ALWAYSBREAK(last->fT != span.fT || last->fOther != span.fOther); 536 bool tsEqual = last->fT == span.fT; 537 bool tsPreciselyEqual = precisely_equal(last->fT, span.fT); 538 SK_ALWAYSBREAK(!tsEqual || tsPreciselyEqual); 539 bool pointsEqual = last->fPt == span.fPt; 540 bool pointsNearlyEqual = AlmostEqualUlps(last->fPt, span.fPt); 541 #if 0 // bufferOverflow test triggers this 542 SK_ALWAYSBREAK(!tsPreciselyEqual || pointsNearlyEqual); 543 #endif 544 // SK_ALWAYSBREAK(!last->fTiny || !tsPreciselyEqual || span.fTiny || tinyTFound); 545 SK_ALWAYSBREAK(last->fTiny || tsPreciselyEqual || !pointsEqual || hasLoop); 546 SK_ALWAYSBREAK(!last->fTiny || pointsEqual); 547 SK_ALWAYSBREAK(!last->fTiny || last->fDone); 548 SK_ALWAYSBREAK(!last->fSmall || pointsNearlyEqual); 549 SK_ALWAYSBREAK(!last->fSmall || last->fDone); 550 // SK_ALWAYSBREAK(!last->fSmall || last->fTiny); 551 // SK_ALWAYSBREAK(last->fTiny || !pointsEqual || last->fDone == span.fDone); 552 if (last->fTiny) { 553 tinyTFound |= !tsPreciselyEqual; 554 } else { 555 tinyTFound = false; 556 } 557 } 558 last = &span; 559 hasLoop |= last->fLoop; 560 } 561 SK_ALWAYSBREAK(done == fDoneSpans); 562 // if (fAngles.count() ) { 563 // fAngles.begin()->debugValidateLoop(); 564 // } 565 #endif 566 } 567