C++程序  |  4455行  |  156.78 KB

/*
 * Copyright 2012 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "SkIntersections.h"
#include "SkOpContour.h"
#include "SkOpSegment.h"
#include "SkPathWriter.h"
#include "SkTSort.h"

#define F (false)      // discard the edge
#define T (true)       // keep the edge

static const bool gUnaryActiveEdge[2][2] = {
//  from=0  from=1
//  to=0,1  to=0,1
    {F, T}, {T, F},
};

static const bool gActiveEdge[kXOR_PathOp + 1][2][2][2][2] = {
//                 miFrom=0                              miFrom=1
//         miTo=0             miTo=1             miTo=0             miTo=1
//     suFrom=0    1      suFrom=0    1      suFrom=0    1      suFrom=0    1
//   suTo=0,1 suTo=0,1  suTo=0,1 suTo=0,1  suTo=0,1 suTo=0,1  suTo=0,1 suTo=0,1
    {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}},  // mi - su
    {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}},  // mi & su
    {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}},  // mi | su
    {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}},  // mi ^ su
};

#undef F
#undef T

enum {
    kOutsideTrackedTCount = 16,  // FIXME: determine what this should be
    kMissingSpanCount = 4,  // FIXME: determine what this should be
};

const SkOpAngle* SkOpSegment::activeAngle(int index, int* start, int* end, bool* done,
        bool* sortable) const {
    if (const SkOpAngle* result = activeAngleInner(index, start, end, done, sortable)) {
        return result;
    }
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0
            && (precisely_negative(referenceT - fTs[lesser].fT) || fTs[lesser].fTiny)) {
        if (const SkOpAngle* result = activeAngleOther(lesser, start, end, done, sortable)) {
            return result;
        }
    }
    do {
        if (const SkOpAngle* result = activeAngleOther(index, start, end, done, sortable)) {
            return result;
        }
        if (++index == fTs.count()) {
            break;
        }
        if (fTs[index - 1].fTiny) {
            referenceT = fTs[index].fT;
            continue;
        }
    } while (precisely_negative(fTs[index].fT - referenceT));
    return NULL;
}

const SkOpAngle* SkOpSegment::activeAngleInner(int index, int* start, int* end, bool* done,
        bool* sortable) const {
    int next = nextExactSpan(index, 1);
    if (next > 0) {
        const SkOpSpan& upSpan = fTs[index];
        if (upSpan.fWindValue || upSpan.fOppValue) {
            if (*end < 0) {
                *start = index;
                *end = next;
            }
            if (!upSpan.fDone) {
                if (upSpan.fWindSum != SK_MinS32) {
                    return spanToAngle(index, next);
                }
                *done = false;
            }
        } else {
            SkASSERT(upSpan.fDone);
        }
    }
    int prev = nextExactSpan(index, -1);
    // edge leading into junction
    if (prev >= 0) {
        const SkOpSpan& downSpan = fTs[prev];
        if (downSpan.fWindValue || downSpan.fOppValue) {
            if (*end < 0) {
                *start = index;
                *end = prev;
            }
            if (!downSpan.fDone) {
                if (downSpan.fWindSum != SK_MinS32) {
                    return spanToAngle(index, prev);
                }
                *done = false;
            }
        } else {
            SkASSERT(downSpan.fDone);
        }
    }
    return NULL;
}

const SkOpAngle* SkOpSegment::activeAngleOther(int index, int* start, int* end, bool* done,
        bool* sortable) const {
    const SkOpSpan* span = &fTs[index];
    SkOpSegment* other = span->fOther;
    int oIndex = span->fOtherIndex;
    return other->activeAngleInner(oIndex, start, end, done, sortable);
}

SkPoint SkOpSegment::activeLeftTop(int* firstT) const {
    SkASSERT(!done());
    SkPoint topPt = {SK_ScalarMax, SK_ScalarMax};
    int count = fTs.count();
    // see if either end is not done since we want smaller Y of the pair
    bool lastDone = true;
    double lastT = -1;
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fDone && lastDone) {
            goto next;
        }
        if (approximately_negative(span.fT - lastT)) {
            goto next;
        }
        {
            const SkPoint& xy = xyAtT(&span);
            if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) {
                topPt = xy;
                if (firstT) {
                    *firstT = index;
                }
            }
            if (fVerb != SkPath::kLine_Verb && !lastDone) {
                SkPoint curveTop = (*CurveTop[SkPathOpsVerbToPoints(fVerb)])(fPts, lastT, span.fT);
                if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY
                        && topPt.fX > curveTop.fX)) {
                    topPt = curveTop;
                    if (firstT) {
                        *firstT = index;
                    }
                }
            }
            lastT = span.fT;
        }
next:
        lastDone = span.fDone;
    }
    return topPt;
}

bool SkOpSegment::activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op) {
    int sumMiWinding = updateWinding(endIndex, index);
    int sumSuWinding = updateOppWinding(endIndex, index);
    if (fOperand) {
        SkTSwap<int>(sumMiWinding, sumSuWinding);
    }
    return activeOp(xorMiMask, xorSuMask, index, endIndex, op, &sumMiWinding, &sumSuWinding);
}

bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op,
        int* sumMiWinding, int* sumSuWinding) {
    int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
    setUpWindings(index, endIndex, sumMiWinding, sumSuWinding,
            &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
    bool miFrom;
    bool miTo;
    bool suFrom;
    bool suTo;
    if (operand()) {
        miFrom = (oppMaxWinding & xorMiMask) != 0;
        miTo = (oppSumWinding & xorMiMask) != 0;
        suFrom = (maxWinding & xorSuMask) != 0;
        suTo = (sumWinding & xorSuMask) != 0;
    } else {
        miFrom = (maxWinding & xorMiMask) != 0;
        miTo = (sumWinding & xorMiMask) != 0;
        suFrom = (oppMaxWinding & xorSuMask) != 0;
        suTo = (oppSumWinding & xorSuMask) != 0;
    }
    bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo];
#if DEBUG_ACTIVE_OP
    SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n",
            __FUNCTION__, debugID(), span(index).fT, span(endIndex).fT,
            SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result);
#endif
    return result;
}

bool SkOpSegment::activeWinding(int index, int endIndex) {
    int sumWinding = updateWinding(endIndex, index);
    return activeWinding(index, endIndex, &sumWinding);
}

bool SkOpSegment::activeWinding(int index, int endIndex, int* sumWinding) {
    int maxWinding;
    setUpWinding(index, endIndex, &maxWinding, sumWinding);
    bool from = maxWinding != 0;
    bool to = *sumWinding  != 0;
    bool result = gUnaryActiveEdge[from][to];
    return result;
}

void SkOpSegment::addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt,
        SkOpSegment* other) {
    int tIndex = -1;
    int tCount = fTs.count();
    int oIndex = -1;
    int oCount = other->fTs.count();
    do {
        ++tIndex;
    } while (startPt != fTs[tIndex].fPt && tIndex < tCount);
    int tIndexStart = tIndex;
    do {
        ++oIndex;
    } while (endPt != other->fTs[oIndex].fPt && oIndex < oCount);
    int oIndexStart = oIndex;
    const SkPoint* nextPt;
    do {
        nextPt = &fTs[++tIndex].fPt;
        SkASSERT(fTs[tIndex].fT < 1 || startPt != *nextPt);
    } while (startPt == *nextPt);
    double nextT = fTs[tIndex].fT;
    const SkPoint* oNextPt;
    do {
        oNextPt = &other->fTs[++oIndex].fPt;
        SkASSERT(other->fTs[oIndex].fT < 1 || endPt != *oNextPt);
    } while (endPt == *oNextPt);
    double oNextT = other->fTs[oIndex].fT;
    // at this point, spans before and after are at:
    //  fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex]
    // if tIndexStart == 0, no prior span
    // if nextT == 1, no following span

    // advance the span with zero winding
    // if the following span exists (not past the end, non-zero winding)
    // connect the two edges
    if (!fTs[tIndexStart].fWindValue) {
        if (tIndexStart > 0 && fTs[tIndexStart - 1].fWindValue) {
#if DEBUG_CONCIDENT
            SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
                    __FUNCTION__, fID, other->fID, tIndexStart - 1,
                    fTs[tIndexStart].fT, xyAtT(tIndexStart).fX,
                    xyAtT(tIndexStart).fY);
#endif
            SkPoint copy = fTs[tIndexStart].fPt;  // add t pair may move the point array
            addTPair(fTs[tIndexStart].fT, other, other->fTs[oIndex].fT, false, copy);
        }
        if (nextT < 1 && fTs[tIndex].fWindValue) {
#if DEBUG_CONCIDENT
            SkDebugf("%s 2 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
                    __FUNCTION__, fID, other->fID, tIndex,
                    fTs[tIndex].fT, xyAtT(tIndex).fX,
                    xyAtT(tIndex).fY);
#endif
            SkPoint copy = fTs[tIndex].fPt;  // add t pair may move the point array
            addTPair(fTs[tIndex].fT, other, other->fTs[oIndexStart].fT, false, copy);
        }
    } else {
        SkASSERT(!other->fTs[oIndexStart].fWindValue);
        if (oIndexStart > 0 && other->fTs[oIndexStart - 1].fWindValue) {
#if DEBUG_CONCIDENT
            SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
                    __FUNCTION__, fID, other->fID, oIndexStart - 1,
                    other->fTs[oIndexStart].fT, other->xyAtT(oIndexStart).fX,
                    other->xyAtT(oIndexStart).fY);
            other->debugAddTPair(other->fTs[oIndexStart].fT, *this, fTs[tIndex].fT);
#endif
        }
        if (oNextT < 1 && other->fTs[oIndex].fWindValue) {
#if DEBUG_CONCIDENT
            SkDebugf("%s 4 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
                    __FUNCTION__, fID, other->fID, oIndex,
                    other->fTs[oIndex].fT, other->xyAtT(oIndex).fX,
                    other->xyAtT(oIndex).fY);
            other->debugAddTPair(other->fTs[oIndex].fT, *this, fTs[tIndexStart].fT);
#endif
        }
    }
}

void SkOpSegment::addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt,
        SkOpSegment* other) {
    // walk this to startPt
    // walk other to startPt
    // if either is > 0, add a pointer to the other, copying adjacent winding
    int tIndex = -1;
    int oIndex = -1;
    do {
        ++tIndex;
    } while (startPt != fTs[tIndex].fPt);
    int ttIndex = tIndex;
    bool checkOtherTMatch = false;
    do {
        const SkOpSpan& span = fTs[ttIndex];
        if (startPt != span.fPt) {
            break;
        }
        if (span.fOther == other && span.fPt == startPt) {
            checkOtherTMatch = true;
            break;
        }
    } while (++ttIndex < count());
    do {
        ++oIndex;
    } while (startPt != other->fTs[oIndex].fPt);
    bool skipAdd = false;
    if (checkOtherTMatch) {
        int ooIndex = oIndex;
        do {
            const SkOpSpan& oSpan = other->fTs[ooIndex];
            if (startPt != oSpan.fPt) {
                break;
            }
            if (oSpan.fT == fTs[ttIndex].fOtherT) {
                skipAdd = true;
                break;
            }
        } while (++ooIndex < other->count());
    }
    if ((tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) && !skipAdd) {
        addTPair(fTs[tIndex].fT, other, other->fTs[oIndex].fT, false, startPt);
    }
    SkPoint nextPt = startPt;
    do {
        const SkPoint* workPt;
        do {
            workPt = &fTs[++tIndex].fPt;
        } while (nextPt == *workPt);
        const SkPoint* oWorkPt;
        do {
            oWorkPt = &other->fTs[++oIndex].fPt;
        } while (nextPt == *oWorkPt);
        nextPt = *workPt;
        double tStart = fTs[tIndex].fT;
        double oStart = other->fTs[oIndex].fT;
        if (tStart == 1 && oStart == 1 && fOperand == other->fOperand) {
            break;
        }
        if (*workPt == *oWorkPt) {
            addTPair(tStart, other, oStart, false, nextPt);
        }
    } while (endPt != nextPt);
}

void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) {
    init(pts, SkPath::kCubic_Verb, operand, evenOdd);
    fBounds.setCubicBounds(pts);
}

void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* path, bool active) const {
    SkPoint edge[4];
    const SkPoint* ePtr;
    int lastT = fTs.count() - 1;
    if (lastT < 0 || (start == 0 && end == lastT) || (start == lastT && end == 0)) {
        ePtr = fPts;
    } else {
    // OPTIMIZE? if not active, skip remainder and return xyAtT(end)
        subDivide(start, end, edge);
        ePtr = edge;
    }
    if (active) {
        bool reverse = ePtr == fPts && start != 0;
        if (reverse) {
            path->deferredMoveLine(ePtr[SkPathOpsVerbToPoints(fVerb)]);
            switch (fVerb) {
                case SkPath::kLine_Verb:
                    path->deferredLine(ePtr[0]);
                    break;
                case SkPath::kQuad_Verb:
                    path->quadTo(ePtr[1], ePtr[0]);
                    break;
                case SkPath::kCubic_Verb:
                    path->cubicTo(ePtr[2], ePtr[1], ePtr[0]);
                    break;
                default:
                    SkASSERT(0);
            }
   //         return ePtr[0];
       } else {
            path->deferredMoveLine(ePtr[0]);
            switch (fVerb) {
                case SkPath::kLine_Verb:
                    path->deferredLine(ePtr[1]);
                    break;
                case SkPath::kQuad_Verb:
                    path->quadTo(ePtr[1], ePtr[2]);
                    break;
                case SkPath::kCubic_Verb:
                    path->cubicTo(ePtr[1], ePtr[2], ePtr[3]);
                    break;
                default:
                    SkASSERT(0);
            }
        }
    }
  //  return ePtr[SkPathOpsVerbToPoints(fVerb)];
}

void SkOpSegment::addEndSpan(int endIndex) {
    SkASSERT(span(endIndex).fT == 1 || (span(endIndex).fTiny
//            && approximately_greater_than_one(span(endIndex).fT)
    ));
    int spanCount = fTs.count();
    int startIndex = endIndex - 1;
    while (fTs[startIndex].fT == 1 || fTs[startIndex].fTiny) {
        --startIndex;
        SkASSERT(startIndex > 0);
        --endIndex;
    }
    SkOpAngle& angle = fAngles.push_back();
    angle.set(this, spanCount - 1, startIndex);
#if DEBUG_ANGLE
    debugCheckPointsEqualish(endIndex, spanCount);
#endif
    setFromAngle(endIndex, &angle);
}

void SkOpSegment::setFromAngle(int endIndex, SkOpAngle* angle) {
    int spanCount = fTs.count();
    do {
        fTs[endIndex].fFromAngle = angle;
    } while (++endIndex < spanCount);
}

void SkOpSegment::addLine(const SkPoint pts[2], bool operand, bool evenOdd) {
    init(pts, SkPath::kLine_Verb, operand, evenOdd);
    fBounds.set(pts, 2);
}

// add 2 to edge or out of range values to get T extremes
void SkOpSegment::addOtherT(int index, double otherT, int otherIndex) {
    SkOpSpan& span = fTs[index];
    if (precisely_zero(otherT)) {
        otherT = 0;
    } else if (precisely_equal(otherT, 1)) {
        otherT = 1;
    }
    span.fOtherT = otherT;
    span.fOtherIndex = otherIndex;
}

void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) {
    init(pts, SkPath::kQuad_Verb, operand, evenOdd);
    fBounds.setQuadBounds(pts);
}

SkOpAngle* SkOpSegment::addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** anglePtr) {
    int spanIndex = count() - 1;
    int startIndex = nextExactSpan(spanIndex, -1);
    SkASSERT(startIndex >= 0);
    SkOpAngle& angle = fAngles.push_back();
    *anglePtr = &angle;
    angle.set(this, spanIndex, startIndex);
    setFromAngle(spanIndex, &angle);
    SkOpSegment* other;
    int oStartIndex, oEndIndex;
    do {
        const SkOpSpan& span = fTs[spanIndex];
        SkASSERT(span.fT > 0);
        other = span.fOther;
        oStartIndex = span.fOtherIndex;
        oEndIndex = other->nextExactSpan(oStartIndex, 1);
        if (oEndIndex > 0 && other->span(oStartIndex).fWindValue) {
            break;
        }
        oEndIndex = oStartIndex;
        oStartIndex = other->nextExactSpan(oEndIndex, -1);
        --spanIndex;
    } while (oStartIndex < 0 || !other->span(oStartIndex).fWindSum);
    SkOpAngle& oAngle = other->fAngles.push_back();
    oAngle.set(other, oStartIndex, oEndIndex);
    other->setToAngle(oEndIndex, &oAngle);
    *otherPtr = other;
    return &oAngle;
}

SkOpAngle* SkOpSegment::addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** anglePtr) {
    int endIndex = nextExactSpan(0, 1);
    SkASSERT(endIndex > 0);
    SkOpAngle& angle = fAngles.push_back();
    *anglePtr = &angle;
    angle.set(this, 0, endIndex);
    setToAngle(endIndex, &angle);
    int spanIndex = 0;
    SkOpSegment* other;
    int oStartIndex, oEndIndex;
    do {
        const SkOpSpan& span = fTs[spanIndex];
        SkASSERT(span.fT < 1);
        other = span.fOther;
        oEndIndex = span.fOtherIndex;
        oStartIndex = other->nextExactSpan(oEndIndex, -1);
        if (oStartIndex >= 0 && other->span(oStartIndex).fWindValue) {
            break;
        }
        oStartIndex = oEndIndex;
        oEndIndex = other->nextExactSpan(oStartIndex, 1);
        ++spanIndex;
    } while (oEndIndex < 0 || !other->span(oStartIndex).fWindValue);
    SkOpAngle& oAngle = other->fAngles.push_back();
    oAngle.set(other, oEndIndex, oStartIndex);
    other->setFromAngle(oEndIndex, &oAngle);
    *otherPtr = other;
    return &oAngle;
}

SkOpAngle* SkOpSegment::addSingletonAngles(int step) {
    SkOpSegment* other;
    SkOpAngle* angle, * otherAngle;
    if (step > 0) {
        otherAngle = addSingletonAngleUp(&other, &angle);
    } else {
        otherAngle = addSingletonAngleDown(&other, &angle);
    }
    angle->insert(otherAngle);
    return angle;
}

void SkOpSegment::addStartSpan(int endIndex) {
    int index = 0;
    SkOpAngle& angle = fAngles.push_back();
    angle.set(this, index, endIndex);
#if DEBUG_ANGLE
    debugCheckPointsEqualish(index, endIndex);
#endif
    setToAngle(endIndex, &angle);
}

void SkOpSegment::setToAngle(int endIndex, SkOpAngle* angle) {
    int index = 0;
    do {
        fTs[index].fToAngle = angle;
    } while (++index < endIndex);
}

    // Defer all coincident edge processing until
    // after normal intersections have been computed

// no need to be tricky; insert in normal T order
// resolve overlapping ts when considering coincidence later

    // add non-coincident intersection. Resulting edges are sorted in T.
int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
    SkASSERT(this != other || fVerb == SkPath::kCubic_Verb);
 #if 0  // this needs an even rougher association to be useful
    SkASSERT(SkDPoint::RoughlyEqual(ptAtT(newT), pt));
 #endif
    const SkPoint& firstPt = fPts[0];
    const SkPoint& lastPt = fPts[SkPathOpsVerbToPoints(fVerb)];
    SkASSERT(newT == 0 || !precisely_zero(newT));
    SkASSERT(newT == 1 || !precisely_equal(newT, 1));
    // FIXME: in the pathological case where there is a ton of intercepts,
    //  binary search?
    int insertedAt = -1;
    int tCount = fTs.count();
    for (int index = 0; index < tCount; ++index) {
        // OPTIMIZATION: if there are three or more identical Ts, then
        // the fourth and following could be further insertion-sorted so
        // that all the edges are clockwise or counterclockwise.
        // This could later limit segment tests to the two adjacent
        // neighbors, although it doesn't help with determining which
        // circular direction to go in.
        const SkOpSpan& span = fTs[index];
        if (newT < span.fT) {
            insertedAt = index;
            break;
        }
        if (newT == span.fT) {
            if (pt == span.fPt) {
                insertedAt = index;
                break;
            }
            if ((pt == firstPt && newT == 0) || (span.fPt == lastPt && newT == 1)) {
                insertedAt = index;
                break;
            }
        }
    }
    SkOpSpan* span;
    if (insertedAt >= 0) {
        span = fTs.insert(insertedAt);
    } else {
        insertedAt = tCount;
        span = fTs.append();
    }
    span->fT = newT;
    span->fOtherT = -1;
    span->fOther = other;
    span->fPt = pt;
#if 0
    // cubics, for instance, may not be exact enough to satisfy this check (e.g., cubicOp69d)
    SkASSERT(approximately_equal(xyAtT(newT).fX, pt.fX)
            && approximately_equal(xyAtT(newT).fY, pt.fY));
#endif
    span->fFromAngle = NULL;
    span->fToAngle = NULL;
    span->fWindSum = SK_MinS32;
    span->fOppSum = SK_MinS32;
    span->fWindValue = 1;
    span->fOppValue = 0;
    span->fChased = false;
    span->fCoincident = false;
    span->fLoop = false;
    span->fNear = false;
    span->fMultiple = false;
    span->fSmall = false;
    span->fTiny = false;
    if ((span->fDone = newT == 1)) {
        ++fDoneSpans;
    }
    int less = -1;
// FIXME: note that this relies on spans being a continguous array
// find range of spans with nearly the same point as this one
    // FIXME: SkDPoint::ApproximatelyEqual is better but breaks tests at the moment
    while (&span[less + 1] - fTs.begin() > 0 && AlmostEqualUlps(span[less].fPt, pt)) {
        if (fVerb == SkPath::kCubic_Verb) {
            double tInterval = newT - span[less].fT;
            double tMid = newT - tInterval / 2;
            SkDPoint midPt = dcubic_xy_at_t(fPts, tMid);
            if (!midPt.approximatelyEqual(xyAtT(span))) {
                break;
            }
        }
        --less;
    }
    int more = 1;
    // FIXME: SkDPoint::ApproximatelyEqual is better but breaks tests at the moment
    while (fTs.end() - &span[more - 1] > 1 && AlmostEqualUlps(span[more].fPt, pt)) {
        if (fVerb == SkPath::kCubic_Verb) {
            double tEndInterval = span[more].fT - newT;
            double tMid = newT - tEndInterval / 2;
            SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid);
            if (!midEndPt.approximatelyEqual(xyAtT(span))) {
                break;
            }
        }
        ++more;
    }
    ++less;
    --more;
    while (more - 1 > less && span[more].fPt == span[more - 1].fPt
            && span[more].fT == span[more - 1].fT) {
        --more;
    }
    if (less == more) {
        return insertedAt;
    }
    if (precisely_negative(span[more].fT - span[less].fT)) {
        return insertedAt;
    }
// if the total range of t values is big enough, mark all tiny
    bool tiny = span[less].fPt == span[more].fPt;
    int index = less;
    do {
        fSmall = span[index].fSmall = true;
        fTiny |= span[index].fTiny = tiny;
        if (!span[index].fDone) {
            span[index].fDone = true;
            ++fDoneSpans;
        }
    } while (++index < more);
    return insertedAt;
}

// set spans from start to end to decrement by one
// note this walks other backwards
// FIXME: there's probably an edge case that can be constructed where
// two span in one segment are separated by float epsilon on one span but
// not the other, if one segment is very small. For this
// case the counts asserted below may or may not be enough to separate the
// spans. Even if the counts work out, what if the spans aren't correctly
// sorted? It feels better in such a case to match the span's other span
// pointer since both coincident segments must contain the same spans.
// FIXME? It seems that decrementing by one will fail for complex paths that
// have three or more coincident edges. Shouldn't this subtract the difference
// between the winding values?
/*                                      |-->                           |-->
this     0>>>>1>>>>2>>>>3>>>4      0>>>>1>>>>2>>>>3>>>4      0>>>>1>>>>2>>>>3>>>4
other         2<<<<1<<<<0               2<<<<1<<<<0               2<<<<1<<<<0
              ^         ^                 <--|                           <--|
           startPt    endPt        test/oTest first pos      test/oTest final pos
*/
void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other) {
    bool binary = fOperand != other->fOperand;
    int index = 0;
    while (startPt != fTs[index].fPt) {
        SkASSERT(index < fTs.count());
        ++index;
    }
    while (index > 0 && precisely_equal(fTs[index].fT, fTs[index - 1].fT)) {
        --index;
    }
    bool oFoundEnd = false;
    int oIndex = other->fTs.count();
    while (startPt != other->fTs[--oIndex].fPt) {  // look for startPt match
        SkASSERT(oIndex > 0);
    }
    double oStartT = other->fTs[oIndex].fT;
    // look for first point beyond match
    while (startPt == other->fTs[--oIndex].fPt || precisely_equal(oStartT, other->fTs[oIndex].fT)) {
        if (!oIndex) {
            return;  // tiny spans may move in the wrong direction
        }
    }
    SkOpSpan* test = &fTs[index];
    SkOpSpan* oTest = &other->fTs[oIndex];
    SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts;
    SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts;
    bool decrement, track, bigger;
    int originalWindValue;
    const SkPoint* testPt;
    const SkPoint* oTestPt;
    do {
        SkASSERT(test->fT < 1);
        SkASSERT(oTest->fT < 1);
        decrement = test->fWindValue && oTest->fWindValue;
        track = test->fWindValue || oTest->fWindValue;
        bigger = test->fWindValue >= oTest->fWindValue;
        testPt = &test->fPt;
        double testT = test->fT;
        oTestPt = &oTest->fPt;
        double oTestT = oTest->fT;
        do {
            if (decrement) {
                if (binary && bigger) {
                    test->fOppValue--;
                } else {
                    decrementSpan(test);
                }
            } else if (track) {
                TrackOutsidePair(&outsidePts, *testPt, *oTestPt);
            }
            SkASSERT(index < fTs.count() - 1);
            test = &fTs[++index];
        } while (*testPt == test->fPt || precisely_equal(testT, test->fT));
        originalWindValue = oTest->fWindValue;
        do {
            SkASSERT(oTest->fT < 1);
            SkASSERT(originalWindValue == oTest->fWindValue);
            if (decrement) {
                if (binary && !bigger) {
                    oTest->fOppValue--;
                } else {
                    other->decrementSpan(oTest);
                }
            } else if (track) {
                TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt);
            }
            if (!oIndex) {
                break;
            }
            oFoundEnd |= endPt == oTest->fPt;
            oTest = &other->fTs[--oIndex];
        } while (*oTestPt == oTest->fPt || precisely_equal(oTestT, oTest->fT));
    } while (endPt != test->fPt && test->fT < 1);
    // FIXME: determine if canceled edges need outside ts added
    if (!oFoundEnd) {
        for (int oIdx2 = oIndex; oIdx2 >= 0; --oIdx2) {
            SkOpSpan* oTst2 = &other->fTs[oIdx2];            
            if (originalWindValue != oTst2->fWindValue) {
                goto skipAdvanceOtherCancel;
            }
            if (!oTst2->fWindValue) {
                goto skipAdvanceOtherCancel;
            }
            if (endPt == other->fTs[oIdx2].fPt) {
                break;
            }
        }
        do {
            SkASSERT(originalWindValue == oTest->fWindValue);
            if (decrement) {
                if (binary && !bigger) {
                    oTest->fOppValue--;
                } else {
                    other->decrementSpan(oTest);
                }
            } else if (track) {
                TrackOutsidePair(&oOutsidePts, *oTestPt, *testPt);
            }
            if (!oIndex) {
                break;
            }
            oTest = &other->fTs[--oIndex];
            oFoundEnd |= endPt == oTest->fPt;
        } while (!oFoundEnd || endPt == oTest->fPt);
    }
skipAdvanceOtherCancel:
    int outCount = outsidePts.count();
    if (!done() && outCount) {
        addCancelOutsides(outsidePts[0], outsidePts[1], other);
        if (outCount > 2) {
            addCancelOutsides(outsidePts[outCount - 2], outsidePts[outCount - 1], other);
        }
    }
    if (!other->done() && oOutsidePts.count()) {
        other->addCancelOutsides(oOutsidePts[0], oOutsidePts[1], this);
    }
    setCoincidentRange(startPt, endPt, other);
    other->setCoincidentRange(startPt, endPt, this);
}

int SkOpSegment::addSelfT(const SkPoint& pt, double newT) {
    // if the tail nearly intersects itself but not quite, the caller records this separately
    int result = addT(this, pt, newT);
    SkOpSpan* span = &fTs[result];
    fLoop = span->fLoop = true;
    return result;
}

// find the starting or ending span with an existing loop of angles
// FIXME? replicate for all identical starting/ending spans?
// OPTIMIZE? remove the spans pointing to windValue==0 here or earlier?
// FIXME? assert that only one other span has a valid windValue or oppValue
void SkOpSegment::addSimpleAngle(int index) {
    SkOpSpan* span = &fTs[index];
    int idx;
    int start, end;
    if (span->fT == 0) {
        idx = 0;
        span = &fTs[0];
        do {
            if (span->fToAngle) {
                SkASSERT(span->fToAngle->loopCount() == 2);
                SkASSERT(!span->fFromAngle);
                span->fFromAngle = span->fToAngle->next();
                return;
            }
            span = &fTs[++idx];
        } while (span->fT == 0);
        SkASSERT(!fTs[0].fTiny && fTs[idx].fT > 0);
        addStartSpan(idx);
        start = 0;
        end = idx;
    } else {
        idx = count() - 1;
        span = &fTs[idx];
        do {
            if (span->fFromAngle) {
                SkASSERT(span->fFromAngle->loopCount() == 2);
                SkASSERT(!span->fToAngle);
                span->fToAngle = span->fFromAngle->next();
                return;
            }
            span = &fTs[--idx];
        } while (span->fT == 1);
        SkASSERT(!fTs[idx].fTiny && fTs[idx].fT < 1);
        addEndSpan(++idx);
        start = idx;
        end = count();
    }
    SkOpSegment* other;
    SkOpSpan* oSpan;
    index = start;
    do {
        span = &fTs[index];
        other = span->fOther;
        int oFrom = span->fOtherIndex;
        oSpan = &other->fTs[oFrom];
        if (oSpan->fT < 1 && oSpan->fWindValue) {
            break;
        }
        if (oSpan->fT == 0) {
            continue;
        }
        oFrom = other->nextExactSpan(oFrom, -1);
        SkOpSpan* oFromSpan = &other->fTs[oFrom];
        SkASSERT(oFromSpan->fT < 1);
        if (oFromSpan->fWindValue) {
            break;
        }
    } while (++index < end);
    SkOpAngle* angle, * oAngle;
    if (span->fT == 0) {
        SkASSERT(span->fOtherIndex - 1 >= 0);
        SkASSERT(span->fOtherT == 1);
        SkDEBUGCODE(int oPriorIndex = other->nextExactSpan(span->fOtherIndex, -1));
        SkDEBUGCODE(const SkOpSpan& oPrior = other->span(oPriorIndex));
        SkASSERT(!oPrior.fTiny && oPrior.fT < 1);
        other->addEndSpan(span->fOtherIndex);
        angle = span->fToAngle;
        oAngle = oSpan->fFromAngle;
    } else {
        SkASSERT(span->fOtherIndex + 1 < other->count());
        SkASSERT(span->fOtherT == 0);
        SkASSERT(!oSpan->fTiny && (other->fTs[span->fOtherIndex + 1].fT > 0
                || (other->fTs[span->fOtherIndex + 1].fFromAngle == NULL
                && other->fTs[span->fOtherIndex + 1].fToAngle == NULL)));
        int oIndex = 1;
        do {
            const SkOpSpan& osSpan = other->span(oIndex);
            if (osSpan.fFromAngle || osSpan.fT > 0) {
                break;
            }
            ++oIndex;
            SkASSERT(oIndex < other->count());
        } while (true);
        other->addStartSpan(oIndex);
        angle = span->fFromAngle;
        oAngle = oSpan->fToAngle;
    }
    angle->insert(oAngle);
}

void SkOpSegment::alignMultiples(SkTDArray<AlignedSpan>* alignedArray) {
    debugValidate();
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        SkOpSpan& span = fTs[index];
        if (!span.fMultiple) {
            continue;
        }
        int end = nextExactSpan(index, 1);
        SkASSERT(end > index + 1);
        const SkPoint& thisPt = span.fPt;
        while (index < end - 1) {
            SkOpSegment* other1 = span.fOther;
            int oCnt = other1->count();
            for (int idx2 = index + 1; idx2 < end; ++idx2) {
                SkOpSpan& span2 = fTs[idx2];
                SkOpSegment* other2 = span2.fOther;
                for (int oIdx = 0; oIdx < oCnt; ++oIdx) {
                    SkOpSpan& oSpan = other1->fTs[oIdx];
                    if (oSpan.fOther != other2) {
                        continue;
                    }
                    if (oSpan.fPt == thisPt) {
                        goto skipExactMatches;
                    }
                }
                for (int oIdx = 0; oIdx < oCnt; ++oIdx) {
                    SkOpSpan& oSpan = other1->fTs[oIdx];
                    if (oSpan.fOther != other2) {
                        continue;
                    }
                    if (SkDPoint::RoughlyEqual(oSpan.fPt, thisPt)) {
                        SkOpSpan& oSpan2 = other2->fTs[oSpan.fOtherIndex];
                        if (zero_or_one(span.fOtherT) || zero_or_one(oSpan.fT)
                                || zero_or_one(span2.fOtherT) || zero_or_one(oSpan2.fT)) {
                            return;
                        }
                        if (!way_roughly_equal(span.fOtherT, oSpan.fT)
                                || !way_roughly_equal(span2.fOtherT, oSpan2.fT)
                                || !way_roughly_equal(span2.fOtherT, oSpan.fOtherT)
                                || !way_roughly_equal(span.fOtherT, oSpan2.fOtherT)) {
                            return;
                        }
                        alignSpan(thisPt, span.fOtherT, other1, span2.fOtherT,
                                other2, &oSpan, alignedArray);
                        alignSpan(thisPt, span2.fOtherT, other2, span.fOtherT, 
                                other1, &oSpan2, alignedArray);
                        break;
                    }
                }
        skipExactMatches:
                ;
            }
            ++index;
        }
    }
    debugValidate();
}

void SkOpSegment::alignSpan(const SkPoint& newPt, double newT, const SkOpSegment* other,
        double otherT, const SkOpSegment* other2, SkOpSpan* oSpan,
        SkTDArray<AlignedSpan>* alignedArray) {
    AlignedSpan* aligned = alignedArray->append();
    aligned->fOldPt = oSpan->fPt;
    aligned->fPt = newPt;
    aligned->fOldT = oSpan->fT;
    aligned->fT = newT;
    aligned->fSegment = this;  // OPTIMIZE: may be unused, can remove
    aligned->fOther1 = other;
    aligned->fOther2 = other2;
    SkASSERT(SkDPoint::RoughlyEqual(oSpan->fPt, newPt));
    oSpan->fPt = newPt;
//    SkASSERT(way_roughly_equal(oSpan->fT, newT));
    oSpan->fT = newT;
//    SkASSERT(way_roughly_equal(oSpan->fOtherT, otherT));
    oSpan->fOtherT = otherT;
}

bool SkOpSegment::alignSpan(int index, double thisT, const SkPoint& thisPt) {
    bool aligned = false;
    SkOpSpan* span = &fTs[index];
    SkOpSegment* other = span->fOther;
    int oIndex = span->fOtherIndex;
    SkOpSpan* oSpan = &other->fTs[oIndex];
    if (span->fT != thisT) {
        span->fT = thisT;
        oSpan->fOtherT = thisT;
        aligned = true;
    }
    if (span->fPt != thisPt) {
        span->fPt = thisPt;
        oSpan->fPt = thisPt;
        aligned = true;
    }
    double oT = oSpan->fT;
    if (oT == 0) {
        return aligned;
    }
    int oStart = other->nextSpan(oIndex, -1) + 1;
    oSpan = &other->fTs[oStart];
    int otherIndex = oStart;
    if (oT == 1) {
        if (aligned) {
            while (oSpan->fPt == thisPt && oSpan->fT != 1) {
                oSpan->fTiny = true;
                ++oSpan;
            }
        }
        return aligned;
    }
    oT = oSpan->fT;
    int oEnd = other->nextSpan(oIndex, 1);
    bool oAligned = false;
    if (oSpan->fPt != thisPt) {
        oAligned |= other->alignSpan(oStart, oT, thisPt);
    }
    while (++otherIndex < oEnd) {
        SkOpSpan* oNextSpan = &other->fTs[otherIndex];
        if (oNextSpan->fT != oT || oNextSpan->fPt != thisPt) {
            oAligned |= other->alignSpan(otherIndex, oT, thisPt);
        }
    }
    if (oAligned) {
        other->alignSpanState(oStart, oEnd);
    }
    return aligned;
}

void SkOpSegment::alignSpanState(int start, int end) {
    SkOpSpan* lastSpan = &fTs[--end];
    bool allSmall = lastSpan->fSmall;
    bool allTiny = lastSpan->fTiny;
    bool allDone = lastSpan->fDone;
    SkDEBUGCODE(int winding = lastSpan->fWindValue);
    SkDEBUGCODE(int oppWinding = lastSpan->fOppValue);
    int index = start;
    while (index < end) {
        SkOpSpan* span = &fTs[index];
        span->fSmall = allSmall;
        span->fTiny = allTiny;
        if (span->fDone != allDone) {
            span->fDone = allDone;
            fDoneSpans += allDone ? 1 : -1;
        }
        SkASSERT(span->fWindValue == winding);
        SkASSERT(span->fOppValue == oppWinding);
        ++index;
    }
}

void SkOpSegment::blindCancel(const SkCoincidence& coincidence, SkOpSegment* other) {
    bool binary = fOperand != other->fOperand;
    int index = 0;
    int last = this->count();
    do {
        SkOpSpan& span = this->fTs[--last];
        if (span.fT != 1 && !span.fSmall) {
            break;
        }
        span.fCoincident = true;
    } while (true);
    int oIndex = other->count();
    do {
        SkOpSpan& oSpan = other->fTs[--oIndex];
        if (oSpan.fT != 1 && !oSpan.fSmall) {
            break;
        }
        oSpan.fCoincident = true;
    } while (true);
    do {
        SkOpSpan* test = &this->fTs[index];
        int baseWind = test->fWindValue;
        int baseOpp = test->fOppValue;
        int endIndex = index;
        while (++endIndex <= last) {
            SkOpSpan* endSpan = &this->fTs[endIndex];
            SkASSERT(endSpan->fT < 1);
            if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) {
                break;
            }
            endSpan->fCoincident = true;
        }
        SkOpSpan* oTest = &other->fTs[oIndex];
        int oBaseWind = oTest->fWindValue;
        int oBaseOpp = oTest->fOppValue;
        int oStartIndex = oIndex;
        while (--oStartIndex >= 0) {
            SkOpSpan* oStartSpan = &other->fTs[oStartIndex];
            if (oStartSpan->fWindValue != oBaseWind || oStartSpan->fOppValue != oBaseOpp) {
                break;
            }
            oStartSpan->fCoincident = true;
        }
        bool decrement = baseWind && oBaseWind;
        bool bigger = baseWind >= oBaseWind;
        do {
            SkASSERT(test->fT < 1);
            if (decrement) {
                if (binary && bigger) {
                    test->fOppValue--;
                } else {
                    decrementSpan(test);
                }
            }
            test->fCoincident = true;
            test = &fTs[++index];
        } while (index < endIndex);
        do {
            SkASSERT(oTest->fT < 1);
            if (decrement) {
                if (binary && !bigger) {
                    oTest->fOppValue--;
                } else {
                    other->decrementSpan(oTest);
                }
            }
            oTest->fCoincident = true;
            oTest = &other->fTs[--oIndex];
        } while (oIndex > oStartIndex);
    } while (index <= last && oIndex >= 0);
    SkASSERT(index > last);
    SkASSERT(oIndex < 0);
}

void SkOpSegment::blindCoincident(const SkCoincidence& coincidence, SkOpSegment* other) {
    bool binary = fOperand != other->fOperand;
    int index = 0;
    int last = this->count();
    do {
        SkOpSpan& span = this->fTs[--last];
        if (span.fT != 1 && !span.fSmall) {
            break;
        }
        span.fCoincident = true;
    } while (true);
    int oIndex = 0;
    int oLast = other->count();
    do {
        SkOpSpan& oSpan = other->fTs[--oLast];
        if (oSpan.fT != 1 && !oSpan.fSmall) {
            break;
        }
        oSpan.fCoincident = true;
    } while (true);
    do {
        SkOpSpan* test = &this->fTs[index];
        int baseWind = test->fWindValue;
        int baseOpp = test->fOppValue;
        int endIndex = index;
        SkOpSpan* endSpan;
        while (++endIndex <= last) {
            endSpan = &this->fTs[endIndex];
            SkASSERT(endSpan->fT < 1);
            if (endSpan->fWindValue != baseWind || endSpan->fOppValue != baseOpp) {
                break;
            }
            endSpan->fCoincident = true;
        }
        SkOpSpan* oTest = &other->fTs[oIndex];
        int oBaseWind = oTest->fWindValue;
        int oBaseOpp = oTest->fOppValue;
        int oEndIndex = oIndex;
        SkOpSpan* oEndSpan;
        while (++oEndIndex <= oLast) {
            oEndSpan = &this->fTs[oEndIndex];
            SkASSERT(oEndSpan->fT < 1);
            if (oEndSpan->fWindValue != oBaseWind || oEndSpan->fOppValue != oBaseOpp) {
                break;
            }
            oEndSpan->fCoincident = true;
        }
        // consolidate the winding count even if done
        if ((test->fWindValue || test->fOppValue) && (oTest->fWindValue || oTest->fOppValue)) {
            if (!binary || test->fWindValue + oTest->fOppValue >= 0) {
                bumpCoincidentBlind(binary, index, endIndex);
                other->bumpCoincidentOBlind(oIndex, oEndIndex);
            } else {
                other->bumpCoincidentBlind(binary, oIndex, oEndIndex);
                bumpCoincidentOBlind(index, endIndex);
            }
        }
        index = endIndex;
        oIndex = oEndIndex;
    } while (index <= last && oIndex <= oLast);
    SkASSERT(index > last);
    SkASSERT(oIndex > oLast);
}

void SkOpSegment::bumpCoincidentBlind(bool binary, int index, int endIndex) {
    const SkOpSpan& oTest = fTs[index];
    int oWindValue = oTest.fWindValue;
    int oOppValue = oTest.fOppValue;
    if (binary) {
        SkTSwap<int>(oWindValue, oOppValue);
    }
    do {
        (void) bumpSpan(&fTs[index], oWindValue, oOppValue);
    } while (++index < endIndex);
}

void SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* indexPtr,
        SkTArray<SkPoint, true>* outsideTs) {
    int index = *indexPtr;
    int oWindValue = oTest.fWindValue;
    int oOppValue = oTest.fOppValue;
    if (binary) {
        SkTSwap<int>(oWindValue, oOppValue);
    }
    SkOpSpan* const test = &fTs[index];
    SkOpSpan* end = test;
    const SkPoint& oStartPt = oTest.fPt;
    do {
        if (bumpSpan(end, oWindValue, oOppValue)) {
            TrackOutside(outsideTs, oStartPt);
        }
        end = &fTs[++index];
    } while ((end->fPt == test->fPt || precisely_equal(end->fT, test->fT)) && end->fT < 1);
    *indexPtr = index;
}

void SkOpSegment::bumpCoincidentOBlind(int index, int endIndex) {
    do {
        zeroSpan(&fTs[index]);
    } while (++index < endIndex);
}

// because of the order in which coincidences are resolved, this and other
// may not have the same intermediate points. Compute the corresponding
// intermediate T values (using this as the master, other as the follower)
// and walk other conditionally -- hoping that it catches up in the end
void SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, int* oIndexPtr,
        SkTArray<SkPoint, true>* oOutsidePts) {
    int oIndex = *oIndexPtr;
    SkOpSpan* const oTest = &fTs[oIndex];
    SkOpSpan* oEnd = oTest;
    const SkPoint& oStartPt = oTest->fPt;
    double oStartT = oTest->fT;
#if 0  // FIXME : figure out what disabling this breaks
    const SkPoint& startPt = test.fPt;
    // this is always true since oEnd == oTest && oStartPt == oTest->fPt -- find proper condition
    if (oStartPt == oEnd->fPt || precisely_equal(oStartT, oEnd->fT)) {
        TrackOutside(oOutsidePts, startPt);
    }
#endif
    while (oStartPt == oEnd->fPt || precisely_equal(oStartT, oEnd->fT)) {
        zeroSpan(oEnd);
        oEnd = &fTs[++oIndex];
    }
    *oIndexPtr = oIndex;
}

// FIXME: need to test this case:
// contourA has two segments that are coincident
// contourB has two segments that are coincident in the same place
// each ends up with +2/0 pairs for winding count
// since logic below doesn't transfer count (only increments/decrements) can this be
// resolved to +4/0 ?

// set spans from start to end to increment the greater by one and decrement
// the lesser
bool SkOpSegment::addTCoincident(const SkPoint& startPt, const SkPoint& endPt, double endT,
        SkOpSegment* other) {
    bool binary = fOperand != other->fOperand;
    int index = 0;
    while (startPt != fTs[index].fPt) {
        SkASSERT(index < fTs.count());
        ++index;
    }
    double startT = fTs[index].fT;
    while (index > 0 && precisely_equal(fTs[index - 1].fT, startT)) {
        --index;
    }
    int oIndex = 0;
    while (startPt != other->fTs[oIndex].fPt) {
        SkASSERT(oIndex < other->fTs.count());
        ++oIndex;
    }
    double oStartT = other->fTs[oIndex].fT;
    while (oIndex > 0 && precisely_equal(other->fTs[oIndex - 1].fT, oStartT)) {
        --oIndex;
    }
    SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts;
    SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts;
    SkOpSpan* test = &fTs[index];
    const SkPoint* testPt = &test->fPt;
    double testT = test->fT;
    SkOpSpan* oTest = &other->fTs[oIndex];
    const SkPoint* oTestPt = &oTest->fPt;
    // paths with extreme data will fail this test and eject out of pathops altogether later on
    // SkASSERT(AlmostEqualUlps(*testPt, *oTestPt));
    do {
        SkASSERT(test->fT < 1);
        if (oTest->fT == 1) {
            // paths with extreme data may be so mismatched that we fail here
            return false;
        }

        // consolidate the winding count even if done
        if ((test->fWindValue == 0 && test->fOppValue == 0)
                || (oTest->fWindValue == 0 && oTest->fOppValue == 0)) {
            SkDEBUGCODE(int firstWind = test->fWindValue);
            SkDEBUGCODE(int firstOpp = test->fOppValue);
            do {
                SkASSERT(firstWind == fTs[index].fWindValue);
                SkASSERT(firstOpp == fTs[index].fOppValue);
                ++index;
                SkASSERT(index < fTs.count());
            } while (*testPt == fTs[index].fPt);
            SkDEBUGCODE(firstWind = oTest->fWindValue);
            SkDEBUGCODE(firstOpp = oTest->fOppValue);
            do {
                SkASSERT(firstWind == other->fTs[oIndex].fWindValue);
                SkASSERT(firstOpp == other->fTs[oIndex].fOppValue);
                ++oIndex;
                SkASSERT(oIndex < other->fTs.count());
            } while (*oTestPt == other->fTs[oIndex].fPt);
        } else {
            if (!binary || test->fWindValue + oTest->fOppValue >= 0) {
                bumpCoincidentThis(*oTest, binary, &index, &outsidePts);
                other->bumpCoincidentOther(*test, &oIndex, &oOutsidePts);
            } else {
                other->bumpCoincidentThis(*test, binary, &oIndex, &oOutsidePts);
                bumpCoincidentOther(*oTest, &index, &outsidePts);
            }
        }
        test = &fTs[index];
        testPt = &test->fPt;
        testT = test->fT;
        oTest = &other->fTs[oIndex];
        oTestPt = &oTest->fPt;
        if (endPt == *testPt || precisely_equal(endT, testT)) {
            break;
        }
//        SkASSERT(AlmostEqualUlps(*testPt, *oTestPt));
    } while (endPt != *oTestPt);
    // in rare cases, one may have ended before the other
    if (endPt != *testPt && !precisely_equal(endT, testT)) {
        int lastWind = test[-1].fWindValue;
        int lastOpp = test[-1].fOppValue;
        bool zero = lastWind == 0 && lastOpp == 0;
        do {
            if (test->fWindValue || test->fOppValue) {
                test->fWindValue = lastWind;
                test->fOppValue = lastOpp;
                if (zero) {
                    test->fDone = true;
                    ++fDoneSpans;
                }
            }
            test = &fTs[++index];
            testPt = &test->fPt;
        } while (endPt != *testPt);
    }
    if (endPt != *oTestPt) {
        // look ahead to see if zeroing more spans will allows us to catch up
        int oPeekIndex = oIndex;
        bool success = true;
        SkOpSpan* oPeek;
        int oCount = other->count();
        do {
            oPeek = &other->fTs[oPeekIndex];
            if (++oPeekIndex == oCount) {
                success = false;
                break;
            }
        } while (endPt != oPeek->fPt);
        if (success) {
            // make sure the matching point completes the coincidence span
            success = false;
            do {
                if (oPeek->fOther == this) {
                    success = true;
                    break;
                }
                if (++oPeekIndex == oCount) {
                    break;
                }
                oPeek = &other->fTs[oPeekIndex];
            } while (endPt == oPeek->fPt);
        }
        if (success) {
            do {
                if (!binary || test->fWindValue + oTest->fOppValue >= 0) {
                    other->bumpCoincidentOther(*test, &oIndex, &oOutsidePts);
                } else {
                    other->bumpCoincidentThis(*test, binary, &oIndex, &oOutsidePts);
                }
                oTest = &other->fTs[oIndex];
                oTestPt = &oTest->fPt;
            } while (endPt != *oTestPt);
        }
    }
    int outCount = outsidePts.count();
    if (!done() && outCount) {
        addCoinOutsides(outsidePts[0], endPt, other);
    }
    if (!other->done() && oOutsidePts.count()) {
        other->addCoinOutsides(oOutsidePts[0], endPt, this);
    }
    setCoincidentRange(startPt, endPt, other);
    other->setCoincidentRange(startPt, endPt, this);
    return true;
}

// FIXME: this doesn't prevent the same span from being added twice
// fix in caller, SkASSERT here?
// FIXME: this may erroneously reject adds for cubic loops
const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
        const SkPoint& pt, const SkPoint& pt2) {
    int tCount = fTs.count();
    for (int tIndex = 0; tIndex < tCount; ++tIndex) {
        const SkOpSpan& span = fTs[tIndex];
        if (!approximately_negative(span.fT - t)) {
            break;
        }
        if (span.fOther == other) {
            bool tsMatch = approximately_equal(span.fT, t);
            bool otherTsMatch = approximately_equal(span.fOtherT, otherT);
            // FIXME: add cubic loop detecting logic here
            // if fLoop bit is set on span, that could be enough if addOtherT copies the bit
            // or if a new bit is added ala fOtherLoop
            if (tsMatch || otherTsMatch) {
#if DEBUG_ADD_T_PAIR
                SkDebugf("%s addTPair duplicate this=%d %1.9g other=%d %1.9g\n",
                        __FUNCTION__, fID, t, other->fID, otherT);
#endif
                return NULL;
            }
        }
    }
    int oCount = other->count();
    for (int oIndex = 0; oIndex < oCount; ++oIndex) {
        const SkOpSpan& oSpan = other->span(oIndex);
        if (!approximately_negative(oSpan.fT - otherT)) {
            break;
        }
        if (oSpan.fOther == this) {
            bool otherTsMatch = approximately_equal(oSpan.fT, otherT);
            bool tsMatch = approximately_equal(oSpan.fOtherT, t);
            if (otherTsMatch || tsMatch) {
#if DEBUG_ADD_T_PAIR
                SkDebugf("%s addTPair other duplicate this=%d %1.9g other=%d %1.9g\n",
                        __FUNCTION__, fID, t, other->fID, otherT);
#endif
                return NULL;
            }
        }
    }
#if DEBUG_ADD_T_PAIR
    SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n",
            __FUNCTION__, fID, t, other->fID, otherT);
#endif
    SkASSERT(other != this);
    int insertedAt = addT(other, pt, t);
    int otherInsertedAt = other->addT(this, pt2, otherT);
    addOtherT(insertedAt, otherT, otherInsertedAt);
    other->addOtherT(otherInsertedAt, t, insertedAt);
    matchWindingValue(insertedAt, t, borrowWind);
    other->matchWindingValue(otherInsertedAt, otherT, borrowWind);
    SkOpSpan& span = this->fTs[insertedAt];
    if (pt != pt2) {
        span.fNear = true;
        SkOpSpan& oSpan = other->fTs[otherInsertedAt];
        oSpan.fNear = true;
    }
    return &span;
}

const SkOpSpan* SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
                           const SkPoint& pt) {
    return addTPair(t, other, otherT, borrowWind, pt, pt);
}

bool SkOpSegment::betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const {
    const SkPoint midPt = ptAtT(midT);
    SkPathOpsBounds bounds;
    bounds.set(pt1.fX, pt1.fY, pt2.fX, pt2.fY);
    bounds.sort();
    return bounds.almostContains(midPt);
}

bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const {
    if (lesser > greater) {
        SkTSwap<int>(lesser, greater);
    }
    return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT);
}

// in extreme cases (like the buffer overflow test) return false to abort
// for now, if one t value represents two different points, then the values are too extreme
// to generate meaningful results
bool SkOpSegment::calcAngles() {
    int spanCount = fTs.count();
    if (spanCount <= 2) {
        return spanCount == 2;
    }
    int index = 1;
    const SkOpSpan* firstSpan = &fTs[index];
    int activePrior = checkSetAngle(0);
    const SkOpSpan* span = &fTs[0];
    if (firstSpan->fT == 0 || span->fTiny || span->fOtherT != 1 || span->fOther->multipleEnds()) {
        index = findStartSpan(0);  // curve start intersects
        if (fTs[index].fT == 0) {
            return false;
        }
        SkASSERT(index > 0);
        if (activePrior >= 0) {
            addStartSpan(index);
        }
    }
    bool addEnd;
    int endIndex = spanCount - 1;
    span = &fTs[endIndex - 1];
    if ((addEnd = span->fT == 1 || span->fTiny)) {  // if curve end intersects
        endIndex = findEndSpan(endIndex);
        SkASSERT(endIndex > 0);
    } else {
        addEnd = fTs[endIndex].fOtherT != 0 || fTs[endIndex].fOther->multipleStarts();
    }
    SkASSERT(endIndex >= index);
    int prior = 0;
    while (index < endIndex) {
        const SkOpSpan& fromSpan = fTs[index];  // for each intermediate intersection
        const SkOpSpan* lastSpan;
        span = &fromSpan;
        int start = index;
        do {
            lastSpan = span;
            span = &fTs[++index];
            SkASSERT(index < spanCount);
            if (!precisely_negative(span->fT - lastSpan->fT) && !lastSpan->fTiny) {
                break;
            }
            if (!SkDPoint::ApproximatelyEqual(lastSpan->fPt, span->fPt)) {
                return false;
            }
        } while (true);
        SkOpAngle* angle = NULL;
        SkOpAngle* priorAngle;
        if (activePrior >= 0) {
            int pActive = firstActive(prior);
            SkASSERT(pActive < start);
            priorAngle = &fAngles.push_back();
            priorAngle->set(this, start, pActive);
        }
        int active = checkSetAngle(start);
        if (active >= 0) {
            SkASSERT(active < index);
            angle = &fAngles.push_back();
            angle->set(this, active, index);
        }
    #if DEBUG_ANGLE
        debugCheckPointsEqualish(start, index);
    #endif
        prior = start;
        do {
            const SkOpSpan* startSpan = &fTs[start - 1];
            if (!startSpan->fSmall || isCanceled(start - 1) || startSpan->fFromAngle
                    || startSpan->fToAngle) {
                break;
            }
            --start;
        } while (start > 0);
        do {
            if (activePrior >= 0) {
                SkASSERT(fTs[start].fFromAngle == NULL);
                fTs[start].fFromAngle = priorAngle;
            }
            if (active >= 0) {
                SkASSERT(fTs[start].fToAngle == NULL);
                fTs[start].fToAngle = angle;
            }
        } while (++start < index);
        activePrior = active;
    }
    if (addEnd && activePrior >= 0) {
        addEndSpan(endIndex);
    }
    return true;
}

int SkOpSegment::checkSetAngle(int tIndex) const {
    const SkOpSpan* span = &fTs[tIndex];
    while (span->fTiny /* || span->fSmall */) {
        span = &fTs[++tIndex];
    }
    return isCanceled(tIndex) ? -1 : tIndex;
}

// at this point, the span is already ordered, or unorderable
int SkOpSegment::computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType) {
    SkASSERT(includeType != SkOpAngle::kUnaryXor);
    SkOpAngle* firstAngle = spanToAngle(endIndex, startIndex);
    if (NULL == firstAngle || NULL == firstAngle->next()) {
        return SK_NaN32;
    }
    // if all angles have a computed winding,
    //  or if no adjacent angles are orderable,
    //  or if adjacent orderable angles have no computed winding,
    //  there's nothing to do
    // if two orderable angles are adjacent, and both are next to orderable angles,
    //  and one has winding computed, transfer to the other
    SkOpAngle* baseAngle = NULL;
    bool tryReverse = false;
    // look for counterclockwise transfers
    SkOpAngle* angle = firstAngle->previous();
    SkOpAngle* next = angle->next();
    firstAngle = next;
    do {
        SkOpAngle* prior = angle;
        angle = next;
        next = angle->next();
        SkASSERT(prior->next() == angle);
        SkASSERT(angle->next() == next);
        if (prior->unorderable() || angle->unorderable() || next->unorderable()) {
            baseAngle = NULL;
            continue;
        }
        int testWinding = angle->segment()->windSum(angle);
        if (SK_MinS32 != testWinding) {
            baseAngle = angle;
            tryReverse = true;
            continue;
        }
        if (baseAngle) {
            ComputeOneSum(baseAngle, angle, includeType);
            baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL;
        }
    } while (next != firstAngle);
    if (baseAngle && SK_MinS32 == firstAngle->segment()->windSum(firstAngle)) {
        firstAngle = baseAngle;
        tryReverse = true;
    }
    if (tryReverse) {
        baseAngle = NULL;
        SkOpAngle* prior = firstAngle;
        do {
            angle = prior;
            prior = angle->previous();
            SkASSERT(prior->next() == angle);
            next = angle->next();
            if (prior->unorderable() || angle->unorderable() || next->unorderable()) {
                baseAngle = NULL;
                continue;
            }
            int testWinding = angle->segment()->windSum(angle);
            if (SK_MinS32 != testWinding) {
                baseAngle = angle;
                continue;
            }
            if (baseAngle) {
                ComputeOneSumReverse(baseAngle, angle, includeType);
                baseAngle = SK_MinS32 != angle->segment()->windSum(angle) ? angle : NULL;
            }
        } while (prior != firstAngle);
    }
    int minIndex = SkMin32(startIndex, endIndex);
    return windSum(minIndex);
}

void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
        SkOpAngle::IncludeType includeType) {
    const SkOpSegment* baseSegment = baseAngle->segment();
    int sumMiWinding = baseSegment->updateWindingReverse(baseAngle);
    int sumSuWinding;
    bool binary = includeType >= SkOpAngle::kBinarySingle;
    if (binary) {
        sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle);
        if (baseSegment->operand()) {
            SkTSwap<int>(sumMiWinding, sumSuWinding);
        }
    }
    SkOpSegment* nextSegment = nextAngle->segment();
    int maxWinding, sumWinding;
    SkOpSpan* last;
    if (binary) {
        int oppMaxWinding, oppSumWinding;
        nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
                &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
        last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
                nextAngle);
    } else {
        nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
                &maxWinding, &sumWinding);
        last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle);
    }
    nextAngle->setLastMarked(last);
}

void SkOpSegment::ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
        SkOpAngle::IncludeType includeType) {
    const SkOpSegment* baseSegment = baseAngle->segment();
    int sumMiWinding = baseSegment->updateWinding(baseAngle);
    int sumSuWinding;
    bool binary = includeType >= SkOpAngle::kBinarySingle;
    if (binary) {
        sumSuWinding = baseSegment->updateOppWinding(baseAngle);
        if (baseSegment->operand()) {
            SkTSwap<int>(sumMiWinding, sumSuWinding);
        }
    }
    SkOpSegment* nextSegment = nextAngle->segment();
    int maxWinding, sumWinding;
    SkOpSpan* last;
    if (binary) {
        int oppMaxWinding, oppSumWinding;
        nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
                &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
        last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
                nextAngle);
    } else {
        nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
                &maxWinding, &sumWinding);
        last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle);
    }
    nextAngle->setLastMarked(last);
}

bool SkOpSegment::containsPt(const SkPoint& pt, int index, int endIndex) const {
    int step = index < endIndex ? 1 : -1;
    do {
        const SkOpSpan& span = this->span(index);
        if (span.fPt == pt) {
            const SkOpSpan& endSpan = this->span(endIndex);
            return span.fT == endSpan.fT && pt != endSpan.fPt;
        }
        index += step;
    } while (index != endIndex);
    return false;
}

bool SkOpSegment::containsT(double t, const SkOpSegment* other, double otherT) const {
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (t < span.fT) {
            return false;
        }
        if (t == span.fT) {
            if (other != span.fOther) {
                continue;
            }
            if (other->fVerb != SkPath::kCubic_Verb) {
                return true;
            }
            if (!other->fLoop) {
                return true;
            }
            double otherMidT = (otherT + span.fOtherT) / 2;
            SkPoint otherPt = other->ptAtT(otherMidT);
            return SkDPoint::ApproximatelyEqual(span.fPt, otherPt);
        }
    }
    return false;
}

int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT,
                              bool* hitSomething, double mid, bool opp, bool current) const {
    SkScalar bottom = fBounds.fBottom;
    int bestTIndex = -1;
    if (bottom <= *bestY) {
        return bestTIndex;
    }
    SkScalar top = fBounds.fTop;
    if (top >= basePt.fY) {
        return bestTIndex;
    }
    if (fBounds.fLeft > basePt.fX) {
        return bestTIndex;
    }
    if (fBounds.fRight < basePt.fX) {
        return bestTIndex;
    }
    if (fBounds.fLeft == fBounds.fRight) {
        // if vertical, and directly above test point, wait for another one
        return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTIndex;
    }
    // intersect ray starting at basePt with edge
    SkIntersections intersections;
    // OPTIMIZE: use specialty function that intersects ray with curve,
    // returning t values only for curve (we don't care about t on ray)
    intersections.allowNear(false);
    int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)])
            (fPts, top, bottom, basePt.fX, false);
    if (pts == 0 || (current && pts == 1)) {
        return bestTIndex;
    }
    if (current) {
        SkASSERT(pts > 1);
        int closestIdx = 0;
        double closest = fabs(intersections[0][0] - mid);
        for (int idx = 1; idx < pts; ++idx) {
            double test = fabs(intersections[0][idx] - mid);
            if (closest > test) {
                closestIdx = idx;
                closest = test;
            }
        }
        intersections.quickRemoveOne(closestIdx, --pts);
    }
    double bestT = -1;
    for (int index = 0; index < pts; ++index) {
        double foundT = intersections[0][index];
        if (approximately_less_than_zero(foundT)
                    || approximately_greater_than_one(foundT)) {
            continue;
        }
        SkScalar testY = (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fY;
        if (approximately_negative(testY - *bestY)
                || approximately_negative(basePt.fY - testY)) {
            continue;
        }
        if (pts > 1 && fVerb == SkPath::kLine_Verb) {
            return SK_MinS32;  // if the intersection is edge on, wait for another one
        }
        if (fVerb > SkPath::kLine_Verb) {
            SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fX;
            if (approximately_zero(dx)) {
                return SK_MinS32;  // hit vertical, wait for another one
            }
        }
        *bestY = testY;
        bestT = foundT;
    }
    if (bestT < 0) {
        return bestTIndex;
    }
    SkASSERT(bestT >= 0);
    SkASSERT(bestT <= 1);
    int start;
    int end = 0;
    do {
        start = end;
        end = nextSpan(start, 1);
    } while (fTs[end].fT < bestT);
    // FIXME: see next candidate for a better pattern to find the next start/end pair
    while (start + 1 < end && fTs[start].fDone) {
        ++start;
    }
    if (!isCanceled(start)) {
        *hitT = bestT;
        bestTIndex = start;
        *hitSomething = true;
    }
    return bestTIndex;
}

bool SkOpSegment::decrementSpan(SkOpSpan* span) {
    SkASSERT(span->fWindValue > 0);
    if (--(span->fWindValue) == 0) {
        if (!span->fOppValue && !span->fDone) {
            span->fDone = true;
            ++fDoneSpans;
            return true;
        }
    }
    return false;
}

bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) {
    SkASSERT(!span->fDone || span->fTiny || span->fSmall);
    span->fWindValue += windDelta;
    SkASSERT(span->fWindValue >= 0);
    span->fOppValue += oppDelta;
    SkASSERT(span->fOppValue >= 0);
    if (fXor) {
        span->fWindValue &= 1;
    }
    if (fOppXor) {
        span->fOppValue &= 1;
    }
    if (!span->fWindValue && !span->fOppValue) {
        span->fDone = true;
        ++fDoneSpans;
        return true;
    }
    return false;
}

const SkOpSpan& SkOpSegment::firstSpan(const SkOpSpan& thisSpan) const {
    const SkOpSpan* firstSpan = &thisSpan; // rewind to the start
    const SkOpSpan* beginSpan = fTs.begin();
    const SkPoint& testPt = thisSpan.fPt;
    while (firstSpan > beginSpan && firstSpan[-1].fPt == testPt) {
        --firstSpan;
    }
    return *firstSpan;
}

const SkOpSpan& SkOpSegment::lastSpan(const SkOpSpan& thisSpan) const {
    const SkOpSpan* endSpan = fTs.end() - 1;  // last can't be small
    const SkOpSpan* lastSpan = &thisSpan;  // find the end
    const SkPoint& testPt = thisSpan.fPt;
    while (lastSpan < endSpan && lastSpan[1].fPt == testPt) {
        ++lastSpan;
    }
    return *lastSpan;
}

// with a loop, the comparison is move involved
// scan backwards and forwards to count all matching points
// (verify that there are twp scans marked as loops)
// compare that against 2 matching scans for loop plus other results
bool SkOpSegment::calcLoopSpanCount(const SkOpSpan& thisSpan, int* smallCounts) {
    const SkOpSpan& firstSpan = this->firstSpan(thisSpan); // rewind to the start
    const SkOpSpan& lastSpan = this->lastSpan(thisSpan);  // find the end
    double firstLoopT = -1, lastLoopT = -1;
    const SkOpSpan* testSpan = &firstSpan - 1;
    while (++testSpan <= &lastSpan) {
        if (testSpan->fLoop) {
            firstLoopT = testSpan->fT;
            break;
        }
    }
    testSpan = &lastSpan + 1;
    while (--testSpan >= &firstSpan) {
        if (testSpan->fLoop) {
            lastLoopT = testSpan->fT;
            break;
        }
    }
    SkASSERT((firstLoopT == -1) == (lastLoopT == -1));
    if (firstLoopT == -1) {
        return false;
    }
    SkASSERT(firstLoopT < lastLoopT);
    testSpan = &firstSpan - 1;
    smallCounts[0] = smallCounts[1] = 0;
    while (++testSpan <= &lastSpan) {
        SkASSERT(approximately_equal(testSpan->fT, firstLoopT) +
                approximately_equal(testSpan->fT, lastLoopT) == 1);
        smallCounts[approximately_equal(testSpan->fT, lastLoopT)]++;
    }
    return true;
}

double SkOpSegment::calcMissingTEnd(const SkOpSegment* ref, double loEnd, double min, double max,
        double hiEnd, const SkOpSegment* other, int thisStart) {
    if (max >= hiEnd) {
        return -1;
    }
    int end = findOtherT(hiEnd, ref);
    if (end < 0) {
        return -1;
    }
    double tHi = span(end).fT;
    double tLo, refLo;
    if (thisStart >= 0) {
        tLo = span(thisStart).fT;
        refLo = min;
    } else {
        int start1 = findOtherT(loEnd, ref);
        SkASSERT(start1 >= 0);
        tLo = span(start1).fT;
        refLo = loEnd;
    }
    double missingT = (max - refLo) / (hiEnd - refLo);
    missingT = tLo + missingT * (tHi - tLo);
    return missingT;
}

double SkOpSegment::calcMissingTStart(const SkOpSegment* ref, double loEnd, double min, double max,
        double hiEnd, const SkOpSegment* other, int thisEnd) {
    if (min <= loEnd) {
        return -1;
    }
    int start = findOtherT(loEnd, ref);
    if (start < 0) {
        return -1;
    }
    double tLo = span(start).fT;
    double tHi, refHi;
    if (thisEnd >= 0) {
        tHi = span(thisEnd).fT;
        refHi = max;
    } else {
        int end1 = findOtherT(hiEnd, ref);
        if (end1 < 0) {
            return -1;
        }
        tHi = span(end1).fT;
        refHi = hiEnd;
    }
    double missingT = (min - loEnd) / (refHi - loEnd);
    missingT = tLo + missingT * (tHi - tLo);
    return missingT;
}

// see if spans with two or more intersections have the same number on the other end
void SkOpSegment::checkDuplicates() {
    debugValidate();
    SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
    int index;
    int endIndex = 0;
    bool endFound;
    do {
        index = endIndex;
        endIndex = nextExactSpan(index, 1);
        if ((endFound = endIndex < 0)) {
            endIndex = count();
        }
        int dupCount = endIndex - index;
        if (dupCount < 2) {
            continue;
        }
        do {
            const SkOpSpan* thisSpan = &fTs[index];
            if (thisSpan->fNear) {
                continue;
            }
            SkOpSegment* other = thisSpan->fOther;
            int oIndex = thisSpan->fOtherIndex;
            int oStart = other->nextExactSpan(oIndex, -1) + 1;
            int oEnd = other->nextExactSpan(oIndex, 1);
            if (oEnd < 0) {
                oEnd = other->count();
            }
            int oCount = oEnd - oStart;
            // force the other to match its t and this pt if not on an end point
            if (oCount != dupCount) {
                MissingSpan& missing = missingSpans.push_back();
                missing.fOther = NULL;
                SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
                missing.fPt = thisSpan->fPt;
                const SkOpSpan& oSpan = other->span(oIndex);
                if (oCount > dupCount) {
                    missing.fSegment = this;
                    missing.fT = thisSpan->fT;
                    other->checkLinks(&oSpan, &missingSpans);
                } else {
                    missing.fSegment = other;
                    missing.fT = oSpan.fT;
                    checkLinks(thisSpan, &missingSpans);
                }
                if (!missingSpans.back().fOther) {
                    missingSpans.pop_back();
                }
            }
        } while (++index < endIndex);
    } while (!endFound);
    int missingCount = missingSpans.count();
    if (missingCount == 0) {
        return;
    }
    SkSTArray<kMissingSpanCount, MissingSpan, true> missingCoincidence;
    for (index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        SkOpSegment* missingOther = missing.fOther;
        if (missing.fSegment == missing.fOther) {
            continue;
        }
#if 0  // FIXME: this eliminates spurious data from skpwww_argus_presse_fr_41 but breaks
       // skpwww_fashionscandal_com_94 -- calcAngles complains, but I don't understand why
        if (missing.fSegment->containsT(missing.fT, missing.fOther, missing.fOtherT)) {
#if DEBUG_DUPLICATES
            SkDebugf("skip 1 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fSegment->fID,
                    missing.fT, missing.fOther->fID, missing.fOtherT);
#endif
            continue;
        }
        if (missing.fOther->containsT(missing.fOtherT, missing.fSegment, missing.fT)) {
#if DEBUG_DUPLICATES
            SkDebugf("skip 2 id=%d t=%1.9g other=%d otherT=%1.9g\n", missing.fOther->fID,
                    missing.fOtherT, missing.fSegment->fID, missing.fT);
#endif
            continue;
        }
#endif
        // skip if adding would insert point into an existing coincindent span
        if (missing.fSegment->inCoincidentSpan(missing.fT, missingOther)
                && missingOther->inCoincidentSpan(missing.fOtherT, this)) {
            continue;
        }
        // skip if the created coincident spans are small
        if (missing.fSegment->coincidentSmall(missing.fPt, missing.fT, missingOther)
                && missingOther->coincidentSmall(missing.fPt, missing.fOtherT, missing.fSegment)) {
            continue;
        }
        const SkOpSpan* added = missing.fSegment->addTPair(missing.fT, missingOther,
                missing.fOtherT, false, missing.fPt);
        if (added && added->fSmall) {
            missing.fSegment->checkSmallCoincidence(*added, &missingCoincidence);
        }
    }
    for (index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        missing.fSegment->fixOtherTIndex();
        missing.fOther->fixOtherTIndex();
    }
    for (index = 0; index < missingCoincidence.count(); ++index) {
        MissingSpan& missing = missingCoincidence[index];
        missing.fSegment->fixOtherTIndex();
    }
    debugValidate();
}

// look to see if the curve end intersects an intermediary that intersects the other
void SkOpSegment::checkEnds() {
    debugValidate();
    SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
    int count = fTs.count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        double otherT = span.fOtherT;
        if (otherT != 0 && otherT != 1) { // only check ends
            continue;
        }
        const SkOpSegment* other = span.fOther;
        // peek start/last describe the range of spans that match the other t of this span
        int peekStart = span.fOtherIndex;
        while (--peekStart >= 0 && other->fTs[peekStart].fT == otherT)
            ;
        int otherCount = other->fTs.count();
        int peekLast = span.fOtherIndex;
        while (++peekLast < otherCount && other->fTs[peekLast].fT == otherT)
            ;
        if (++peekStart == --peekLast) { // if there isn't a range, there's nothing to do
            continue;
        }
        // t start/last describe the range of spans that match the t of this span
        double t = span.fT;
        double tBottom = -1;
        int tStart = -1;
        int tLast = count;
        bool lastSmall = false;
        double afterT = t;
        for (int inner = 0; inner < count; ++inner) {
            double innerT = fTs[inner].fT;
            if (innerT <= t && innerT > tBottom) {
                if (innerT < t || !lastSmall) {
                    tStart = inner - 1;
                }
                tBottom = innerT;
            }
            if (innerT > afterT) {
                if (t == afterT && lastSmall) {
                    afterT = innerT;
                } else {
                    tLast = inner;
                    break;
                }
            }
            lastSmall = innerT <= t ? fTs[inner].fSmall : false;
        }
        for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) {
            if (peekIndex == span.fOtherIndex) {  // skip the other span pointed to by this span
                continue;
            }
            const SkOpSpan& peekSpan = other->fTs[peekIndex];
            SkOpSegment* match = peekSpan.fOther;
            if (match->done()) {
                continue;  // if the edge has already been eaten (likely coincidence), ignore it
            }
            const double matchT = peekSpan.fOtherT;
            // see if any of the spans match the other spans
            for (int tIndex = tStart + 1; tIndex < tLast; ++tIndex) {
                const SkOpSpan& tSpan = fTs[tIndex];
                if (tSpan.fOther == match) {
                    if (tSpan.fOtherT == matchT) {
                        goto nextPeekIndex;
                    }
                    double midT = (tSpan.fOtherT + matchT) / 2;
                    if (match->betweenPoints(midT, tSpan.fPt, peekSpan.fPt)) {
                        goto nextPeekIndex;
                    }
                }
            }
            if (missingSpans.count() > 0) {
                const MissingSpan& lastMissing = missingSpans.back();
                if (lastMissing.fT == t
                        && lastMissing.fOther == match
                        && lastMissing.fOtherT == matchT) {
                    SkASSERT(lastMissing.fPt == peekSpan.fPt);
                    continue;
                }
            }
#if DEBUG_CHECK_ENDS
            SkDebugf("%s id=%d missing t=%1.9g other=%d otherT=%1.9g pt=(%1.9g,%1.9g)\n",
                    __FUNCTION__, fID, t, match->fID, matchT, peekSpan.fPt.fX, peekSpan.fPt.fY);
#endif
            // this segment is missing a entry that the other contains
            // remember so we can add the missing one and recompute the indices
            {
                MissingSpan& missing = missingSpans.push_back();
                SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
                missing.fT = t;
                SkASSERT(this != match);
                missing.fOther = match;
                missing.fOtherT = matchT;
                missing.fPt = peekSpan.fPt;
            }
            break;
nextPeekIndex:
            ;
        }
    }
    if (missingSpans.count() == 0) {
        debugValidate();
        return;
    }
    debugValidate();
    int missingCount = missingSpans.count();
    for (int index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        if (this != missing.fOther) {
            addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt);
        }
    }
    fixOtherTIndex();
    // OPTIMIZATION: this may fix indices more than once. Build an array of unique segments to
    // avoid this
    for (int index = 0; index < missingCount; ++index)  {
        missingSpans[index].fOther->fixOtherTIndex();
    }
    debugValidate();
}

void SkOpSegment::checkLinks(const SkOpSpan* base,
        SkTArray<MissingSpan, true>* missingSpans) const {
    const SkOpSpan* first = fTs.begin();
    const SkOpSpan* last = fTs.end() - 1;
    SkASSERT(base >= first && last >= base);
    const SkOpSegment* other = base->fOther;
    const SkOpSpan* oFirst = other->fTs.begin();
    const SkOpSpan* oLast = other->fTs.end() - 1;
    const SkOpSpan* oSpan = &other->fTs[base->fOtherIndex];
    const SkOpSpan* test = base;
    const SkOpSpan* missing = NULL;
    while (test > first && (--test)->fPt == base->fPt) {
        if (this == test->fOther) {
            continue;
        }
        CheckOneLink(test, oSpan, oFirst, oLast, &missing, missingSpans);
    }
    test = base;
    while (test < last && (++test)->fPt == base->fPt) {
        SkASSERT(this != test->fOther);
        CheckOneLink(test, oSpan, oFirst, oLast, &missing, missingSpans);
    }
}

// see if spans with two or more intersections all agree on common t and point values
void SkOpSegment::checkMultiples() {
    debugValidate();
    int index;
    int end = 0;
    while (fTs[++end].fT == 0)
        ;
    while (fTs[end].fT < 1) {
        int start = index = end;
        end = nextExactSpan(index, 1);
        if (end <= index) {
            return;  // buffer overflow example triggers this
        }
        if (index + 1 == end) {
            continue;
        }
        // force the duplicates to agree on t and pt if not on the end
        SkOpSpan& span = fTs[index];
        double thisT = span.fT;
        const SkPoint& thisPt = span.fPt;
        span.fMultiple = true;
        bool aligned = false;
        while (++index < end) {
            aligned |= alignSpan(index, thisT, thisPt);
        }
        if (aligned) {
            alignSpanState(start, end);
        }
        fMultiples = true;
    }
    debugValidate();
}

void SkOpSegment::CheckOneLink(const SkOpSpan* test, const SkOpSpan* oSpan,
        const SkOpSpan* oFirst, const SkOpSpan* oLast, const SkOpSpan** missingPtr,
        SkTArray<MissingSpan, true>* missingSpans) {
    SkASSERT(oSpan->fPt == test->fPt);
    const SkOpSpan* oTest = oSpan;
    while (oTest > oFirst && (--oTest)->fPt == test->fPt) {
        if (oTest->fOther == test->fOther && oTest->fOtherT == test->fOtherT) {
            return;
        }
    }
    oTest = oSpan;
    while (oTest < oLast && (++oTest)->fPt == test->fPt) {
        if (oTest->fOther == test->fOther && oTest->fOtherT == test->fOtherT) {
            return;
        }
    }
    if (*missingPtr) {
        missingSpans->push_back();
    }
    MissingSpan& lastMissing = missingSpans->back();
    if (*missingPtr) {
        lastMissing = missingSpans->end()[-2];
    }
    *missingPtr = test;
    lastMissing.fOther = test->fOther;
    lastMissing.fOtherT = test->fOtherT;
}

bool SkOpSegment::checkSmall(int index) const {
    if (fTs[index].fSmall) {
        return true;
    }
    double tBase = fTs[index].fT;
    while (index > 0 && precisely_negative(tBase - fTs[--index].fT))
        ;
    return fTs[index].fSmall;
}

// a pair of curves may turn into coincident lines -- small may be a hint that that happened
// if a cubic contains a loop, the counts must be adjusted
void SkOpSegment::checkSmall() {
    SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
    const SkOpSpan* beginSpan = fTs.begin();
    const SkOpSpan* thisSpan = beginSpan - 1;
    const SkOpSpan* endSpan = fTs.end() - 1;  // last can't be small
    while (++thisSpan < endSpan) {
        if (!thisSpan->fSmall) {
            continue;
        }
        if (!thisSpan->fWindValue) {
            continue;
        }
        const SkOpSpan& firstSpan = this->firstSpan(*thisSpan);
        const SkOpSpan& lastSpan = this->lastSpan(*thisSpan);
        const SkOpSpan* nextSpan = &firstSpan + 1;
        ptrdiff_t smallCount = &lastSpan - &firstSpan + 1;
        SkASSERT(1 <= smallCount && smallCount < count());
        if (smallCount <= 1 && !nextSpan->fSmall) {
            SkASSERT(1 == smallCount);
            checkSmallCoincidence(firstSpan, NULL);
            continue;
        }
        // at this point, check for missing computed intersections
        const SkPoint& testPt = firstSpan.fPt;
        thisSpan = &firstSpan - 1;
        SkOpSegment* other = NULL;
        while (++thisSpan <= &lastSpan) {
            other = thisSpan->fOther;
            if (other != this) {
                break;
            }
        }
        SkASSERT(other != this);
        int oIndex = thisSpan->fOtherIndex;
        const SkOpSpan& oSpan = other->span(oIndex);
        const SkOpSpan& oFirstSpan = other->firstSpan(oSpan);
        const SkOpSpan& oLastSpan = other->lastSpan(oSpan);
        ptrdiff_t oCount = &oLastSpan - &oFirstSpan + 1;
        if (fLoop) {
            int smallCounts[2];
            SkASSERT(!other->fLoop);  // FIXME: we need more complicated logic for pair of loops
            if (calcLoopSpanCount(*thisSpan, smallCounts)) {
                if (smallCounts[0] && oCount != smallCounts[0]) {
                    SkASSERT(0);  // FIXME: need a working test case to properly code & debug
                }
                if (smallCounts[1] && oCount != smallCounts[1]) {
                    SkASSERT(0);  // FIXME: need a working test case to properly code & debug
                }
                goto nextSmallCheck;
            }
        }
        if (other->fLoop) {
            int otherCounts[2];
            if (other->calcLoopSpanCount(other->span(oIndex), otherCounts)) {
                if (otherCounts[0] && otherCounts[0] != smallCount) {
                    SkASSERT(0);  // FIXME: need a working test case to properly code & debug
                }
                if (otherCounts[1] && otherCounts[1] != smallCount) {
                    SkASSERT(0);  // FIXME: need a working test case to properly code & debug
                }
                goto nextSmallCheck;
            }
        }
        if (oCount != smallCount) {  // check if number of pts in this match other
            MissingSpan& missing = missingSpans.push_back();
            missing.fOther = NULL;
            SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
            missing.fPt = testPt;
            const SkOpSpan& oSpan = other->span(oIndex);
            if (oCount > smallCount) {
                missing.fSegment = this;
                missing.fT = thisSpan->fT;
                other->checkLinks(&oSpan, &missingSpans);
            } else {
                missing.fSegment = other;
                missing.fT = oSpan.fT;
                checkLinks(thisSpan, &missingSpans);
            }
            if (!missingSpans.back().fOther || missing.fSegment->done()) {
                missingSpans.pop_back();
            }
        }
nextSmallCheck:
        thisSpan = &lastSpan;
    }
    int missingCount = missingSpans.count();
    for (int index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        SkOpSegment* missingOther = missing.fOther;
        // note that add t pair may edit span arrays, so prior pointers to spans are no longer valid
        if (!missing.fSegment->addTPair(missing.fT, missingOther, missing.fOtherT, false,
                missing.fPt)) {
            continue;
        }
        int otherTIndex = missingOther->findT(missing.fOtherT, missing.fPt, missing.fSegment);
        const SkOpSpan& otherSpan = missingOther->span(otherTIndex);
        if (otherSpan.fSmall) {
            const SkOpSpan* nextSpan = &otherSpan;
            do {
                ++nextSpan;
            } while (nextSpan->fSmall);
            SkAssertResult(missing.fSegment->addTCoincident(missing.fPt, nextSpan->fPt,
                    nextSpan->fT, missingOther));
        } else if (otherSpan.fT > 0) {
            const SkOpSpan* priorSpan = &otherSpan;
            do {
                --priorSpan;
            } while (priorSpan->fT == otherSpan.fT);
            if (priorSpan->fSmall) {
                missing.fSegment->addTCancel(missing.fPt, priorSpan->fPt, missingOther);
            }
        }
    }
    // OPTIMIZATION: this may fix indices more than once. Build an array of unique segments to
    // avoid this
    for (int index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        missing.fSegment->fixOtherTIndex();
        missing.fOther->fixOtherTIndex();
    }
    debugValidate();
}

void SkOpSegment::checkSmallCoincidence(const SkOpSpan& span,
        SkTArray<MissingSpan, true>* checkMultiple) {
    SkASSERT(span.fSmall);
    if (0 && !span.fWindValue) {
        return;
    }
    SkASSERT(&span < fTs.end() - 1);
    const SkOpSpan* next = &span + 1;
    SkASSERT(!next->fSmall || checkMultiple);
    if (checkMultiple) {
        while (next->fSmall) {
            ++next;
            SkASSERT(next < fTs.end());
        }
    }
    SkOpSegment* other = span.fOther;
    while (other != next->fOther) {
        if (!checkMultiple) {
            return;
        }
        const SkOpSpan* test = next + 1;
        if (test == fTs.end()) {
            return;
        }
        if (test->fPt != next->fPt || !precisely_equal(test->fT, next->fT)) {
            return;
        }
        next = test;
    }
    SkASSERT(span.fT < next->fT);
    int oStartIndex = other->findExactT(span.fOtherT, this);
    int oEndIndex = other->findExactT(next->fOtherT, this);
    // FIXME: be overly conservative by limiting this to the caller that allows multiple smalls
    if (!checkMultiple || fVerb != SkPath::kLine_Verb || other->fVerb != SkPath::kLine_Verb) {
        SkPoint mid = ptAtT((span.fT + next->fT) / 2);
        const SkOpSpan& oSpanStart = other->fTs[oStartIndex];
        const SkOpSpan& oSpanEnd = other->fTs[oEndIndex];
        SkPoint oMid = other->ptAtT((oSpanStart.fT + oSpanEnd.fT) / 2);
        if (!SkDPoint::ApproximatelyEqual(mid, oMid)) {
            return;
        }
    }
    // FIXME: again, be overly conservative to avoid breaking existing tests
    const SkOpSpan& oSpan = oStartIndex < oEndIndex ? other->fTs[oStartIndex]
            : other->fTs[oEndIndex];
    if (checkMultiple && !oSpan.fSmall) {
        return;
    }
//    SkASSERT(oSpan.fSmall);
    if (oStartIndex < oEndIndex) {
        SkAssertResult(addTCoincident(span.fPt, next->fPt, next->fT, other));
    } else {
        addTCancel(span.fPt, next->fPt, other);
    }
    if (!checkMultiple) {
        return;
    }
    // check to see if either segment is coincident with a third segment -- if it is, and if
    // the opposite segment is not already coincident with the third, make it so
    // OPTIMIZE: to make this check easier, add coincident and cancel could set a coincident bit
    if (span.fWindValue != 1 || span.fOppValue != 0) {
//        start here;
        // iterate through the spans, looking for the third coincident case
        // if we find one, we need to return state to the caller so that the indices can be fixed
        // this also suggests that all of this function is fragile since it relies on a valid index
    }
    // probably should make this a common function rather than copy/paste code
    if (oSpan.fWindValue != 1 || oSpan.fOppValue != 0) {
        const SkOpSpan* oTest = &oSpan;
        while (--oTest >= other->fTs.begin()) {
            if (oTest->fPt != oSpan.fPt || !precisely_equal(oTest->fT, oSpan.fT)) {
                break;
            }
            SkOpSegment* testOther = oTest->fOther;
            SkASSERT(testOther != this);
            // look in both directions to see if there is a coincident span
            const SkOpSpan* tTest = testOther->fTs.begin();
            for (int testIndex = 0; testIndex < testOther->count(); ++testIndex) {
                if (tTest->fPt != span.fPt) {
                    ++tTest;
                    continue;
                }
                if (testOther->verb() != SkPath::kLine_Verb
                        || other->verb() != SkPath::kLine_Verb) {
                    SkPoint mid = ptAtT((span.fT + next->fT) / 2);
                    SkPoint oMid = other->ptAtT((oTest->fOtherT + tTest->fT) / 2);
                    if (!SkDPoint::ApproximatelyEqual(mid, oMid)) {
                        continue;
                    }
                }
#if DEBUG_CONCIDENT
                SkDebugf("%s coincident found=%d %1.9g %1.9g\n", __FUNCTION__, testOther->fID,
                        oTest->fOtherT, tTest->fT);
#endif
                if (tTest->fT < oTest->fOtherT) {
                    SkAssertResult(addTCoincident(span.fPt, next->fPt, next->fT, testOther));
                } else {
                    addTCancel(span.fPt, next->fPt, testOther);
                }
                MissingSpan missing;
                missing.fSegment = testOther;
                checkMultiple->push_back(missing);
                break;
            }
        }
        oTest = &oSpan;
        while (++oTest < other->fTs.end()) {
            if (oTest->fPt != oSpan.fPt || !precisely_equal(oTest->fT, oSpan.fT)) {
                break;
            }

        }
    }
}

// if pair of spans on either side of tiny have the same end point and mid point, mark
// them as parallel
void SkOpSegment::checkTiny() {
    SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
    SkOpSpan* thisSpan = fTs.begin() - 1;
    const SkOpSpan* endSpan = fTs.end() - 1;  // last can't be tiny
    while (++thisSpan < endSpan) {
        if (!thisSpan->fTiny) {
            continue;
        }
        SkOpSpan* nextSpan = thisSpan + 1;
        double thisT = thisSpan->fT;
        double nextT = nextSpan->fT;
        if (thisT == nextT) {
            continue;
        }
        SkASSERT(thisT < nextT);
        SkASSERT(thisSpan->fPt == nextSpan->fPt);
        SkOpSegment* thisOther = thisSpan->fOther;
        SkOpSegment* nextOther = nextSpan->fOther;
        int oIndex = thisSpan->fOtherIndex;
        for (int oStep = -1; oStep <= 1; oStep += 2) {
            int oEnd = thisOther->nextExactSpan(oIndex, oStep);
            if (oEnd < 0) {
                continue;
            }
            const SkOpSpan& oSpan = thisOther->span(oEnd);
            int nIndex = nextSpan->fOtherIndex;
            for (int nStep = -1; nStep <= 1; nStep += 2) {
                int nEnd = nextOther->nextExactSpan(nIndex, nStep);
                if (nEnd < 0) {
                    continue;
                }
                const SkOpSpan& nSpan = nextOther->span(nEnd);
                if (oSpan.fPt != nSpan.fPt) {
                    continue;
                }
                double oMidT = (thisSpan->fOtherT + oSpan.fT) / 2;
                const SkPoint& oPt = thisOther->ptAtT(oMidT);
                double nMidT = (nextSpan->fOtherT + nSpan.fT) / 2;
                const SkPoint& nPt = nextOther->ptAtT(nMidT);
                if (!AlmostEqualUlps(oPt, nPt)) {
                    continue;
                }
#if DEBUG_CHECK_TINY
                SkDebugf("%s [%d] add coincidence [%d] [%d]\n", __FUNCTION__, fID,
                    thisOther->fID, nextOther->fID);
#endif
                // this segment is missing a entry that the other contains
                // remember so we can add the missing one and recompute the indices
                MissingSpan& missing = missingSpans.push_back();
                SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
                missing.fSegment = thisOther;
                missing.fT = thisSpan->fOtherT;
                SkASSERT(this != nextOther);
                missing.fOther = nextOther;
                missing.fOtherT = nextSpan->fOtherT;
                missing.fPt = thisSpan->fPt;
            }
        }
    }
    int missingCount = missingSpans.count();
    if (!missingCount) {
        return;
    }
    for (int index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        if (missing.fSegment != missing.fOther) {
            missing.fSegment->addTPair(missing.fT, missing.fOther, missing.fOtherT, false,
                    missing.fPt);
        }
    }
    // OPTIMIZE: consolidate to avoid multiple calls to fix index
    for (int index = 0; index < missingCount; ++index)  {
        MissingSpan& missing = missingSpans[index];
        missing.fSegment->fixOtherTIndex();
        missing.fOther->fixOtherTIndex();
    }
}

bool SkOpSegment::coincidentSmall(const SkPoint& pt, double t, const SkOpSegment* other) const {
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = this->span(index);
        if (span.fOther != other) {
            continue;
        }
        if (span.fPt == pt) {
            continue;
        }
        if (!AlmostEqualUlps(span.fPt, pt)) {
            continue;
        }
        if (fVerb != SkPath::kCubic_Verb) {
            return true;
        }
        double tInterval = t - span.fT;
        double tMid = t - tInterval / 2;
        SkDPoint midPt = dcubic_xy_at_t(fPts, tMid);
        return midPt.approximatelyEqual(xyAtT(t));
    }
    return false;
}

bool SkOpSegment::findCoincidentMatch(const SkOpSpan* span, const SkOpSegment* other, int oStart,
        int oEnd, int step, SkPoint* startPt, SkPoint* endPt, double* endT) const {
    SkASSERT(span->fT == 0 || span->fT == 1);
    SkASSERT(span->fOtherT == 0 || span->fOtherT == 1);
    const SkOpSpan* otherSpan = &other->span(oEnd);
    double refT = otherSpan->fT;
    const SkPoint& refPt = otherSpan->fPt;
    const SkOpSpan* lastSpan = &other->span(step > 0 ? other->count() - 1 : 0);
    do {
        const SkOpSegment* match = span->fOther;
        if (match == otherSpan->fOther) {
            // find start of respective spans and see if both have winding
            int startIndex, endIndex;
            if (span->fOtherT == 1) {
                endIndex = span->fOtherIndex;
                startIndex = match->nextExactSpan(endIndex, -1);
            } else {
                startIndex = span->fOtherIndex;
                endIndex = match->nextExactSpan(startIndex, 1);
            }
            const SkOpSpan& startSpan = match->span(startIndex);
            if (startSpan.fWindValue != 0) {
                // draw ray from endSpan.fPt perpendicular to end tangent and measure distance
                // to other segment.
                const SkOpSpan& endSpan = match->span(endIndex);
                SkDLine ray;
                SkVector dxdy;
                if (span->fOtherT == 1) {
                    ray.fPts[0].set(startSpan.fPt);
                    dxdy = match->dxdy(startIndex);
                } else {
                    ray.fPts[0].set(endSpan.fPt);
                    dxdy = match->dxdy(endIndex);
                }
                ray.fPts[1].fX = ray.fPts[0].fX + dxdy.fY;
                ray.fPts[1].fY = ray.fPts[0].fY - dxdy.fX;
                SkIntersections i;
                int roots = (i.*CurveRay[SkPathOpsVerbToPoints(other->verb())])(other->pts(), ray);
                for (int index = 0; index < roots; ++index) {
                    if (ray.fPts[0].approximatelyEqual(i.pt(index))) {
                        double matchMidT = (match->span(startIndex).fT
                                + match->span(endIndex).fT) / 2;
                        SkPoint matchMidPt = match->ptAtT(matchMidT);
                        double otherMidT = (i[0][index] + other->span(oStart).fT) / 2;
                        SkPoint otherMidPt = other->ptAtT(otherMidT);
                        if (SkDPoint::ApproximatelyEqual(matchMidPt, otherMidPt)) {
                            *startPt = startSpan.fPt;
                            *endPt = endSpan.fPt;
                            *endT = endSpan.fT;
                            return true;
                        }
                    }
                }
            }
            return false;
        }
        if (otherSpan == lastSpan) {
            break;
        }
        otherSpan += step;
    } while (otherSpan->fT == refT || otherSpan->fPt == refPt);
    return false;
}

int SkOpSegment::findEndSpan(int endIndex) const {
    const SkOpSpan* span = &fTs[--endIndex];
    const SkPoint& lastPt = span->fPt;
    double endT = span->fT;
    do {
        span = &fTs[--endIndex];
    } while (SkDPoint::ApproximatelyEqual(span->fPt, lastPt) && (span->fT == endT || span->fTiny));
    return endIndex + 1;
}

/*
 The M and S variable name parts stand for the operators.
   Mi stands for Minuend (see wiki subtraction, analogous to difference)
   Su stands for Subtrahend
 The Opp variable name part designates that the value is for the Opposite operator.
 Opposite values result from combining coincident spans.
 */
SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
                                     bool* unsortable, SkPathOp op, const int xorMiMask,
                                     const int xorSuMask) {
    const int startIndex = *nextStart;
    const int endIndex = *nextEnd;
    SkASSERT(startIndex != endIndex);
    SkDEBUGCODE(const int count = fTs.count());
    SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
    int step = SkSign32(endIndex - startIndex);
    *nextStart = startIndex;
    SkOpSegment* other = isSimple(nextStart, &step);
    if (other) 
    {
    // mark the smaller of startIndex, endIndex done, and all adjacent
    // spans with the same T value (but not 'other' spans)
#if DEBUG_WINDING
        SkDebugf("%s simple\n", __FUNCTION__);
#endif
        int min = SkMin32(startIndex, endIndex);
        if (fTs[min].fDone) {
            return NULL;
        }
        markDoneBinary(min);
        double startT = other->fTs[*nextStart].fT;
        *nextEnd = *nextStart;
        do {
            *nextEnd += step;
        } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
        SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
        if (other->isTiny(SkMin32(*nextStart, *nextEnd))) {
            *unsortable = true;
            return NULL;
        }
        return other;
    }
    const int end = nextExactSpan(startIndex, step);
    SkASSERT(end >= 0);
    SkASSERT(startIndex - endIndex != 0);
    SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
    // more than one viable candidate -- measure angles to find best

    int calcWinding = computeSum(startIndex, end, SkOpAngle::kBinaryOpp);
    bool sortable = calcWinding != SK_NaN32;
    if (!sortable) {
        *unsortable = true;
        markDoneBinary(SkMin32(startIndex, endIndex));
        return NULL;
    }
    SkOpAngle* angle = spanToAngle(end, startIndex);
    if (angle->unorderable()) {
        *unsortable = true;
        markDoneBinary(SkMin32(startIndex, endIndex));
        return NULL;
    }
#if DEBUG_SORT
    SkDebugf("%s\n", __FUNCTION__);
    angle->debugLoop();
#endif
    int sumMiWinding = updateWinding(endIndex, startIndex);
    if (sumMiWinding == SK_MinS32) {
        *unsortable = true;
        markDoneBinary(SkMin32(startIndex, endIndex));
        return NULL;
    }
    int sumSuWinding = updateOppWinding(endIndex, startIndex);
    if (operand()) {
        SkTSwap<int>(sumMiWinding, sumSuWinding);
    }
    SkOpAngle* nextAngle = angle->next();
    const SkOpAngle* foundAngle = NULL;
    bool foundDone = false;
    // iterate through the angle, and compute everyone's winding
    SkOpSegment* nextSegment;
    int activeCount = 0;
    do {
        nextSegment = nextAngle->segment();
        bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(),
                nextAngle->end(), op, &sumMiWinding, &sumSuWinding);
        if (activeAngle) {
            ++activeCount;
            if (!foundAngle || (foundDone && activeCount & 1)) {
                if (nextSegment->isTiny(nextAngle)) {
                    *unsortable = true;
                    markDoneBinary(SkMin32(startIndex, endIndex));
                    return NULL;
                }
                foundAngle = nextAngle;
                foundDone = nextSegment->done(nextAngle);
            }
        }
        if (nextSegment->done()) {
            continue;
        }
        if (nextSegment->isTiny(nextAngle)) {
            continue;
        }
        if (!activeAngle) {
            (void) nextSegment->markAndChaseDoneBinary(nextAngle->start(), nextAngle->end());
        }
        SkOpSpan* last = nextAngle->lastMarked();
        if (last) {
            SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last));
            *chase->append() = last;
#if DEBUG_WINDING
            SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__,
                    last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
                    last->fSmall);
#endif
        }
    } while ((nextAngle = nextAngle->next()) != angle);
#if DEBUG_ANGLE
    if (foundAngle) {
        foundAngle->debugSameAs(foundAngle);
    }
#endif

    markDoneBinary(SkMin32(startIndex, endIndex));
    if (!foundAngle) {
        return NULL;
    }
    *nextStart = foundAngle->start();
    *nextEnd = foundAngle->end();
    nextSegment = foundAngle->segment();
#if DEBUG_WINDING
    SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
            __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
 #endif
    return nextSegment;
}

SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart,
                                          int* nextEnd, bool* unsortable) {
    const int startIndex = *nextStart;
    const int endIndex = *nextEnd;
    SkASSERT(startIndex != endIndex);
    SkDEBUGCODE(const int count = fTs.count());
    SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
    int step = SkSign32(endIndex - startIndex);
    *nextStart = startIndex;
    SkOpSegment* other = isSimple(nextStart, &step);
    if (other) 
    {
    // mark the smaller of startIndex, endIndex done, and all adjacent
    // spans with the same T value (but not 'other' spans)
#if DEBUG_WINDING
        SkDebugf("%s simple\n", __FUNCTION__);
#endif
        int min = SkMin32(startIndex, endIndex);
        if (fTs[min].fDone) {
            return NULL;
        }
        markDoneUnary(min);
        double startT = other->fTs[*nextStart].fT;
        *nextEnd = *nextStart;
        do {
            *nextEnd += step;
        } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
        SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
        if (other->isTiny(SkMin32(*nextStart, *nextEnd))) {
            *unsortable = true;
            return NULL;
        }
        return other;
    }
    const int end = nextExactSpan(startIndex, step);
    SkASSERT(end >= 0);
    SkASSERT(startIndex - endIndex != 0);
    SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
    // more than one viable candidate -- measure angles to find best

    int calcWinding = computeSum(startIndex, end, SkOpAngle::kUnaryWinding);
    bool sortable = calcWinding != SK_NaN32;
    if (!sortable) {
        *unsortable = true;
        markDoneUnary(SkMin32(startIndex, endIndex));
        return NULL;
    }
    SkOpAngle* angle = spanToAngle(end, startIndex);
#if DEBUG_SORT
    SkDebugf("%s\n", __FUNCTION__);
    angle->debugLoop();
#endif
    int sumWinding = updateWinding(endIndex, startIndex);
    SkOpAngle* nextAngle = angle->next();
    const SkOpAngle* foundAngle = NULL;
    bool foundDone = false;
    SkOpSegment* nextSegment;
    int activeCount = 0;
    do {
        nextSegment = nextAngle->segment();
        bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(),
                &sumWinding);
        if (activeAngle) {
            ++activeCount;
            if (!foundAngle || (foundDone && activeCount & 1)) {
                if (nextSegment->isTiny(nextAngle)) {
                    *unsortable = true;
                    markDoneUnary(SkMin32(startIndex, endIndex));
                    return NULL;
                }
                foundAngle = nextAngle;
                foundDone = nextSegment->done(nextAngle);
            }
        }
        if (nextSegment->done()) {
            continue;
        }
        if (nextSegment->isTiny(nextAngle)) {
            continue;
        }
        if (!activeAngle) {
            nextSegment->markAndChaseDoneUnary(nextAngle->start(), nextAngle->end());
        }
        SkOpSpan* last = nextAngle->lastMarked();
        if (last) {
            SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last));
            *chase->append() = last;
#if DEBUG_WINDING
            SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__,
                    last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
                    last->fSmall);
#endif
        }
    } while ((nextAngle = nextAngle->next()) != angle);
    markDoneUnary(SkMin32(startIndex, endIndex));
    if (!foundAngle) {
        return NULL;
    }
    *nextStart = foundAngle->start();
    *nextEnd = foundAngle->end();
    nextSegment = foundAngle->segment();
#if DEBUG_WINDING
    SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
            __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
 #endif
    return nextSegment;
}

SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsortable) {
    const int startIndex = *nextStart;
    const int endIndex = *nextEnd;
    SkASSERT(startIndex != endIndex);
    SkDEBUGCODE(int count = fTs.count());
    SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
    int step = SkSign32(endIndex - startIndex);
// Detect cases where all the ends canceled out (e.g.,
// there is no angle) and therefore there's only one valid connection 
    *nextStart = startIndex;
    SkOpSegment* other = isSimple(nextStart, &step);
    if (other)
    {
#if DEBUG_WINDING
        SkDebugf("%s simple\n", __FUNCTION__);
#endif
        int min = SkMin32(startIndex, endIndex);
        if (fTs[min].fDone) {
            return NULL;
        }
        markDone(min, 1);
        double startT = other->fTs[*nextStart].fT;
        // FIXME: I don't know why the logic here is difference from the winding case
        SkDEBUGCODE(bool firstLoop = true;)
        if ((approximately_less_than_zero(startT) && step < 0)
                || (approximately_greater_than_one(startT) && step > 0)) {
            step = -step;
            SkDEBUGCODE(firstLoop = false;)
        }
        do {
            *nextEnd = *nextStart;
            do {
                *nextEnd += step;
            } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
            if (other->fTs[SkMin32(*nextStart, *nextEnd)].fWindValue) {
                break;
            }
            SkASSERT(firstLoop);
            SkDEBUGCODE(firstLoop = false;)
            step = -step;
        } while (true);
        SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
        return other;
    }
    SkASSERT(startIndex - endIndex != 0);
    SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
    // parallel block above with presorted version
    int end = nextExactSpan(startIndex, step);
    SkASSERT(end >= 0);
    SkOpAngle* angle = spanToAngle(end, startIndex);
    SkASSERT(angle);
#if DEBUG_SORT
    SkDebugf("%s\n", __FUNCTION__);
    angle->debugLoop();
#endif
    SkOpAngle* nextAngle = angle->next();
    const SkOpAngle* foundAngle = NULL;
    bool foundDone = false;
    SkOpSegment* nextSegment;
    int activeCount = 0;
    do {
        nextSegment = nextAngle->segment();
        ++activeCount;
        if (!foundAngle || (foundDone && activeCount & 1)) {
            if (nextSegment->isTiny(nextAngle)) {
                *unsortable = true;
                return NULL;
            }
            foundAngle = nextAngle;
            if (!(foundDone = nextSegment->done(nextAngle))) {
                break;
            }
        }
        nextAngle = nextAngle->next();
    } while (nextAngle != angle);
    markDone(SkMin32(startIndex, endIndex), 1);
    if (!foundAngle) {
        return NULL;
    }
    *nextStart = foundAngle->start();
    *nextEnd = foundAngle->end();
    nextSegment = foundAngle->segment();
#if DEBUG_WINDING
    SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
            __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
 #endif
    return nextSegment;
}

int SkOpSegment::findStartSpan(int startIndex) const {
    int index = startIndex;
    const SkOpSpan* span = &fTs[index];
    const SkPoint& firstPt = span->fPt;
    double firstT = span->fT;
    const SkOpSpan* prior;
    do {
        prior = span;
        span = &fTs[++index];
    } while (SkDPoint::ApproximatelyEqual(span->fPt, firstPt)
            && (span->fT == firstT || prior->fTiny));
    return index;
}

int SkOpSegment::findExactT(double t, const SkOpSegment* match) const {
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fT == t && span.fOther == match) {
            return index;
        }
    }
    SkASSERT(0);
    return -1;
}

int SkOpSegment::findOtherT(double t, const SkOpSegment* match) const {
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fOtherT == t && span.fOther == match) {
            return index;
        }
    }
    return -1;
}

int SkOpSegment::findT(double t, const SkPoint& pt, const SkOpSegment* match) const {
    int count = this->count();
    // prefer exact matches over approximate matches
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fT == t && span.fOther == match) {
            return index;
        }
    }
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (approximately_equal_orderable(span.fT, t) && span.fOther == match) {
            return index;
        }
    }
    // Usually, the pair of ts are an exact match. It's possible that the t values have
    // been adjusted to make multiple intersections align. In this rare case, look for a
    // matching point / match pair instead.
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fPt == pt && span.fOther == match) {
            return index;
        }
    }
    SkASSERT(0);
    return -1;
}

SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsortable,
        bool firstPass) {
    // iterate through T intersections and return topmost
    // topmost tangent from y-min to first pt is closer to horizontal
    SkASSERT(!done());
    int firstT = -1;
    /* SkPoint topPt = */ activeLeftTop(&firstT);
    if (firstT < 0) {
        *unsortable = !firstPass;
        firstT = 0;
        while (fTs[firstT].fDone) {
            SkASSERT(firstT < fTs.count());
            ++firstT;
        }
        *tIndexPtr = firstT;
        *endIndexPtr = nextExactSpan(firstT, 1);
        return this;
    }
    // sort the edges to find the leftmost
    int step = 1;
    int end;
    if (span(firstT).fDone || (end = nextSpan(firstT, step)) == -1) {
        step = -1;
        end = nextSpan(firstT, step);
        SkASSERT(end != -1);
    }
    // if the topmost T is not on end, or is three-way or more, find left
    // look for left-ness from tLeft to firstT (matching y of other)
    SkASSERT(firstT - end != 0);
    SkOpAngle* markAngle = spanToAngle(firstT, end);
    if (!markAngle) {
        markAngle = addSingletonAngles(step);
    }
    markAngle->markStops();
    const SkOpAngle* baseAngle = markAngle->next() == markAngle && !isVertical() ? markAngle
            : markAngle->findFirst();
    if (!baseAngle) {
        return NULL;  // nothing to do
    }
    SkScalar top = SK_ScalarMax;
    const SkOpAngle* firstAngle = NULL;
    const SkOpAngle* angle = baseAngle;
    do {
        if (!angle->unorderable()) {
            SkOpSegment* next = angle->segment();
            SkPathOpsBounds bounds;
            next->subDivideBounds(angle->end(), angle->start(), &bounds);
            if (approximately_greater(top, bounds.fTop)) {
                top = bounds.fTop;
                firstAngle = angle;
            }
        }
        angle = angle->next();
    } while (angle != baseAngle);
    SkASSERT(firstAngle);
#if DEBUG_SORT
    SkDebugf("%s\n", __FUNCTION__);
    firstAngle->debugLoop();
#endif
    // skip edges that have already been processed
    angle = firstAngle;
    SkOpSegment* leftSegment = NULL;
    bool looped = false;
    do {
        *unsortable = angle->unorderable();
        if (firstPass || !*unsortable) {
            leftSegment = angle->segment();
            *tIndexPtr = angle->end();
            *endIndexPtr = angle->start();
            if (!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fDone) {
                break;
            }
        }
        angle = angle->next();
        looped = true;
    } while (angle != firstAngle);
    if (angle == firstAngle && looped) {
        return NULL;
    }
    if (leftSegment->verb() >= SkPath::kQuad_Verb) {
        const int tIndex = *tIndexPtr;
        const int endIndex = *endIndexPtr;
        bool swap;
        if (!leftSegment->clockwise(tIndex, endIndex, &swap)) {
    #if DEBUG_SWAP_TOP
            SkDebugf("%s swap=%d inflections=%d serpentine=%d controlledbyends=%d monotonic=%d\n",
                    __FUNCTION__,
                    swap, leftSegment->debugInflections(tIndex, endIndex),
                    leftSegment->serpentine(tIndex, endIndex),
                    leftSegment->controlsContainedByEnds(tIndex, endIndex),
                    leftSegment->monotonicInY(tIndex, endIndex));
    #endif
            if (swap) {
    // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first
    // sorted but merely the first not already processed (i.e., not done)
                SkTSwap(*tIndexPtr, *endIndexPtr);
            }
        }
    }
    SkASSERT(!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fTiny);
    return leftSegment;
}

int SkOpSegment::firstActive(int tIndex) const {
    while (fTs[tIndex].fTiny) {
        SkASSERT(!isCanceled(tIndex));
        ++tIndex;
    }
    return tIndex;
}

// FIXME: not crazy about this
// when the intersections are performed, the other index is into an
// incomplete array. As the array grows, the indices become incorrect
// while the following fixes the indices up again, it isn't smart about
// skipping segments whose indices are already correct
// assuming we leave the code that wrote the index in the first place
// FIXME: if called after remove, this needs to correct tiny
void SkOpSegment::fixOtherTIndex() {
    int iCount = fTs.count();
    for (int i = 0; i < iCount; ++i) {
        SkOpSpan& iSpan = fTs[i];
        double oT = iSpan.fOtherT;
        SkOpSegment* other = iSpan.fOther;
        int oCount = other->fTs.count();
        SkDEBUGCODE(iSpan.fOtherIndex = -1);
        for (int o = 0; o < oCount; ++o) {
            SkOpSpan& oSpan = other->fTs[o];
            if (oT == oSpan.fT && this == oSpan.fOther && oSpan.fOtherT == iSpan.fT) {
                iSpan.fOtherIndex = o;
                oSpan.fOtherIndex = i;
                break;
            }
        }
        SkASSERT(iSpan.fOtherIndex >= 0);
    }
}

bool SkOpSegment::inCoincidentSpan(double t, const SkOpSegment* other) const {
    int foundEnds = 0;
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        const SkOpSpan& span = this->span(index);
        if (span.fCoincident) {
            foundEnds |= (span.fOther == other) << ((t > span.fT) + (t >= span.fT));
        }
    }
    SkASSERT(foundEnds != 7);
    return foundEnds == 0x3 || foundEnds == 0x5 || foundEnds == 0x6;  // two bits set
}

void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) {
    fDoneSpans = 0;
    fOperand = operand;
    fXor = evenOdd;
    fPts = pts;
    fVerb = verb;
    fLoop = fMultiples = fSmall = fTiny = false;
}

void SkOpSegment::initWinding(int start, int end, SkOpAngle::IncludeType angleIncludeType) {
    int local = spanSign(start, end);
    if (angleIncludeType == SkOpAngle::kBinarySingle) {
        int oppLocal = oppSign(start, end);
        (void) markAndChaseWinding(start, end, local, oppLocal);
    // OPTIMIZATION: the reverse mark and chase could skip the first marking
        (void) markAndChaseWinding(end, start, local, oppLocal);
    } else {
        (void) markAndChaseWinding(start, end, local);
    // OPTIMIZATION: the reverse mark and chase could skip the first marking
        (void) markAndChaseWinding(end, start, local);
    }
}

/*
when we start with a vertical intersect, we try to use the dx to determine if the edge is to
the left or the right of vertical. This determines if we need to add the span's
sign or not. However, this isn't enough.
If the supplied sign (winding) is zero, then we didn't hit another vertical span, so dx is needed.
If there was a winding, then it may or may not need adjusting. If the span the winding was borrowed
from has the same x direction as this span, the winding should change. If the dx is opposite, then
the same winding is shared by both.
*/
void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkScalar hitDx,
                              int oppWind, SkScalar hitOppDx) {
    SkASSERT(hitDx || !winding);
    SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX;
    SkASSERT(dx);
    int windVal = windValue(SkMin32(start, end));
#if DEBUG_WINDING_AT_T
    SkDebugf("%s id=%d oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, debugID(), winding,
            hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal);
#endif
    int sideWind = winding + (dx < 0 ? windVal : -windVal);
    if (abs(winding) < abs(sideWind)) {
        winding = sideWind;
    }
    SkDEBUGCODE(int oppLocal = oppSign(start, end));
    SkASSERT(hitOppDx || !oppWind || !oppLocal);
    int oppWindVal = oppValue(SkMin32(start, end));
    if (!oppWind) {
        oppWind = dx < 0 ? oppWindVal : -oppWindVal;
    } else if (hitOppDx * dx >= 0) {
        int oppSideWind = oppWind + (dx < 0 ? oppWindVal : -oppWindVal);
        if (abs(oppWind) < abs(oppSideWind)) {
            oppWind = oppSideWind;
        }
    }
#if DEBUG_WINDING_AT_T
    SkDebugf(" winding=%d oppWind=%d\n", winding, oppWind);
#endif
    (void) markAndChaseWinding(start, end, winding, oppWind);
    // OPTIMIZATION: the reverse mark and chase could skip the first marking
    (void) markAndChaseWinding(end, start, winding, oppWind);
}

bool SkOpSegment::inLoop(const SkOpAngle* baseAngle, int spanCount, int* indexPtr) const {
    if (!baseAngle->inLoop()) {
        return false;
    }
    int index = *indexPtr;
    SkOpAngle* from = fTs[index].fFromAngle;
    SkOpAngle* to = fTs[index].fToAngle;
    while (++index < spanCount) {
        SkOpAngle* nextFrom = fTs[index].fFromAngle;
        SkOpAngle* nextTo = fTs[index].fToAngle;
        if (from != nextFrom || to != nextTo) {
            break;
        }
    }
    *indexPtr = index;
    return true;
}

// OPTIMIZE: successive calls could start were the last leaves off
// or calls could specialize to walk forwards or backwards
bool SkOpSegment::isMissing(double startT, const SkPoint& pt) const {
    int tCount = fTs.count();
    for (int index = 0; index < tCount; ++index) {
        const SkOpSpan& span = fTs[index];
        if (approximately_zero(startT - span.fT) && pt == span.fPt) {
            return false;
        }
    }
    return true;
}


SkOpSegment* SkOpSegment::isSimple(int* end, int* step) {
    return nextChase(end, step, NULL, NULL);
}

bool SkOpSegment::isTiny(const SkOpAngle* angle) const {
    int start = angle->start();
    int end = angle->end();
    const SkOpSpan& mSpan = fTs[SkMin32(start, end)];
    return mSpan.fTiny;
}

bool SkOpSegment::isTiny(int index) const {
    return fTs[index].fTiny;
}

// look pair of active edges going away from coincident edge
// one of them should be the continuation of other
// if both are active, look to see if they both the connect to another coincident pair
// if at least one is a line, then make the pair coincident
// if neither is a line, test for coincidence
bool SkOpSegment::joinCoincidence(SkOpSegment* other, double otherT, const SkPoint& otherPt,
        int step, bool cancel) {
    int otherTIndex = other->findT(otherT, otherPt, this);
    int next = other->nextExactSpan(otherTIndex, step);
    int otherMin = SkMin32(otherTIndex, next);
    int otherWind = other->span(otherMin).fWindValue;
    if (otherWind == 0) {
        return false;
    }
    SkASSERT(next >= 0);
    int tIndex = 0;
    do {
        SkOpSpan* test = &fTs[tIndex];
        SkASSERT(test->fT == 0);
        if (test->fOther == other || test->fOtherT != 1) {
            continue;
        }
        SkPoint startPt, endPt;
        double endT;
        if (findCoincidentMatch(test, other, otherTIndex, next, step, &startPt, &endPt, &endT)) {
            SkOpSegment* match = test->fOther;
            if (cancel) {
                match->addTCancel(startPt, endPt, other);
            } else {
                SkAssertResult(match->addTCoincident(startPt, endPt, endT, other));
            }
            return true;
        }
    } while (fTs[++tIndex].fT == 0);
    return false;
}

// this span is excluded by the winding rule -- chase the ends
// as long as they are unambiguous to mark connections as done
// and give them the same winding value

SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) {
    int step = SkSign32(endIndex - index);
    int min = SkMin32(index, endIndex);
    markDoneBinary(min);
    SkOpSpan* last = NULL;
    SkOpSegment* other = this;
    while ((other = other->nextChase(&index, &step, &min, &last))) {
        if (other->done()) {
            SkASSERT(!last);
            break;
        }
        other->markDoneBinary(min);
    }
    return last;
}

SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) {
    int step = SkSign32(endIndex - index);
    int min = SkMin32(index, endIndex);
    markDoneUnary(min);
    SkOpSpan* last = NULL;
    SkOpSegment* other = this;
    while ((other = other->nextChase(&index, &step, &min, &last))) {
        if (other->done()) {
            SkASSERT(!last);
            break;
        }
        other->markDoneUnary(min);
    }
    return last;
}

SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding) {
    int index = angle->start();
    int endIndex = angle->end();
    int step = SkSign32(endIndex - index);
    int min = SkMin32(index, endIndex);
    markWinding(min, winding);
    SkOpSpan* last = NULL;
    SkOpSegment* other = this;
    while ((other = other->nextChase(&index, &step, &min, &last))) {
        if (other->fTs[min].fWindSum != SK_MinS32) {
//            SkASSERT(other->fTs[min].fWindSum == winding);
            SkASSERT(!last);
            break;
        }
        other->markWinding(min, winding);
    }
    return last;
}

SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding) {
    int min = SkMin32(index, endIndex);
    int step = SkSign32(endIndex - index);
    markWinding(min, winding);
    SkOpSpan* last = NULL;
    SkOpSegment* other = this;
    while ((other = other->nextChase(&index, &step, &min, &last))) {
        if (other->fTs[min].fWindSum != SK_MinS32) {
            SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop);
            SkASSERT(!last);
            break;
        }
        other->markWinding(min, winding);
    }
    return last;
}

SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int oppWinding) {
    int min = SkMin32(index, endIndex);
    int step = SkSign32(endIndex - index);
    markWinding(min, winding, oppWinding);
    SkOpSpan* last = NULL;
    SkOpSegment* other = this;
    while ((other = other->nextChase(&index, &step, &min, &last))) {
        if (other->fTs[min].fWindSum != SK_MinS32) {
#ifdef SK_DEBUG
            if (!other->fTs[min].fLoop) {
                if (fOperand == other->fOperand) {
// FIXME: this is probably a bug -- rects4 asserts here
//                    SkASSERT(other->fTs[min].fWindSum == winding);
// FIXME: this is probably a bug -- rects3 asserts here
//                    SkASSERT(other->fTs[min].fOppSum == oppWinding);
                } else {
// FIXME: this is probably a bug -- issue414409b asserts here
//                    SkASSERT(other->fTs[min].fWindSum == oppWinding);
// FIXME: this is probably a bug -- skpwww_joomla_org_23 asserts here
//                    SkASSERT(other->fTs[min].fOppSum == winding);
                }
            }
            SkASSERT(!last);
#endif
            break;
        }
        if (fOperand == other->fOperand) {
            other->markWinding(min, winding, oppWinding);
        } else {
            other->markWinding(min, oppWinding, winding);
        }
    }
    return last;
}

SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding) {
    int start = angle->start();
    int end = angle->end();
    return markAndChaseWinding(start, end, winding, oppWinding);
}

SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle) {
    SkASSERT(angle->segment() == this);
    if (UseInnerWinding(maxWinding, sumWinding)) {
        maxWinding = sumWinding;
    }
    SkOpSpan* last = markAndChaseWinding(angle, maxWinding);
#if DEBUG_WINDING
    if (last) {
        SkDebugf("%s last id=%d windSum=", __FUNCTION__,
                last->fOther->fTs[last->fOtherIndex].fOther->debugID());
        SkPathOpsDebug::WindingPrintf(last->fWindSum);
        SkDebugf(" small=%d\n", last->fSmall);
    }
#endif
    return last;
}

SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding,
                                 int oppSumWinding, const SkOpAngle* angle) {
    SkASSERT(angle->segment() == this);
    if (UseInnerWinding(maxWinding, sumWinding)) {
        maxWinding = sumWinding;
    }
    if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) {
        oppMaxWinding = oppSumWinding;
    }
    SkOpSpan* last = markAndChaseWinding(angle, maxWinding, oppMaxWinding);
#if DEBUG_WINDING
    if (last) {
        SkDebugf("%s last id=%d windSum=", __FUNCTION__,
                last->fOther->fTs[last->fOtherIndex].fOther->debugID());
        SkPathOpsDebug::WindingPrintf(last->fWindSum);
        SkDebugf(" small=%d\n", last->fSmall);
    }
#endif
    return last;
}

// FIXME: this should also mark spans with equal (x,y)
// This may be called when the segment is already marked done. While this
// wastes time, it shouldn't do any more than spin through the T spans.
// OPTIMIZATION: abort on first done found (assuming that this code is
// always called to mark segments done).
void SkOpSegment::markDone(int index, int winding) {
  //  SkASSERT(!done());
    SkASSERT(winding);
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
        markOneDone(__FUNCTION__, lesser, winding);
    }
    do {
        markOneDone(__FUNCTION__, index, winding);
    } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
    debugValidate();
}

void SkOpSegment::markDoneBinary(int index) {
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
        markOneDoneBinary(__FUNCTION__, lesser);
    }
    do {
        markOneDoneBinary(__FUNCTION__, index);
    } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
    debugValidate();
}

void SkOpSegment::markDoneUnary(int index) {
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
        markOneDoneUnary(__FUNCTION__, lesser);
    }
    do {
        markOneDoneUnary(__FUNCTION__, index);
    } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
    debugValidate();
}

void SkOpSegment::markOneDone(const char* funName, int tIndex, int winding) {
    SkOpSpan* span = markOneWinding(funName, tIndex, winding);
    if (!span || span->fDone) {
        return;
    }
    span->fDone = true;
    fDoneSpans++;
}

void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex) {
    SkOpSpan* span = verifyOneWinding(funName, tIndex);
    if (!span) {
        return;
    }
    SkASSERT(!span->fDone);
    span->fDone = true;
    fDoneSpans++;
}

void SkOpSegment::markOneDoneUnary(const char* funName, int tIndex) {
    SkOpSpan* span = verifyOneWindingU(funName, tIndex);
    if (!span) {
        return;
    }
    if (span->fWindSum == SK_MinS32) {
        SkDebugf("%s uncomputed\n", __FUNCTION__);
    }
    SkASSERT(!span->fDone);
    span->fDone = true;
    fDoneSpans++;
}

SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding) {
    SkOpSpan& span = fTs[tIndex];
    if (span.fDone && !span.fSmall) {
        return NULL;
    }
#if DEBUG_MARK_DONE
    debugShowNewWinding(funName, span, winding);
#endif
    SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
#if DEBUG_LIMIT_WIND_SUM
    SkASSERT(abs(winding) <= DEBUG_LIMIT_WIND_SUM);
#endif
    span.fWindSum = winding;
    return &span;
}

SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding,
                                      int oppWinding) {
    SkOpSpan& span = fTs[tIndex];
    if (span.fDone && !span.fSmall) {
        return NULL;
    }
#if DEBUG_MARK_DONE
    debugShowNewWinding(funName, span, winding, oppWinding);
#endif
    SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
#if DEBUG_LIMIT_WIND_SUM
    SkASSERT(abs(winding) <= DEBUG_LIMIT_WIND_SUM);
#endif
    span.fWindSum = winding;
    SkASSERT(span.fOppSum == SK_MinS32 || span.fOppSum == oppWinding);
#if DEBUG_LIMIT_WIND_SUM
    SkASSERT(abs(oppWinding) <= DEBUG_LIMIT_WIND_SUM);
#endif
    span.fOppSum = oppWinding;
    debugValidate();
    return &span;
}

// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
bool SkOpSegment::clockwise(int tStart, int tEnd, bool* swap) const {
    SkASSERT(fVerb != SkPath::kLine_Verb);
    SkPoint edge[4];
    subDivide(tStart, tEnd, edge);
    int points = SkPathOpsVerbToPoints(fVerb);
    double sum = (edge[0].fX - edge[points].fX) * (edge[0].fY + edge[points].fY);
    bool sumSet = false;
    if (fVerb == SkPath::kCubic_Verb) {
        SkDCubic cubic;
        cubic.set(edge);
        double inflectionTs[2];
        int inflections = cubic.findInflections(inflectionTs);
        // FIXME: this fixes cubicOp114 and breaks cubicOp58d
        // the trouble is that cubics with inflections confuse whether the curve breaks towards
        // or away, which in turn is used to determine if it is on the far right or left.
        // Probably a totally different approach is in order. At one time I tried to project a
        // horizontal ray to determine winding, but was confused by how to map the vertically
        // oriented winding computation over. 
        if (0 && inflections) {
            double tLo = this->span(tStart).fT;
            double tHi = this->span(tEnd).fT;
            double tLoStart = tLo;
            for (int index = 0; index < inflections; ++index) {
                if (between(tLo, inflectionTs[index], tHi)) {
                    tLo = inflectionTs[index];
                }
            }
            if (tLo != tLoStart && tLo != tHi) {
                SkDPoint sub[2];
                sub[0] = cubic.ptAtT(tLo);
                sub[1].set(edge[3]);
                SkDPoint ctrl[2];
                SkDCubic::SubDivide(fPts, sub[0], sub[1], tLo, tHi, ctrl);
                edge[0] = sub[0].asSkPoint();
                edge[1] = ctrl[0].asSkPoint();
                edge[2] = ctrl[1].asSkPoint();
                sum = (edge[0].fX - edge[3].fX) * (edge[0].fY + edge[3].fY);
            }
        }
        SkScalar lesser = SkTMin<SkScalar>(edge[0].fY, edge[3].fY);
        if (edge[1].fY < lesser && edge[2].fY < lesser) {
            SkDLine tangent1 = {{ {edge[0].fX, edge[0].fY}, {edge[1].fX, edge[1].fY} }};
            SkDLine tangent2 = {{ {edge[2].fX, edge[2].fY}, {edge[3].fX, edge[3].fY} }};
            if (SkIntersections::Test(tangent1, tangent2)) {
                SkPoint topPt = cubic_top(fPts, fTs[tStart].fT, fTs[tEnd].fT);
                sum += (topPt.fX - edge[0].fX) * (topPt.fY + edge[0].fY);
                sum += (edge[3].fX - topPt.fX) * (edge[3].fY + topPt.fY);
                sumSet = true;
            }
        }
    }
    if (!sumSet) {
        for (int idx = 0; idx < points; ++idx){
            sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY);
        }
    }
    if (fVerb == SkPath::kCubic_Verb) {
        SkDCubic cubic;
        cubic.set(edge);
         *swap = sum > 0 && !cubic.monotonicInY() && !cubic.serpentine();
    } else {
        SkDQuad quad;
        quad.set(edge);
        *swap = sum > 0 && !quad.monotonicInY();
    }
    return sum <= 0;
}

bool SkOpSegment::monotonicInY(int tStart, int tEnd) const {
    SkASSERT(fVerb != SkPath::kLine_Verb);
    if (fVerb == SkPath::kQuad_Verb) {
        SkDQuad dst = SkDQuad::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
        return dst.monotonicInY();
    }
    SkASSERT(fVerb == SkPath::kCubic_Verb);
    SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
    return dst.monotonicInY();
}

bool SkOpSegment::serpentine(int tStart, int tEnd) const {
    if (fVerb != SkPath::kCubic_Verb) {
        return false;
    }
    SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
    return dst.serpentine();
}

SkOpSpan* SkOpSegment::verifyOneWinding(const char* funName, int tIndex) {
    SkOpSpan& span = fTs[tIndex];
    if (span.fDone) {
        return NULL;
    }
#if DEBUG_MARK_DONE
    debugShowNewWinding(funName, span, span.fWindSum, span.fOppSum);
#endif
// If the prior angle in the sort is unorderable, the winding sum may not be computable.
// To enable the assert, the 'prior is unorderable' state could be
// piped down to this test, but not sure it's worth it.
// (Once the sort order is stored in the span, this test may be feasible.)
//    SkASSERT(span.fWindSum != SK_MinS32);
//    SkASSERT(span.fOppSum != SK_MinS32);
    return &span;
}

SkOpSpan* SkOpSegment::verifyOneWindingU(const char* funName, int tIndex) {
    SkOpSpan& span = fTs[tIndex];
    if (span.fDone) {
        return NULL;
    }
#if DEBUG_MARK_DONE
    debugShowNewWinding(funName, span, span.fWindSum);
#endif
// If the prior angle in the sort is unorderable, the winding sum may not be computable.
// To enable the assert, the 'prior is unorderable' state could be
// piped down to this test, but not sure it's worth it.
// (Once the sort order is stored in the span, this test may be feasible.)
//    SkASSERT(span.fWindSum != SK_MinS32);
    return &span;
}

void SkOpSegment::markWinding(int index, int winding) {
//    SkASSERT(!done());
    SkASSERT(winding);
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
        markOneWinding(__FUNCTION__, lesser, winding);
    }
    do {
        markOneWinding(__FUNCTION__, index, winding);
   } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
    debugValidate();
}

void SkOpSegment::markWinding(int index, int winding, int oppWinding) {
//    SkASSERT(!done());
    SkASSERT(winding || oppWinding);
    double referenceT = fTs[index].fT;
    int lesser = index;
    while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
        markOneWinding(__FUNCTION__, lesser, winding, oppWinding);
    }
    do {
        markOneWinding(__FUNCTION__, index, winding, oppWinding);
   } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
    debugValidate();
}

void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) {
    int nextDoorWind = SK_MaxS32;
    int nextOppWind = SK_MaxS32;
    // prefer exact matches
    if (tIndex > 0) {
        const SkOpSpan& below = fTs[tIndex - 1];
        if (below.fT == t) {
            nextDoorWind = below.fWindValue;
            nextOppWind = below.fOppValue;
        }
    }
    if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) {
        const SkOpSpan& above = fTs[tIndex + 1];
        if (above.fT == t) {
            nextDoorWind = above.fWindValue;
            nextOppWind = above.fOppValue;
        }
    }
    if (nextDoorWind == SK_MaxS32 && tIndex > 0) {
        const SkOpSpan& below = fTs[tIndex - 1];
        if (approximately_negative(t - below.fT)) {
            nextDoorWind = below.fWindValue;
            nextOppWind = below.fOppValue;
        }
    }
    if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) {
        const SkOpSpan& above = fTs[tIndex + 1];
        if (approximately_negative(above.fT - t)) {
            nextDoorWind = above.fWindValue;
            nextOppWind = above.fOppValue;
        }
    }
    if (nextDoorWind == SK_MaxS32 && borrowWind && tIndex > 0 && t < 1) {
        const SkOpSpan& below = fTs[tIndex - 1];
        nextDoorWind = below.fWindValue;
        nextOppWind = below.fOppValue;
    }
    if (nextDoorWind != SK_MaxS32) {
        SkOpSpan& newSpan = fTs[tIndex];
        newSpan.fWindValue = nextDoorWind;
        newSpan.fOppValue = nextOppWind;
        if (!nextDoorWind && !nextOppWind && !newSpan.fDone) {
            newSpan.fDone = true;
            ++fDoneSpans;
        }
    }
}

bool SkOpSegment::nextCandidate(int* start, int* end) const {
    while (fTs[*end].fDone) {
        if (fTs[*end].fT == 1) {
            return false;
        }
        ++(*end);
    }
    *start = *end;
    *end = nextExactSpan(*start, 1);
    return true;
}

static SkOpSegment* set_last(SkOpSpan** last, SkOpSpan* endSpan) {
    if (last && !endSpan->fSmall) {
        *last = endSpan;
    }
    return NULL;
}

SkOpSegment* SkOpSegment::nextChase(int* indexPtr, int* stepPtr, int* minPtr, SkOpSpan** last) {
    int origIndex = *indexPtr;
    int step = *stepPtr;
    int end = nextExactSpan(origIndex, step);
    SkASSERT(end >= 0);
    SkOpSpan& endSpan = fTs[end];
    SkOpAngle* angle = step > 0 ? endSpan.fFromAngle : endSpan.fToAngle;
    int foundIndex;
    int otherEnd;
    SkOpSegment* other;
    if (angle == NULL) {
        if (endSpan.fT != 0 && endSpan.fT != 1) {
            return NULL;
        }
        other = endSpan.fOther;
        foundIndex = endSpan.fOtherIndex;
        otherEnd = other->nextExactSpan(foundIndex, step);
    } else {
        int loopCount = angle->loopCount();
        if (loopCount > 2) {
            return set_last(last, &endSpan);
        }
        const SkOpAngle* next = angle->next();
        if (NULL == next) {
            return NULL;
        }
        if (angle->sign() != next->sign()) {
#if DEBUG_WINDING
            SkDebugf("%s mismatched signs\n", __FUNCTION__);
#endif
        //    return set_last(last, &endSpan);
        }
        other = next->segment();
        foundIndex = end = next->start();
        otherEnd = next->end();
    }
    int foundStep = foundIndex < otherEnd ? 1 : -1;
    if (*stepPtr != foundStep) {
        return set_last(last, &endSpan);
    }
    SkASSERT(*indexPtr >= 0);
    if (otherEnd < 0) {
        return NULL;
    }
//    SkASSERT(otherEnd >= 0);
#if 1
    int origMin = origIndex + (step < 0 ? step : 0);
    const SkOpSpan& orig = this->span(origMin);
#endif
    int foundMin = SkMin32(foundIndex, otherEnd);
#if 1
    const SkOpSpan& found = other->span(foundMin);
    if (found.fWindValue != orig.fWindValue || found.fOppValue != orig.fOppValue) {
          return set_last(last, &endSpan);
    }
#endif
    *indexPtr = foundIndex;
    *stepPtr = foundStep;
    if (minPtr) {
        *minPtr = foundMin;
    }
    return other;
}

// This has callers for two different situations: one establishes the end
// of the current span, and one establishes the beginning of the next span
// (thus the name). When this is looking for the end of the current span,
// coincidence is found when the beginning Ts contain -step and the end
// contains step. When it is looking for the beginning of the next, the
// first Ts found can be ignored and the last Ts should contain -step.
// OPTIMIZATION: probably should split into two functions
int SkOpSegment::nextSpan(int from, int step) const {
    const SkOpSpan& fromSpan = fTs[from];
    int count = fTs.count();
    int to = from;
    while (step > 0 ? ++to < count : --to >= 0) {
        const SkOpSpan& span = fTs[to];
        if (approximately_zero(span.fT - fromSpan.fT)) {
            continue;
        }
        return to;
    }
    return -1;
}

// FIXME
// this returns at any difference in T, vs. a preset minimum. It may be
// that all callers to nextSpan should use this instead.
int SkOpSegment::nextExactSpan(int from, int step) const {
    int to = from;
    if (step < 0) {
        const SkOpSpan& fromSpan = fTs[from];
        while (--to >= 0) {
            const SkOpSpan& span = fTs[to];
            if (precisely_negative(fromSpan.fT - span.fT) || span.fTiny) {
                continue;
            }
            return to;
        }
    } else {
        while (fTs[from].fTiny) {
            from++;
        }
        const SkOpSpan& fromSpan = fTs[from];
        int count = fTs.count();
        while (++to < count) {
            const SkOpSpan& span = fTs[to];
            if (precisely_negative(span.fT - fromSpan.fT)) {
                continue;
            }
            return to;
        }
    }
    return -1;
}

void SkOpSegment::pinT(const SkPoint& pt, double* t) {
    if (pt == fPts[0]) {
        *t = 0;
    }
    int count = SkPathOpsVerbToPoints(fVerb);
    if (pt == fPts[count]) {
        *t = 1;
    }
}

bool SkOpSegment::reversePoints(const SkPoint& p1, const SkPoint& p2) const {
    SkASSERT(p1 != p2);
    int spanCount = count();
    int p1IndexMin = -1;
    int p2IndexMax = spanCount;
    for (int index = 0; index < spanCount; ++index) {
        const SkOpSpan& span = fTs[index];
        if (span.fPt == p1) {
            if (p1IndexMin < 0) {
                p1IndexMin = index;
            }
        } else if (span.fPt == p2) {
            p2IndexMax = index;
        }
    }
    return p1IndexMin > p2IndexMax;
}

void SkOpSegment::setCoincidentRange(const SkPoint& startPt, const SkPoint& endPt, 
        SkOpSegment* other) {
    int count = this->count();
    for (int index = 0; index < count; ++index) {
        SkOpSpan &span = fTs[index];
        if ((startPt == span.fPt || endPt == span.fPt) && other == span.fOther) {
            span.fCoincident = true;
        }
    }
}

void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding,
        int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) {
    int deltaSum = spanSign(index, endIndex);
    int oppDeltaSum = oppSign(index, endIndex);
    if (operand()) {
        *maxWinding = *sumSuWinding;
        *sumWinding = *sumSuWinding -= deltaSum;
        *oppMaxWinding = *sumMiWinding;
        *oppSumWinding = *sumMiWinding -= oppDeltaSum;
    } else {
        *maxWinding = *sumMiWinding;
        *sumWinding = *sumMiWinding -= deltaSum;
        *oppMaxWinding = *sumSuWinding;
        *oppSumWinding = *sumSuWinding -= oppDeltaSum;
    }
#if DEBUG_LIMIT_WIND_SUM
    SkASSERT(abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM);
    SkASSERT(abs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM);
#endif
}

void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding,
        int* maxWinding, int* sumWinding) {
    int deltaSum = spanSign(index, endIndex);
    *maxWinding = *sumMiWinding;
    *sumWinding = *sumMiWinding -= deltaSum;
#if DEBUG_LIMIT_WIND_SUM
    SkASSERT(abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM);
#endif
}

void SkOpSegment::sortAngles() {
    int spanCount = fTs.count();
    if (spanCount <= 2) {
        return;
    }
    int index = 0;
    do {
        SkOpAngle* fromAngle = fTs[index].fFromAngle;
        SkOpAngle* toAngle = fTs[index].fToAngle;
        if (!fromAngle && !toAngle) {
            index += 1;
            continue;
        }
        SkOpAngle* baseAngle = NULL;
        if (fromAngle) {
            baseAngle = fromAngle;
            if (inLoop(baseAngle, spanCount, &index)) {
                continue;
            }
        }
#if DEBUG_ANGLE
        bool wroteAfterHeader = false;
#endif
        if (toAngle) {
            if (!baseAngle) {
                baseAngle = toAngle;
                if (inLoop(baseAngle, spanCount, &index)) {
                    continue;
                }
            } else {
                SkDEBUGCODE(int newIndex = index);
                SkASSERT(!inLoop(baseAngle, spanCount, &newIndex) && newIndex == index);
#if DEBUG_ANGLE
                SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT,
                        index);
                wroteAfterHeader = true;
#endif
                baseAngle->insert(toAngle);
            }
        }
        SkOpAngle* nextFrom, * nextTo;
        int firstIndex = index;
        do {
            SkOpSpan& span = fTs[index];
            SkOpSegment* other = span.fOther;
            SkOpSpan& oSpan = other->fTs[span.fOtherIndex];
            SkOpAngle* oAngle = oSpan.fFromAngle;
            if (oAngle) {
#if DEBUG_ANGLE
                if (!wroteAfterHeader) {
                    SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT,
                            index);
                    wroteAfterHeader = true;
                }
#endif
                if (!oAngle->loopContains(*baseAngle)) {
                    baseAngle->insert(oAngle);
                }
            }
            oAngle = oSpan.fToAngle;
            if (oAngle) {
#if DEBUG_ANGLE
                if (!wroteAfterHeader) {
                    SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), fTs[index].fT,
                            index);
                    wroteAfterHeader = true;
                }
#endif
                if (!oAngle->loopContains(*baseAngle)) {
                    baseAngle->insert(oAngle);
                }
            }
            if (++index == spanCount) {
                break;
            }
            nextFrom = fTs[index].fFromAngle;
            nextTo = fTs[index].fToAngle;
        } while (fromAngle == nextFrom && toAngle == nextTo);
        if (baseAngle && baseAngle->loopCount() == 1) {
            index = firstIndex;
            do {
                SkOpSpan& span = fTs[index];
                span.fFromAngle = span.fToAngle = NULL;
                if (++index == spanCount) {
                    break;
                }
                nextFrom = fTs[index].fFromAngle;
                nextTo = fTs[index].fToAngle;
            } while (fromAngle == nextFrom && toAngle == nextTo);
            baseAngle = NULL;
        }
#if DEBUG_SORT
        SkASSERT(!baseAngle || baseAngle->loopCount() > 1);
#endif
    } while (index < spanCount);
}

// return true if midpoints were computed
bool SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const {
    SkASSERT(start != end);
    edge[0] = fTs[start].fPt;
    int points = SkPathOpsVerbToPoints(fVerb);
    edge[points] = fTs[end].fPt;
    if (fVerb == SkPath::kLine_Verb) {
        return false;
    }
    double startT = fTs[start].fT;
    double endT = fTs[end].fT;
    if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) {
        // don't compute midpoints if we already have them
        if (fVerb == SkPath::kQuad_Verb) {
            edge[1] = fPts[1];
            return false;
        }
        SkASSERT(fVerb == SkPath::kCubic_Verb);
        if (start < end) {
            edge[1] = fPts[1];
            edge[2] = fPts[2];
            return false;
        }
        edge[1] = fPts[2];
        edge[2] = fPts[1];
        return false;
    }
    const SkDPoint sub[2] = {{ edge[0].fX, edge[0].fY}, {edge[points].fX, edge[points].fY }};
    if (fVerb == SkPath::kQuad_Verb) {
        edge[1] = SkDQuad::SubDivide(fPts, sub[0], sub[1], startT, endT).asSkPoint();
    } else {
        SkASSERT(fVerb == SkPath::kCubic_Verb);
        SkDPoint ctrl[2];
        SkDCubic::SubDivide(fPts, sub[0], sub[1], startT, endT, ctrl);
        edge[1] = ctrl[0].asSkPoint();
        edge[2] = ctrl[1].asSkPoint();
    }
    return true;
}

// return true if midpoints were computed
bool SkOpSegment::subDivide(int start, int end, SkDCubic* result) const {
    SkASSERT(start != end);
    (*result)[0].set(fTs[start].fPt);
    int points = SkPathOpsVerbToPoints(fVerb);
    (*result)[points].set(fTs[end].fPt);
    if (fVerb == SkPath::kLine_Verb) {
        return false;
    }
    double startT = fTs[start].fT;
    double endT = fTs[end].fT;
    if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) {
        // don't compute midpoints if we already have them
        if (fVerb == SkPath::kQuad_Verb) {
            (*result)[1].set(fPts[1]);
            return false;
        }
        SkASSERT(fVerb == SkPath::kCubic_Verb);
        if (start < end) {
            (*result)[1].set(fPts[1]);
            (*result)[2].set(fPts[2]);
            return false;
        }
        (*result)[1].set(fPts[2]);
        (*result)[2].set(fPts[1]);
        return false;
    }
    if (fVerb == SkPath::kQuad_Verb) {
        (*result)[1] = SkDQuad::SubDivide(fPts, (*result)[0], (*result)[2], startT, endT);
    } else {
        SkASSERT(fVerb == SkPath::kCubic_Verb);
        SkDCubic::SubDivide(fPts, (*result)[0], (*result)[3], startT, endT, &(*result)[1]);
    }
    return true;
}

void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const {
    SkPoint edge[4];
    subDivide(start, end, edge);
    (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge);
}

void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const SkPoint& endPt,
        const SkPoint& startPt) {
    int outCount = outsidePts->count();
    if (outCount == 0 || endPt != (*outsidePts)[outCount - 2]) {
        outsidePts->push_back(endPt);
        outsidePts->push_back(startPt);
    }
}

void SkOpSegment::TrackOutside(SkTArray<SkPoint, true>* outsidePts, const SkPoint& startPt) {
    int outCount = outsidePts->count();
    if (outCount == 0 || startPt != (*outsidePts)[outCount - 1]) {
        outsidePts->push_back(startPt);
    }
}

void SkOpSegment::undoneSpan(int* start, int* end) {
    int tCount = fTs.count();
    int index;
    for (index = 0; index < tCount; ++index) {
        if (!fTs[index].fDone) {
            break;
        }
    }
    SkASSERT(index < tCount - 1);
    *start = index;
    double startT = fTs[index].fT;
    while (approximately_negative(fTs[++index].fT - startT))
        SkASSERT(index < tCount);
    SkASSERT(index < tCount);
    *end = index;
}

int SkOpSegment::updateOppWinding(int index, int endIndex) const {
    int lesser = SkMin32(index, endIndex);
    int oppWinding = oppSum(lesser);
    int oppSpanWinding = oppSign(index, endIndex);
    if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding)
            && oppWinding != SK_MaxS32) {
        oppWinding -= oppSpanWinding;
    }
    return oppWinding;
}

int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const {
    int startIndex = angle->start();
    int endIndex = angle->end();
    return updateOppWinding(endIndex, startIndex);
}

int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const {
    int startIndex = angle->start();
    int endIndex = angle->end();
    return updateOppWinding(startIndex, endIndex);
}

int SkOpSegment::updateWinding(int index, int endIndex) const {
    int lesser = SkMin32(index, endIndex);
    int winding = windSum(lesser);
    if (winding == SK_MinS32) {
        return winding;
    }
    int spanWinding = spanSign(index, endIndex);
    if (winding && UseInnerWinding(winding - spanWinding, winding)
            && winding != SK_MaxS32) {
        winding -= spanWinding;
    }
    return winding;
}

int SkOpSegment::updateWinding(const SkOpAngle* angle) const {
    int startIndex = angle->start();
    int endIndex = angle->end();
    return updateWinding(endIndex, startIndex);
}

int SkOpSegment::updateWindingReverse(int index, int endIndex) const {
    int lesser = SkMin32(index, endIndex);
    int winding = windSum(lesser);
    int spanWinding = spanSign(endIndex, index);
    if (winding && UseInnerWindingReverse(winding - spanWinding, winding)
            && winding != SK_MaxS32) {
        winding -= spanWinding;
    }
    return winding;
}

int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const {
    int startIndex = angle->start();
    int endIndex = angle->end();
    return updateWindingReverse(endIndex, startIndex);
}

// OPTIMIZATION: does the following also work, and is it any faster?
// return outerWinding * innerWinding > 0
//      || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) {
    SkASSERT(outerWinding != SK_MaxS32);
    SkASSERT(innerWinding != SK_MaxS32);
    int absOut = abs(outerWinding);
    int absIn = abs(innerWinding);
    bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
    return result;
}

bool SkOpSegment::UseInnerWindingReverse(int outerWinding, int innerWinding) {
    SkASSERT(outerWinding != SK_MaxS32);
    SkASSERT(innerWinding != SK_MaxS32);
    int absOut = abs(outerWinding);
    int absIn = abs(innerWinding);
    bool result = absOut == absIn ? true : absOut < absIn;
    return result;
}

int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const {
    if (approximately_zero(tHit - t(tIndex))) {  // if we hit the end of a span, disregard
        return SK_MinS32;
    }
    int winding = crossOpp ? oppSum(tIndex) : windSum(tIndex);
    SkASSERT(winding != SK_MinS32);
    int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex);
#if DEBUG_WINDING_AT_T
    SkDebugf("%s id=%d opp=%d tHit=%1.9g t=%1.9g oldWinding=%d windValue=%d", __FUNCTION__,
            debugID(), crossOpp, tHit, t(tIndex), winding, windVal);
#endif
    // see if a + change in T results in a +/- change in X (compute x'(T))
    *dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX;
    if (fVerb > SkPath::kLine_Verb && approximately_zero(*dx)) {
        *dx = fPts[2].fX - fPts[1].fX - *dx;
    }
    if (*dx == 0) {
#if DEBUG_WINDING_AT_T
        SkDebugf(" dx=0 winding=SK_MinS32\n");
#endif
        return SK_MinS32;
    }
    if (windVal < 0) {  // reverse sign if opp contour traveled in reverse
            *dx = -*dx;
    }
    if (winding * *dx > 0) {  // if same signs, result is negative
        winding += *dx > 0 ? -windVal : windVal;
    }
#if DEBUG_WINDING_AT_T
    SkDebugf(" dx=%c winding=%d\n", *dx > 0 ? '+' : '-', winding);
#endif
    return winding;
}

int SkOpSegment::windSum(const SkOpAngle* angle) const {
    int start = angle->start();
    int end = angle->end();
    int index = SkMin32(start, end);
    return windSum(index);
}

void SkOpSegment::zeroSpan(SkOpSpan* span) {
    SkASSERT(span->fWindValue > 0 || span->fOppValue != 0);
    span->fWindValue = 0;
    span->fOppValue = 0;
    if (span->fTiny || span->fSmall) {
        return;
    }
    SkASSERT(!span->fDone);
    span->fDone = true;
    ++fDoneSpans;
}