C++程序  |  2706行  |  99.1 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 "Simplify.h"

#undef SkASSERT
#define SkASSERT(cond) while (!(cond)) { sk_throw(); }

// FIXME: remove once debugging is complete
#if 01 // set to 1 for no debugging whatsoever

//const bool gRunTestsInOneThread = false;

#define DEBUG_ACTIVE_LESS_THAN 0
#define DEBUG_ADD 0
#define DEBUG_ADD_BOTTOM_TS 0
#define DEBUG_ADD_INTERSECTING_TS 0
#define DEBUG_ADJUST_COINCIDENT 0
#define DEBUG_ASSEMBLE 0
#define DEBUG_BOTTOM 0
#define DEBUG_BRIDGE 0
#define DEBUG_DUMP 0
#define DEBUG_SORT_HORIZONTAL 0
#define DEBUG_OUT 0
#define DEBUG_OUT_LESS_THAN 0
#define DEBUG_SPLIT 0
#define DEBUG_STITCH_EDGE 0
#define DEBUG_TRIM_LINE 0

#else

//const bool gRunTestsInOneThread = true;

#define DEBUG_ACTIVE_LESS_THAN 0
#define DEBUG_ADD 01
#define DEBUG_ADD_BOTTOM_TS 0
#define DEBUG_ADD_INTERSECTING_TS 0
#define DEBUG_ADJUST_COINCIDENT 1
#define DEBUG_ASSEMBLE 1
#define DEBUG_BOTTOM 0
#define DEBUG_BRIDGE 1
#define DEBUG_DUMP 1
#define DEBUG_SORT_HORIZONTAL 01
#define DEBUG_OUT 01
#define DEBUG_OUT_LESS_THAN 0
#define DEBUG_SPLIT 1
#define DEBUG_STITCH_EDGE 1
#define DEBUG_TRIM_LINE 1

#endif

#if DEBUG_ASSEMBLE || DEBUG_BRIDGE
static const char* kLVerbStr[] = {"", "line", "quad", "cubic"};
#endif
#if DEBUG_STITCH_EDGE
static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"};
#endif

static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
        Intersections& intersections) {
    const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
    return intersect(aLine, bLine, intersections);
}

static int QuadLineIntersect(const SkPoint a[3], const SkPoint b[2],
        Intersections& intersections) {
    const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
    const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
    intersect(aQuad, bLine, intersections);
    return intersections.fUsed;
}

static int CubicLineIntersect(const SkPoint a[2], const SkPoint b[3],
        Intersections& intersections) {
    const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
            {a[3].fX, a[3].fY}};
    const _Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
    return intersect(aCubic, bLine, intersections);
}

static int QuadIntersect(const SkPoint a[3], const SkPoint b[3],
        Intersections& intersections) {
    const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
    const Quadratic bQuad = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY}};
    intersect2(aQuad, bQuad, intersections);
    return intersections.fUsed;
}

static int CubicIntersect(const SkPoint a[4], const SkPoint b[4],
        Intersections& intersections) {
    const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
            {a[3].fX, a[3].fY}};
    const Cubic bCubic = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}, {b[2].fX, b[2].fY},
            {b[3].fX, b[3].fY}};
    intersect(aCubic, bCubic, intersections);
    return intersections.fUsed;
}

static int LineIntersect(const SkPoint a[2], SkScalar left, SkScalar right,
        SkScalar y, double aRange[2]) {
    const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    return horizontalLineIntersect(aLine, left, right, y, aRange);
}

static int QuadIntersect(const SkPoint a[3], SkScalar left, SkScalar right,
        SkScalar y, double aRange[3]) {
    const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
    return horizontalIntersect(aQuad, left, right, y, aRange);
}

static int CubicIntersect(const SkPoint a[4], SkScalar left, SkScalar right,
        SkScalar y, double aRange[4]) {
    const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
            {a[3].fX, a[3].fY}};
    return horizontalIntersect(aCubic, left, right, y, aRange);
}

static void LineXYAtT(const SkPoint a[2], double t, SkPoint* out) {
    const _Line line = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    double x, y;
    xy_at_t(line, t, x, y);
    out->fX = SkDoubleToScalar(x);
    out->fY = SkDoubleToScalar(y);
}

static void QuadXYAtT(const SkPoint a[3], double t, SkPoint* out) {
    const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
    double x, y;
    xy_at_t(quad, t, x, y);
    out->fX = SkDoubleToScalar(x);
    out->fY = SkDoubleToScalar(y);
}

static void CubicXYAtT(const SkPoint a[4], double t, SkPoint* out) {
    const Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
            {a[3].fX, a[3].fY}};
    double x, y;
    xy_at_t(cubic, t, x, y);
    out->fX = SkDoubleToScalar(x);
    out->fY = SkDoubleToScalar(y);
}

static SkScalar LineYAtT(const SkPoint a[2], double t) {
    const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    double y;
    xy_at_t(aLine, t, *(double*) 0, y);
    return SkDoubleToScalar(y);
}

static SkScalar QuadYAtT(const SkPoint a[3], double t) {
    const Quadratic quad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY}};
    double y;
    xy_at_t(quad, t, *(double*) 0, y);
    return SkDoubleToScalar(y);
}

static SkScalar CubicYAtT(const SkPoint a[4], double t) {
    const Cubic cubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}, {a[2].fX, a[2].fY},
            {a[3].fX, a[3].fY}};
    double y;
    xy_at_t(cubic, t, *(double*) 0, y);
    return SkDoubleToScalar(y);
}

static void LineSubDivide(const SkPoint a[2], double startT, double endT,
        SkPoint sub[2]) {
    const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    _Line dst;
    sub_divide(aLine, startT, endT, dst);
    sub[0].fX = SkDoubleToScalar(dst[0].x);
    sub[0].fY = SkDoubleToScalar(dst[0].y);
    sub[1].fX = SkDoubleToScalar(dst[1].x);
    sub[1].fY = SkDoubleToScalar(dst[1].y);
}

static void QuadSubDivide(const SkPoint a[3], double startT, double endT,
        SkPoint sub[3]) {
    const Quadratic aQuad = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
            {a[2].fX, a[2].fY}};
    Quadratic dst;
    sub_divide(aQuad, startT, endT, dst);
    sub[0].fX = SkDoubleToScalar(dst[0].x);
    sub[0].fY = SkDoubleToScalar(dst[0].y);
    sub[1].fX = SkDoubleToScalar(dst[1].x);
    sub[1].fY = SkDoubleToScalar(dst[1].y);
    sub[2].fX = SkDoubleToScalar(dst[2].x);
    sub[2].fY = SkDoubleToScalar(dst[2].y);
}

static void CubicSubDivide(const SkPoint a[4], double startT, double endT,
        SkPoint sub[4]) {
    const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
            {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}};
    Cubic dst;
    sub_divide(aCubic, startT, endT, dst);
    sub[0].fX = SkDoubleToScalar(dst[0].x);
    sub[0].fY = SkDoubleToScalar(dst[0].y);
    sub[1].fX = SkDoubleToScalar(dst[1].x);
    sub[1].fY = SkDoubleToScalar(dst[1].y);
    sub[2].fX = SkDoubleToScalar(dst[2].x);
    sub[2].fY = SkDoubleToScalar(dst[2].y);
    sub[3].fX = SkDoubleToScalar(dst[3].x);
    sub[3].fY = SkDoubleToScalar(dst[3].y);
}

static void QuadSubBounds(const SkPoint a[3], double startT, double endT,
        SkRect& bounds) {
    SkPoint dst[3];
    QuadSubDivide(a, startT, endT, dst);
    bounds.fLeft = bounds.fRight = dst[0].fX;
    bounds.fTop = bounds.fBottom = dst[0].fY;
    for (int index = 1; index < 3; ++index) {
        bounds.growToInclude(dst[index].fX, dst[index].fY);
    }
}

static void CubicSubBounds(const SkPoint a[4], double startT, double endT,
        SkRect& bounds) {
    SkPoint dst[4];
    CubicSubDivide(a, startT, endT, dst);
    bounds.fLeft = bounds.fRight = dst[0].fX;
    bounds.fTop = bounds.fBottom = dst[0].fY;
    for (int index = 1; index < 4; ++index) {
        bounds.growToInclude(dst[index].fX, dst[index].fY);
    }
}

static SkPath::Verb QuadReduceOrder(SkPoint a[4]) {
    const Quadratic aQuad =  {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
            {a[2].fX, a[2].fY}};
    Quadratic dst;
    int order = reduceOrder(aQuad, dst, kReduceOrder_TreatAsFill);
    for (int index = 0; index < order; ++index) {
        a[index].fX = SkDoubleToScalar(dst[index].x);
        a[index].fY = SkDoubleToScalar(dst[index].y);
    }
    if (order == 1) { // FIXME: allow returning points, caller should discard
        a[1] = a[0];
        return (SkPath::Verb) order;
    }
    return (SkPath::Verb) (order - 1);
}

static SkPath::Verb CubicReduceOrder(SkPoint a[4]) {
    const Cubic aCubic = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY},
            {a[2].fX, a[2].fY}, {a[3].fX, a[3].fY}};
    Cubic dst;
    int order = reduceOrder(aCubic, dst, kReduceOrder_QuadraticsAllowed, kReduceOrder_TreatAsFill);
    for (int index = 0; index < order; ++index) {
        a[index].fX = SkDoubleToScalar(dst[index].x);
        a[index].fY = SkDoubleToScalar(dst[index].y);
    }
    if (order == 1) { // FIXME: allow returning points, caller should discard
        a[1] = a[0];
        return (SkPath::Verb) order;
    }
    return (SkPath::Verb) (order - 1);
}

static bool IsCoincident(const SkPoint a[2], const SkPoint& above,
        const SkPoint& below) {
    const _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
    const _Line bLine = {{above.fX, above.fY}, {below.fX, below.fY}};
    return implicit_matches_ulps(aLine, bLine, 32);
}

/*
list of edges
bounds for edge
sort
active T

if a contour's bounds is outside of the active area, no need to create edges
*/

/* given one or more paths,
 find the bounds of each contour, select the active contours
 for each active contour, compute a set of edges
 each edge corresponds to one or more lines and curves
 leave edges unbroken as long as possible
 when breaking edges, compute the t at the break but leave the control points alone

 */

void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
    SkPath::Iter iter(path, false);
    SkPoint pts[4];
    SkPath::Verb verb;
    SkRect bounds;
    bounds.setEmpty();
    int count = 0;
    while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
        switch (verb) {
            case SkPath::kMove_Verb:
                if (!bounds.isEmpty()) {
                    *boundsArray.append() = bounds;
                }
                bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
                count = 0;
                break;
            case SkPath::kLine_Verb:
                count = 1;
                break;
            case SkPath::kQuad_Verb:
                count = 2;
                break;
            case SkPath::kCubic_Verb:
                count = 3;
                break;
            case SkPath::kClose_Verb:
                count = 0;
                break;
            default:
                SkDEBUGFAIL("bad verb");
                return;
        }
        for (int i = 1; i <= count; ++i) {
            bounds.growToInclude(pts[i].fX, pts[i].fY);
        }
    }
}

static bool extendLine(const SkPoint line[2], const SkPoint& add) {
    // FIXME: allow this to extend lines that have slopes that are nearly equal
    SkScalar dx1 = line[1].fX - line[0].fX;
    SkScalar dy1 = line[1].fY - line[0].fY;
    SkScalar dx2 = add.fX - line[0].fX;
    SkScalar dy2 = add.fY - line[0].fY;
    return dx1 * dy2 == dx2 * dy1;
}

// OPTIMIZATION: this should point to a list of input data rather than duplicating
// the line data here. This would reduce the need to assemble the results.
struct OutEdge {
    bool operator<(const OutEdge& rh) const {
        const SkPoint& first = fPts[0];
        const SkPoint& rhFirst = rh.fPts[0];
        return first.fY == rhFirst.fY
                ? first.fX < rhFirst.fX
                : first.fY < rhFirst.fY;
    }

    SkPoint fPts[4];
    int fID; // id of edge generating data
    uint8_t fVerb; // FIXME: not read from everywhere
    bool fCloseCall; // edge is trimmable if not originally coincident
};

class OutEdgeBuilder {
public:
    OutEdgeBuilder(bool fill)
        : fFill(fill) {
        }

    void addCurve(const SkPoint line[4], SkPath::Verb verb, int id,
            bool closeCall) {
        OutEdge& newEdge = fEdges.push_back();
        memcpy(newEdge.fPts, line, (verb + 1) * sizeof(SkPoint));
        newEdge.fVerb = verb;
        newEdge.fID = id;
        newEdge.fCloseCall = closeCall;
    }

    bool trimLine(SkScalar y, int id) {
        size_t count = fEdges.count();
        while (count-- != 0) {
            OutEdge& edge = fEdges[count];
            if (edge.fID != id) {
                continue;
            }
            if (edge.fCloseCall) {
                return false;
            }
            SkASSERT(edge.fPts[0].fY <= y);
            if (edge.fPts[1].fY <= y) {
                continue;
            }
            edge.fPts[1].fX = edge.fPts[0].fX + (y - edge.fPts[0].fY)
                    * (edge.fPts[1].fX - edge.fPts[0].fX)
                    / (edge.fPts[1].fY - edge.fPts[0].fY);
            edge.fPts[1].fY = y;
#if DEBUG_TRIM_LINE
            SkDebugf("%s edge=%d %1.9g,%1.9g\n", __FUNCTION__, id,
                    edge.fPts[1].fX, y);
#endif
            return true;
        }
        return false;
    }

    void assemble(SkPath& simple) {
        size_t listCount = fEdges.count();
        if (listCount == 0) {
            return;
        }
        do {
            size_t listIndex = 0;
            int advance = 1;
            while (listIndex < listCount && fTops[listIndex] == 0) {
                ++listIndex;
            }
            if (listIndex >= listCount) {
                break;
            }
            int closeEdgeIndex = -listIndex - 1;
            // the curve is deferred and not added right away because the
            // following edge may extend the first curve.
            SkPoint firstPt, lastCurve[4];
            uint8_t lastVerb;
#if DEBUG_ASSEMBLE
            int firstIndex, lastIndex;
            const int tab = 8;
#endif
            bool doMove = true;
            int edgeIndex;
            do {
                SkPoint* ptArray = fEdges[listIndex].fPts;
                uint8_t verb = fEdges[listIndex].fVerb;
                SkPoint* curve[4];
                if (advance < 0) {
                    curve[0] = &ptArray[verb];
                    if (verb == SkPath::kCubic_Verb) {
                        curve[1] = &ptArray[2];
                        curve[2] = &ptArray[1];
                    }
                    curve[verb] = &ptArray[0];
                } else {
                    curve[0] = &ptArray[0];
                    if (verb == SkPath::kCubic_Verb) {
                        curve[1] = &ptArray[1];
                        curve[2] = &ptArray[2];
                    }
                    curve[verb] = &ptArray[verb];
                }
                if (verb == SkPath::kQuad_Verb) {
                    curve[1] = &ptArray[1];
                }
                if (doMove) {
                    firstPt = *curve[0];
                    simple.moveTo(curve[0]->fX, curve[0]->fY);
#if DEBUG_ASSEMBLE
                    SkDebugf("%s %d moveTo (%g,%g)\n", __FUNCTION__,
                            listIndex + 1, curve[0]->fX, curve[0]->fY);
                    firstIndex = listIndex;
#endif
                    for (int index = 0; index <= verb; ++index) {
                        lastCurve[index] = *curve[index];
                    }
                    doMove = false;
                } else {
                    bool gap = lastCurve[lastVerb] != *curve[0];
                    if (gap || lastVerb != SkPath::kLine_Verb) { // output the accumulated curve before the gap
                        // FIXME: see comment in bridge -- this probably
                        // conceals errors
                        SkASSERT(fFill && UlpsDiff(lastCurve[lastVerb].fY,
                                curve[0]->fY) <= 10);
                        switch (lastVerb) {
                            case SkPath::kLine_Verb:
                                simple.lineTo(lastCurve[1].fX, lastCurve[1].fY);
                                break;
                            case SkPath::kQuad_Verb:
                                simple.quadTo(lastCurve[1].fX, lastCurve[1].fY,
                                        lastCurve[2].fX, lastCurve[2].fY);
                                break;
                            case SkPath::kCubic_Verb:
                                simple.cubicTo(lastCurve[1].fX, lastCurve[1].fY,
                                        lastCurve[2].fX, lastCurve[2].fY,
                                        lastCurve[3].fX, lastCurve[3].fY);
                                break;
                        }
#if DEBUG_ASSEMBLE
                        SkDebugf("%*s %d %sTo (%g,%g)\n", tab, "", lastIndex + 1,
                                kLVerbStr[lastVerb], lastCurve[lastVerb].fX,
                                lastCurve[lastVerb].fY);
#endif
                    }
                    int firstCopy = 1;
                    if (gap || (lastVerb == SkPath::kLine_Verb
                             && (verb != SkPath::kLine_Verb
                             || !extendLine(lastCurve, *curve[verb])))) {
                        // FIXME: see comment in bridge -- this probably
                        // conceals errors
                        SkASSERT(lastCurve[lastVerb] == *curve[0] ||
                                (fFill && UlpsDiff(lastCurve[lastVerb].fY,
                                curve[0]->fY) <= 10));
                        simple.lineTo(curve[0]->fX, curve[0]->fY);
#if DEBUG_ASSEMBLE
                        SkDebugf("%*s %d gap lineTo (%g,%g)\n", tab, "",
                                lastIndex + 1, curve[0]->fX, curve[0]->fY);
#endif
                        firstCopy = 0;
                    } else if (lastVerb != SkPath::kLine_Verb) {
                        firstCopy = 0;
                    }
                    for (int index = firstCopy; index <= verb; ++index) {
                        lastCurve[index] = *curve[index];
                    }
                }
                lastVerb = verb;
#if DEBUG_ASSEMBLE
                lastIndex = listIndex;
#endif
                if (advance < 0) {
                    edgeIndex = fTops[listIndex];
                    fTops[listIndex] = 0;
                } else {
                    edgeIndex = fBottoms[listIndex];
                    fBottoms[listIndex] = 0;
                }
                if (edgeIndex) {
                    listIndex = abs(edgeIndex) - 1;
                    if (edgeIndex < 0) {
                        fTops[listIndex] = 0;
                    } else {
                        fBottoms[listIndex] = 0;
                    }
                }
                if (edgeIndex == closeEdgeIndex || edgeIndex == 0) {
                    switch (lastVerb) {
                        case SkPath::kLine_Verb:
                            simple.lineTo(lastCurve[1].fX, lastCurve[1].fY);
                            break;
                        case SkPath::kQuad_Verb:
                            simple.quadTo(lastCurve[1].fX, lastCurve[1].fY,
                                    lastCurve[2].fX, lastCurve[2].fY);
                            break;
                        case SkPath::kCubic_Verb:
                            simple.cubicTo(lastCurve[1].fX, lastCurve[1].fY,
                                    lastCurve[2].fX, lastCurve[2].fY,
                                    lastCurve[3].fX, lastCurve[3].fY);
                            break;
                    }
#if DEBUG_ASSEMBLE
                    SkDebugf("%*s %d %sTo last (%g, %g)\n", tab, "",
                            lastIndex + 1, kLVerbStr[lastVerb],
                            lastCurve[lastVerb].fX, lastCurve[lastVerb].fY);
#endif
                    if (lastCurve[lastVerb] != firstPt) {
                        simple.lineTo(firstPt.fX, firstPt.fY);
#if DEBUG_ASSEMBLE
                        SkDebugf("%*s %d final line (%g, %g)\n", tab, "",
                                firstIndex + 1, firstPt.fX, firstPt.fY);
#endif
                    }
                    simple.close();
#if DEBUG_ASSEMBLE
                    SkDebugf("%*s   close\n", tab, "");
#endif
                    break;
                }
                // if this and next edge go different directions
#if DEBUG_ASSEMBLE
                SkDebugf("%*s   advance=%d edgeIndex=%d flip=%s\n", tab, "",
                        advance, edgeIndex, advance > 0 ^ edgeIndex < 0 ?
                        "true" : "false");
#endif
                if (advance > 0 ^ edgeIndex < 0) {
                    advance = -advance;
                }
            } while (edgeIndex);
        } while (true);
    }

    // sort points by y, then x
    // if x/y is identical, sort bottoms before tops
    // if identical and both tops/bottoms, sort by angle
    static bool lessThan(SkTArray<OutEdge>& edges, const int one,
            const int two) {
        const OutEdge& oneEdge = edges[abs(one) - 1];
        int oneIndex = one < 0 ? 0 : oneEdge.fVerb;
        const SkPoint& startPt1 = oneEdge.fPts[oneIndex];
        const OutEdge& twoEdge = edges[abs(two) - 1];
        int twoIndex = two < 0 ? 0 : twoEdge.fVerb;
        const SkPoint& startPt2 = twoEdge.fPts[twoIndex];
        if (startPt1.fY != startPt2.fY) {
    #if DEBUG_OUT_LESS_THAN
            SkDebugf("%s %d<%d (%g,%g) %s startPt1.fY < startPt2.fY\n", __FUNCTION__,
                    one, two, startPt1.fY, startPt2.fY,
                    startPt1.fY < startPt2.fY ? "true" : "false");
    #endif
            return startPt1.fY < startPt2.fY;
        }
        if (startPt1.fX != startPt2.fX) {
    #if DEBUG_OUT_LESS_THAN
            SkDebugf("%s %d<%d (%g,%g) %s startPt1.fX < startPt2.fX\n", __FUNCTION__,
                    one, two, startPt1.fX, startPt2.fX,
                    startPt1.fX < startPt2.fX ? "true" : "false");
    #endif
            return startPt1.fX < startPt2.fX;
        }
        const SkPoint& endPt1 = oneEdge.fPts[oneIndex ^ oneEdge.fVerb];
        const SkPoint& endPt2 = twoEdge.fPts[twoIndex ^ twoEdge.fVerb];
        SkScalar dy1 = startPt1.fY - endPt1.fY;
        SkScalar dy2 = startPt2.fY - endPt2.fY;
        SkScalar dy1y2 = dy1 * dy2;
        if (dy1y2 < 0) { // different signs
    #if DEBUG_OUT_LESS_THAN
                SkDebugf("%s %d<%d %s dy1 > 0\n", __FUNCTION__, one, two,
                        dy1 > 0 ? "true" : "false");
    #endif
            return dy1 > 0; // one < two if one goes up and two goes down
        }
        if (dy1y2 == 0) {
    #if DEBUG_OUT_LESS_THAN
            SkDebugf("%s %d<%d %s endPt1.fX < endPt2.fX\n", __FUNCTION__,
                    one, two, endPt1.fX < endPt2.fX ? "true" : "false");
    #endif
            return endPt1.fX < endPt2.fX;
        }
        SkScalar dx1y2 = (startPt1.fX - endPt1.fX) * dy2;
        SkScalar dx2y1 = (startPt2.fX - endPt2.fX) * dy1;
    #if DEBUG_OUT_LESS_THAN
        SkDebugf("%s %d<%d %s dy2 < 0 ^ dx1y2 < dx2y1\n", __FUNCTION__,
                one, two, dy2 < 0 ^ dx1y2 < dx2y1 ? "true" : "false");
    #endif
        return dy2 > 0 ^ dx1y2 < dx2y1;
    }

    // Sort the indices of paired points and then create more indices so
    // assemble() can find the next edge and connect the top or bottom
    void bridge() {
        size_t index;
        size_t count = fEdges.count();
        if (!count) {
            return;
        }
        SkASSERT(!fFill || count > 1);
        fTops.setCount(count);
        sk_bzero(fTops.begin(), sizeof(fTops[0]) * count);
        fBottoms.setCount(count);
        sk_bzero(fBottoms.begin(), sizeof(fBottoms[0]) * count);
        SkTDArray<int> order;
        for (index = 1; index <= count; ++index) {
            *order.append() = -index;
        }
        for (index = 1; index <= count; ++index) {
            *order.append() = index;
        }
        QSort<SkTArray<OutEdge>, int>(fEdges, order.begin(), order.end() - 1, lessThan);
        int* lastPtr = order.end() - 1;
        int* leftPtr = order.begin();
        while (leftPtr < lastPtr) {
            int leftIndex = *leftPtr;
            int leftOutIndex = abs(leftIndex) - 1;
            const OutEdge& left = fEdges[leftOutIndex];
            int* rightPtr = leftPtr + 1;
            int rightIndex = *rightPtr;
            int rightOutIndex = abs(rightIndex) - 1;
            const OutEdge& right = fEdges[rightOutIndex];
            bool pairUp = fFill;
            if (!pairUp) {
                const SkPoint& leftMatch =
                        left.fPts[leftIndex < 0 ? 0 : left.fVerb];
                const SkPoint& rightMatch =
                        right.fPts[rightIndex < 0 ? 0 : right.fVerb];
                pairUp = leftMatch == rightMatch;
            } else {
        #if DEBUG_OUT
        // FIXME : not happy that error in low bit is allowed
        // this probably conceals error elsewhere
                if (UlpsDiff(left.fPts[leftIndex < 0 ? 0 : left.fVerb].fY,
                        right.fPts[rightIndex < 0 ? 0 : right.fVerb].fY) > 1) {
                    *fMismatches.append() = leftIndex;
                    if (rightPtr == lastPtr) {
                        *fMismatches.append() = rightIndex;
                    }
                    pairUp = false;
                }
        #else
                SkASSERT(UlpsDiff(left.fPts[leftIndex < 0 ? 0 : left.fVerb].fY,
                        right.fPts[rightIndex < 0 ? 0 : right.fVerb].fY) <= 10);
        #endif
            }
            if (pairUp) {
                if (leftIndex < 0) {
                    fTops[leftOutIndex] = rightIndex;
                } else {
                    fBottoms[leftOutIndex] = rightIndex;
                }
                if (rightIndex < 0) {
                    fTops[rightOutIndex] = leftIndex;
                } else {
                    fBottoms[rightOutIndex] = leftIndex;
                }
                ++rightPtr;
            }
            leftPtr = rightPtr;
        }
#if DEBUG_OUT
        int* mismatch = fMismatches.begin();
        while (mismatch != fMismatches.end()) {
            int leftIndex = *mismatch++;
            int leftOutIndex = abs(leftIndex) - 1;
            const OutEdge& left = fEdges[leftOutIndex];
            const SkPoint& leftPt = left.fPts[leftIndex < 0 ? 0 : left.fVerb];
            SkDebugf("%s left=%d %s (%1.9g,%1.9g)\n",
                    __FUNCTION__, left.fID, leftIndex < 0 ? "top" : "bot",
                    leftPt.fX, leftPt.fY);
        }
        SkASSERT(fMismatches.count() == 0);
#endif
#if DEBUG_BRIDGE
    for (index = 0; index < count; ++index) {
        const OutEdge& edge = fEdges[index];
        uint8_t verb = edge.fVerb;
        SkDebugf("%s %d edge=%d %s (%1.9g,%1.9g) (%1.9g,%1.9g)\n",
                index == 0 ? __FUNCTION__ : "      ",
                index + 1, edge.fID, kLVerbStr[verb], edge.fPts[0].fX,
                edge.fPts[0].fY, edge.fPts[verb].fX, edge.fPts[verb].fY);
    }
    for (index = 0; index < count; ++index) {
        SkDebugf("       top    of % 2d connects to %s of % 2d\n", index + 1,
                fTops[index] < 0 ? "top   " : "bottom", abs(fTops[index]));
        SkDebugf("       bottom of % 2d connects to %s of % 2d\n", index + 1,
                fBottoms[index] < 0 ? "top   " : "bottom", abs(fBottoms[index]));
    }
#endif
    }

protected:
    SkTArray<OutEdge> fEdges;
    SkTDArray<int> fTops;
    SkTDArray<int> fBottoms;
    bool fFill;
#if DEBUG_OUT
    SkTDArray<int> fMismatches;
#endif
};

// Bounds, unlike Rect, does not consider a vertical line to be empty.
struct Bounds : public SkRect {
    static bool Intersects(const Bounds& a, const Bounds& b) {
        return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
                a.fTop <= b.fBottom && b.fTop <= a.fBottom;
    }

    bool isEmpty() {
        return fLeft > fRight || fTop > fBottom
                || (fLeft == fRight && fTop == fBottom)
                || isnan(fLeft) || isnan(fRight)
                || isnan(fTop) || isnan(fBottom);
    }
};

class Intercepts {
public:
    Intercepts()
        : fTopIntercepts(0)
        , fBottomIntercepts(0)
        , fExplicit(false) {
    }

    Intercepts& operator=(const Intercepts& src) {
        fTs = src.fTs;
        fTopIntercepts = src.fTopIntercepts;
        fBottomIntercepts = src.fBottomIntercepts;
        return *this;
    }

    // OPTIMIZATION: remove this function if it's never called
    double t(int tIndex) const {
        if (tIndex == 0) {
            return 0;
        }
        if (tIndex > fTs.count()) {
            return 1;
        }
        return fTs[tIndex - 1];
    }

#if DEBUG_DUMP
    void dump(const SkPoint* pts, SkPath::Verb verb) {
        const char className[] = "Intercepts";
        const int tab = 8;
        for (int i = 0; i < fTs.count(); ++i) {
            SkPoint out;
            switch (verb) {
                case SkPath::kLine_Verb:
                    LineXYAtT(pts, fTs[i], &out);
                    break;
                case SkPath::kQuad_Verb:
                    QuadXYAtT(pts, fTs[i], &out);
                    break;
                case SkPath::kCubic_Verb:
                    CubicXYAtT(pts, fTs[i], &out);
                    break;
                default:
                    SkASSERT(0);
            }
            SkDebugf("%*s.fTs[%d]=%1.9g (%1.9g,%1.9g)\n", tab + sizeof(className),
                    className, i, fTs[i], out.fX, out.fY);
        }
        SkDebugf("%*s.fTopIntercepts=%u\n", tab + sizeof(className),
                className, fTopIntercepts);
        SkDebugf("%*s.fBottomIntercepts=%u\n", tab + sizeof(className),
                className, fBottomIntercepts);
        SkDebugf("%*s.fExplicit=%d\n", tab + sizeof(className),
                className, fExplicit);
    }
#endif

    SkTDArray<double> fTs;
    unsigned char fTopIntercepts; // 0=init state 1=1 edge >1=multiple edges
    unsigned char fBottomIntercepts;
    bool fExplicit; // if set, suppress 0 and 1

};

struct HorizontalEdge {
    bool operator<(const HorizontalEdge& rh) const {
        return fY == rh.fY ? fLeft == rh.fLeft ? fRight < rh.fRight
                : fLeft < rh.fLeft : fY < rh.fY;
    }

#if DEBUG_DUMP
    void dump() {
        const char className[] = "HorizontalEdge";
        const int tab = 4;
        SkDebugf("%*s.fLeft=%1.9g\n", tab + sizeof(className), className, fLeft);
        SkDebugf("%*s.fRight=%1.9g\n", tab + sizeof(className), className, fRight);
        SkDebugf("%*s.fY=%1.9g\n", tab + sizeof(className), className, fY);
    }
#endif

    SkScalar fLeft;
    SkScalar fRight;
    SkScalar fY;
};

struct InEdge {
    bool operator<(const InEdge& rh) const {
        return fBounds.fTop == rh.fBounds.fTop
                ? fBounds.fLeft < rh.fBounds.fLeft
                : fBounds.fTop < rh.fBounds.fTop;
    }

    // Avoid collapsing t values that are close to the same since
    // we walk ts to describe consecutive intersections. Since a pair of ts can
    // be nearly equal, any problems caused by this should be taken care
    // of later.
    int add(double* ts, size_t count, ptrdiff_t verbIndex) {
        // FIXME: in the pathological case where there is a ton of intercepts, binary search?
        bool foundIntercept = false;
        int insertedAt = -1;
        Intercepts& intercepts = fIntercepts[verbIndex];
        for (size_t index = 0; index < count; ++index) {
            double t = ts[index];
            if (t <= 0) {
                intercepts.fTopIntercepts <<= 1;
                fContainsIntercepts |= ++intercepts.fTopIntercepts > 1;
                continue;
            }
            if (t >= 1) {
                intercepts.fBottomIntercepts <<= 1;
                fContainsIntercepts |= ++intercepts.fBottomIntercepts > 1;
                continue;
            }
            fIntersected = true;
            foundIntercept = true;
            size_t tCount = intercepts.fTs.count();
            double delta;
            for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
                if (t <= intercepts.fTs[idx2]) {
                    // FIXME: ?  if (t < intercepts.fTs[idx2]) // failed
                    delta = intercepts.fTs[idx2] - t;
                    if (delta > 0) {
                        insertedAt = idx2;
                        *intercepts.fTs.insert(idx2) = t;
                    }
                    goto nextPt;
                }
            }
            if (tCount == 0 || (delta = t - intercepts.fTs[tCount - 1]) > 0) {
                insertedAt = tCount;
                *intercepts.fTs.append() = t;
            }
    nextPt:
            ;
        }
        fContainsIntercepts |= foundIntercept;
        return insertedAt;
    }

    void addPartial(SkTArray<InEdge>& edges, int ptStart, int ptEnd,
            int verbStart, int verbEnd) {
        InEdge* edge = edges.push_back_n(1);
        int verbCount = verbEnd - verbStart;
        edge->fIntercepts.push_back_n(verbCount);
     //   uint8_t* verbs = &fVerbs[verbStart];
        for (int ceptIdx = 0; ceptIdx < verbCount; ++ceptIdx) {
            edge->fIntercepts[ceptIdx] = fIntercepts[verbStart + ceptIdx];
        }
        edge->fPts.append(ptEnd - ptStart, &fPts[ptStart]);
        edge->fVerbs.append(verbCount, &fVerbs[verbStart]);
        edge->setBounds();
        edge->fWinding = fWinding;
        edge->fContainsIntercepts = fContainsIntercepts; // FIXME: may not be correct -- but do we need to know?
    }

    void addSplit(SkTArray<InEdge>& edges, SkPoint* pts, uint8_t verb,
            Intercepts& intercepts, int firstT, int lastT, bool flipped) {
        InEdge* edge = edges.push_back_n(1);
        edge->fIntercepts.push_back_n(1);
        if (firstT == 0) {
            *edge->fIntercepts[0].fTs.append() = 0;
        } else {
            *edge->fIntercepts[0].fTs.append() = intercepts.fTs[firstT - 1];
        }
        bool add1 = lastT == intercepts.fTs.count();
        edge->fIntercepts[0].fTs.append(lastT - firstT, &intercepts.fTs[firstT]);
        if (add1) {
            *edge->fIntercepts[0].fTs.append() = 1;
        }
        edge->fIntercepts[0].fExplicit = true;
        edge->fPts.append(verb + 1, pts);
        edge->fVerbs.append(1, &verb);
        // FIXME: bounds could be better for partial Ts
        edge->setSubBounds();
        edge->fContainsIntercepts = fContainsIntercepts; // FIXME: may not be correct -- but do we need to know?
        if (flipped) {
            edge->flipTs();
            edge->fWinding = -fWinding;
        } else {
            edge->fWinding = fWinding;
        }
    }

    bool cached(const InEdge* edge) {
        // FIXME: in the pathological case where there is a ton of edges, binary search?
        size_t count = fCached.count();
        for (size_t index = 0; index < count; ++index) {
            if (edge == fCached[index]) {
                return true;
            }
            if (edge < fCached[index]) {
                *fCached.insert(index) = edge;
                return false;
            }
        }
        *fCached.append() = edge;
        return false;
    }

    void complete(signed char winding) {
        setBounds();
        fIntercepts.push_back_n(fVerbs.count());
        if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
            flip();
        }
        fContainsIntercepts = fIntersected = false;
    }

    void flip() {
        size_t index;
        size_t last = fPts.count() - 1;
        for (index = 0; index < last; ++index, --last) {
            SkTSwap<SkPoint>(fPts[index], fPts[last]);
        }
        last = fVerbs.count() - 1;
        for (index = 0; index < last; ++index, --last) {
            SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
        }
    }

    void flipTs() {
        SkASSERT(fIntercepts.count() == 1);
        Intercepts& intercepts = fIntercepts[0];
        SkASSERT(intercepts.fExplicit);
        SkTDArray<double>& ts = intercepts.fTs;
        size_t index;
        size_t last = ts.count() - 1;
        for (index = 0; index < last; ++index, --last) {
            SkTSwap<double>(ts[index], ts[last]);
        }
    }

    void reset() {
        fCached.reset();
        fIntercepts.reset();
        fPts.reset();
        fVerbs.reset();
        fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
        fWinding = 0;
        fContainsIntercepts = false;
        fIntersected = false;
    }

    void setBounds() {
        SkPoint* ptPtr = fPts.begin();
        SkPoint* ptLast = fPts.end();
        if (ptPtr == ptLast) {
            SkDebugf("%s empty edge\n", __FUNCTION__);
            SkASSERT(0);
            // FIXME: delete empty edge?
            return;
        }
        fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
        ++ptPtr;
        while (ptPtr != ptLast) {
            fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
            ++ptPtr;
        }
    }

    // recompute bounds based on subrange of T values
    void setSubBounds() {
        SkASSERT(fIntercepts.count() == 1);
        Intercepts& intercepts = fIntercepts[0];
        SkASSERT(intercepts.fExplicit);
        SkASSERT(fVerbs.count() == 1);
        SkTDArray<double>& ts = intercepts.fTs;
        if (fVerbs[0] == SkPath::kQuad_Verb) {
            SkASSERT(fPts.count() == 3);
            QuadSubBounds(fPts.begin(), ts[0], ts[ts.count() - 1], fBounds);
        } else {
            SkASSERT(fVerbs[0] == SkPath::kCubic_Verb);
            SkASSERT(fPts.count() == 4);
            CubicSubBounds(fPts.begin(), ts[0], ts[ts.count() - 1], fBounds);
        }
    }

    void splitInflectionPts(SkTArray<InEdge>& edges) {
        if (!fIntersected) {
            return;
        }
        uint8_t* verbs = fVerbs.begin();
        SkPoint* pts = fPts.begin();
        int lastVerb = 0;
        int lastPt = 0;
        uint8_t verb;
        bool edgeSplit = false;
        for (int ceptIdx = 0; ceptIdx < fIntercepts.count(); ++ceptIdx, pts += verb) {
            Intercepts& intercepts = fIntercepts[ceptIdx];
            verb = *verbs++;
            if (verb <= SkPath::kLine_Verb) {
                continue;
            }
            size_t tCount = intercepts.fTs.count();
            if (!tCount) {
                continue;
            }
            size_t tIndex = (size_t) -1;
            SkScalar y = pts[0].fY;
            int lastSplit = 0;
            int firstSplit = -1;
            bool curveSplit = false;
            while (++tIndex < tCount) {
                double nextT = intercepts.fTs[tIndex];
                SkScalar nextY = verb == SkPath::kQuad_Verb
                        ? QuadYAtT(pts, nextT) : CubicYAtT(pts, nextT);
                if (nextY < y) {
                    edgeSplit = curveSplit = true;
                    if (firstSplit < 0) {
                        firstSplit = tIndex;
                        int nextPt = pts - fPts.begin();
                        int nextVerb = verbs - 1 - fVerbs.begin();
                        if (lastVerb < nextVerb) {
                            addPartial(edges, lastPt, nextPt, lastVerb, nextVerb);
            #if DEBUG_SPLIT
                            SkDebugf("%s addPartial 1\n", __FUNCTION__);
            #endif
                        }
                        lastPt = nextPt;
                        lastVerb = nextVerb;
                    }
                } else {
                    if (firstSplit >= 0) {
                        if (lastSplit < firstSplit) {
                            addSplit(edges, pts, verb, intercepts,
                                    lastSplit, firstSplit, false);
            #if DEBUG_SPLIT
                            SkDebugf("%s addSplit 1 tIndex=%d,%d\n",
                                    __FUNCTION__, lastSplit, firstSplit);
            #endif
                        }
                        addSplit(edges, pts, verb, intercepts,
                                firstSplit, tIndex, true);
            #if DEBUG_SPLIT
                        SkDebugf("%s addSplit 2 tIndex=%d,%d flip\n",
                                __FUNCTION__, firstSplit, tIndex);
            #endif
                        lastSplit = tIndex;
                        firstSplit = -1;
                    }
                }
                y = nextY;
            }
            if (curveSplit) {
                if (firstSplit < 0) {
                    firstSplit = lastSplit;
                } else {
                    addSplit(edges, pts, verb, intercepts, lastSplit,
                            firstSplit, false);
            #if DEBUG_SPLIT
                    SkDebugf("%s addSplit 3 tIndex=%d,%d\n", __FUNCTION__,
                            lastSplit, firstSplit);
            #endif
                }
                addSplit(edges, pts, verb, intercepts, firstSplit,
                        tIndex, pts[verb].fY < y);
            #if DEBUG_SPLIT
                SkDebugf("%s addSplit 4 tIndex=%d,%d %s\n", __FUNCTION__,
                        firstSplit, tIndex, pts[verb].fY < y ? "flip" : "");
            #endif
            }
        }
        // collapse remainder -- if there's nothing left, clear it somehow?
        if (edgeSplit) {
            int nextVerb = verbs - 1 - fVerbs.begin();
            if (lastVerb < nextVerb) {
                int nextPt = pts - fPts.begin();
                addPartial(edges, lastPt, nextPt, lastVerb, nextVerb);
            #if DEBUG_SPLIT
                SkDebugf("%s addPartial 2\n", __FUNCTION__);
            #endif
            }
            // OPTIMIZATION: reuse the edge instead of marking it empty
            reset();
        }
    }

#if DEBUG_DUMP
    void dump() {
        int i;
        const char className[] = "InEdge";
        const int tab = 4;
        SkDebugf("InEdge %p (edge=%d)\n", this, fID);
        for (i = 0; i < fCached.count(); ++i) {
            SkDebugf("%*s.fCached[%d]=0x%08x\n", tab + sizeof(className),
                    className, i, fCached[i]);
        }
        uint8_t* verbs = fVerbs.begin();
        SkPoint* pts = fPts.begin();
        for (i = 0; i < fIntercepts.count(); ++i) {
            SkDebugf("%*s.fIntercepts[%d]:\n", tab + sizeof(className),
                    className, i);
            fIntercepts[i].dump(pts, (SkPath::Verb) *verbs);
            pts += *verbs++;
        }
        for (i = 0; i < fPts.count(); ++i) {
            SkDebugf("%*s.fPts[%d]=(%1.9g,%1.9g)\n", tab + sizeof(className),
                    className, i, fPts[i].fX, fPts[i].fY);
        }
        for (i = 0; i < fVerbs.count(); ++i) {
            SkDebugf("%*s.fVerbs[%d]=%d\n", tab + sizeof(className),
                    className, i, fVerbs[i]);
        }
        SkDebugf("%*s.fBounds=(%1.9g, %1.9g, %1.9g, %1.9g)\n", tab + sizeof(className),
                className, fBounds.fLeft, fBounds.fTop,
                fBounds.fRight, fBounds.fBottom);
        SkDebugf("%*s.fWinding=%d\n", tab + sizeof(className), className,
                fWinding);
        SkDebugf("%*s.fContainsIntercepts=%d\n", tab + sizeof(className),
                className, fContainsIntercepts);
        SkDebugf("%*s.fIntersected=%d\n", tab + sizeof(className),
                className, fIntersected);
    }
#endif

    // FIXME: temporary data : move this to a separate struct?
    SkTDArray<const InEdge*> fCached; // list of edges already intercepted
    SkTArray<Intercepts> fIntercepts; // one per verb

    // persistent data
    SkTDArray<SkPoint> fPts;
    SkTDArray<uint8_t> fVerbs;
    Bounds fBounds;
    int fID;
    signed char fWinding;
    bool fContainsIntercepts;
    bool fIntersected;
};

class InEdgeBuilder {
public:

InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges,
        SkTDArray<HorizontalEdge>& horizontalEdges)
    : fPath(path)
    , fCurrentEdge(NULL)
    , fEdges(edges)
    , fHorizontalEdges(horizontalEdges)
    , fIgnoreHorizontal(ignoreHorizontal)
    , fContainsCurves(false)
{
    walk();
}

bool containsCurves() const {
    return fContainsCurves;
}

protected:

void addEdge() {
    SkASSERT(fCurrentEdge);
    fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
    fPtOffset = 1;
    *fCurrentEdge->fVerbs.append() = fVerb;
}

bool complete() {
    if (fCurrentEdge && fCurrentEdge->fVerbs.count()) {
        fCurrentEdge->complete(fWinding);
        fCurrentEdge = NULL;
        return true;
    }
    return false;
}

int direction(SkPath::Verb verb) {
    fPtCount = verb + 1;
    if (fIgnoreHorizontal && isHorizontal()) {
        return 0;
    }
    return fPts[0].fY == fPts[verb].fY
            ? fPts[0].fX == fPts[verb].fX ? 0 : fPts[0].fX < fPts[verb].fX
            ? 1 : -1 : fPts[0].fY < fPts[verb].fY ? 1 : -1;
}

bool isHorizontal() {
    SkScalar y = fPts[0].fY;
    for (int i = 1; i < fPtCount; ++i) {
        if (fPts[i].fY != y) {
            return false;
        }
    }
    return true;
}

void startEdge() {
    if (!fCurrentEdge) {
        fCurrentEdge = fEdges.push_back_n(1);
    }
    fWinding = 0;
    fPtOffset = 0;
}

void walk() {
    SkPath::Iter iter(fPath, true);
    int winding = 0;
    while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
        switch (fVerb) {
            case SkPath::kMove_Verb:
                startEdge();
                continue;
            case SkPath::kLine_Verb:
                winding = direction(SkPath::kLine_Verb);
                break;
            case SkPath::kQuad_Verb:
                fVerb = QuadReduceOrder(fPts);
                winding = direction(fVerb);
                fContainsCurves |= fVerb == SkPath::kQuad_Verb;
                break;
            case SkPath::kCubic_Verb:
                fVerb = CubicReduceOrder(fPts);
                winding = direction(fVerb);
                fContainsCurves |= fVerb >= SkPath::kQuad_Verb;
                break;
            case SkPath::kClose_Verb:
                SkASSERT(fCurrentEdge);
                complete();
                continue;
            default:
                SkDEBUGFAIL("bad verb");
                return;
        }
        if (winding == 0) {
            HorizontalEdge* horizontalEdge = fHorizontalEdges.append();
            // FIXME: for degenerate quads and cubics, compute x extremes
            horizontalEdge->fLeft = fPts[0].fX;
            horizontalEdge->fRight = fPts[fVerb].fX;
            horizontalEdge->fY = fPts[0].fY;
            if (horizontalEdge->fLeft > horizontalEdge->fRight) {
                SkTSwap<SkScalar>(horizontalEdge->fLeft, horizontalEdge->fRight);
            }
            if (complete()) {
                startEdge();
            }
            continue;
        }
        if (fWinding + winding == 0) {
            // FIXME: if prior verb or this verb is a horizontal line, reverse
            // it instead of starting a new edge
            SkASSERT(fCurrentEdge);
            if (complete()) {
                startEdge();
            }
        }
        fWinding = winding;
        addEdge();
    }
    if (!complete()) {
        if (fCurrentEdge) {
            fEdges.pop_back();
        }
    }
}

private:
    const SkPath& fPath;
    InEdge* fCurrentEdge;
    SkTArray<InEdge>& fEdges;
    SkTDArray<HorizontalEdge>& fHorizontalEdges;
    SkPoint fPts[4];
    SkPath::Verb fVerb;
    int fPtCount;
    int fPtOffset;
    int8_t fWinding;
    bool fIgnoreHorizontal;
    bool fContainsCurves;
};

struct WorkEdge {
    SkScalar bottom() const {
        return fPts[verb()].fY;
    }

    void init(const InEdge* edge) {
        fEdge = edge;
        fPts = edge->fPts.begin();
        fVerb = edge->fVerbs.begin();
    }

    bool advance() {
        SkASSERT(fVerb < fEdge->fVerbs.end());
        fPts += *fVerb++;
        return fVerb != fEdge->fVerbs.end();
    }

    const SkPoint* lastPoints() const {
        SkASSERT(fPts >= fEdge->fPts.begin() + lastVerb());
        return &fPts[-lastVerb()];
    }

    SkPath::Verb lastVerb() const {
        SkASSERT(fVerb > fEdge->fVerbs.begin());
        return (SkPath::Verb) fVerb[-1];
    }

    const SkPoint* points() const {
        return fPts;
    }

    SkPath::Verb verb() const {
        return (SkPath::Verb) *fVerb;
    }

    ptrdiff_t verbIndex() const {
        return fVerb - fEdge->fVerbs.begin();
    }

    int winding() const {
        return fEdge->fWinding;
    }

    const InEdge* fEdge;
    const SkPoint* fPts;
    const uint8_t* fVerb;
};

// always constructed with SkTDArray because new edges are inserted
// this may be a inappropriate optimization, suggesting that a separate array of
// ActiveEdge* may be faster to insert and search

// OPTIMIZATION: Brian suggests that global sorting should be unnecessary, since
// as active edges are introduced, only local sorting should be required
class ActiveEdge {
public:
    // this logic must be kept in sync with tooCloseToCall
    // callers expect this to only read fAbove, fTangent
    bool operator<(const ActiveEdge& rh) const {
        if (fVerb == rh.fVerb) {
            // FIXME: don't know what to do if verb is quad, cubic
            return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow);
        }
        // figure out which is quad, line
        // if cached data says line did not intersect quad, use top/bottom
        if (fVerb != SkPath::kLine_Verb ? noIntersect(rh) : rh.noIntersect(*this)) {
            return abCompare(fAbove, fBelow, rh.fAbove, rh.fBelow);
        }
        // use whichever of top/tangent tangent/bottom overlaps more
        // with line top/bot
        // assumes quad/cubic can already be upconverted to cubic/cubic
        const SkPoint* line[2];
        const SkPoint* curve[4];
        if (fVerb != SkPath::kLine_Verb) {
            line[0] = &rh.fAbove;
            line[1] = &rh.fBelow;
            curve[0] = &fAbove;
            curve[1] = &fTangent;
            curve[2] = &fBelow;
        } else {
            line[0] = &fAbove;
            line[1] = &fBelow;
            curve[0] = &rh.fAbove;
            curve[1] = &rh.fTangent;
            curve[2] = &rh.fBelow;
        }
        // FIXME: code has been abandoned, incomplete....
        return false;
    }

    bool abCompare(const SkPoint& a1, const SkPoint& a2, const SkPoint& b1,
            const SkPoint& b2) const {
        double topD = a1.fX - b1.fX;
        if (b1.fY < a1.fY) {
            topD = (b2.fY - b1.fY) * topD - (a1.fY - b1.fY) * (b2.fX - b1.fX);
        } else if (b1.fY > a1.fY) {
            topD = (a2.fY - a1.fY) * topD + (b1.fY - a1.fY) * (a2.fX - a1.fX);
        }
        double botD = a2.fX - b2.fX;
        if (b2.fY > a2.fY) {
            botD = (b2.fY - b1.fY) * botD - (a2.fY - b2.fY) * (b2.fX - b1.fX);
        } else if (b2.fY < a2.fY) {
            botD = (a2.fY - a1.fY) * botD + (b2.fY - a2.fY) * (a2.fX - a1.fX);
        }
        // return sign of greater absolute value
        return (fabs(topD) > fabs(botD) ? topD : botD) < 0;
    }

    // If a pair of edges are nearly coincident for some span, add a T in the
    // edge so it can be shortened to match the other edge. Note that another
    // approach is to trim the edge after it is added to the OutBuilder list --
    // FIXME: since this has no effect if the edge is already done (i.e.,
    // fYBottom >= y) maybe this can only be done by calling trimLine later.
    void addTatYBelow(SkScalar y) {
        if (fBelow.fY <= y || fYBottom >= y) {
            return;
        }
        addTatYInner(y);
        fFixBelow = true;
    }

    void addTatYAbove(SkScalar y) {
        if (fBelow.fY <= y) {
            return;
        }
        addTatYInner(y);
    }

    void addTatYInner(SkScalar y) {
        if (fWorkEdge.fPts[0].fY > y) {
            backup(y);
        }
        SkScalar left = fWorkEdge.fPts[0].fX;
        SkScalar right = fWorkEdge.fPts[1].fX;
        if (left > right) {
            SkTSwap(left, right);
        }
        double ts[2];
        SkASSERT(fWorkEdge.fVerb[0] == SkPath::kLine_Verb);
        int pts = LineIntersect(fWorkEdge.fPts, left, right, y, ts);
        SkASSERT(pts == 1);
        // An ActiveEdge or WorkEdge has no need to modify the T values computed
        // in the InEdge, except in the following case. If a pair of edges are
        // nearly coincident, this may not be detected when the edges are
        // intersected. Later, when sorted, and this near-coincidence is found,
        // an additional t value must be added, requiring the cast below.
        InEdge* writable = const_cast<InEdge*>(fWorkEdge.fEdge);
        int insertedAt = writable->add(ts, pts, fWorkEdge.verbIndex());
    #if DEBUG_ADJUST_COINCIDENT
        SkDebugf("%s edge=%d y=%1.9g t=%1.9g\n", __FUNCTION__, ID(), y, ts[0]);
    #endif
        if (insertedAt >= 0) {
            if (insertedAt + 1 < fTIndex) {
                SkASSERT(insertedAt + 2 == fTIndex);
                --fTIndex;
            }
        }
    }

    bool advanceT() {
        SkASSERT(fTIndex <= fTs->count() - fExplicitTs);
        return ++fTIndex <= fTs->count() - fExplicitTs;
    }

    bool advance() {
    // FIXME: flip sense of next
        bool result = fWorkEdge.advance();
        fDone = !result;
        initT();
        return result;
    }

    void backup(SkScalar y) {
        do {
            SkASSERT(fWorkEdge.fEdge->fVerbs.begin() < fWorkEdge.fVerb);
            fWorkEdge.fPts -= *--fWorkEdge.fVerb;
            SkASSERT(fWorkEdge.fEdge->fPts.begin() <= fWorkEdge.fPts);
        } while (fWorkEdge.fPts[0].fY >= y);
        initT();
        SkASSERT(!fExplicitTs);
        fTIndex = fTs->count() + 1;
    }

    void calcAboveBelow(double tAbove, double tBelow) {
        fVerb = fWorkEdge.verb();
        switch (fVerb) {
            case SkPath::kLine_Verb:
                LineXYAtT(fWorkEdge.fPts, tAbove, &fAbove);
                LineXYAtT(fWorkEdge.fPts, tBelow, &fTangent);
                fBelow = fTangent;
                break;
            case SkPath::kQuad_Verb:
                // FIXME: put array in struct to avoid copy?
                SkPoint quad[3];
                QuadSubDivide(fWorkEdge.fPts, tAbove, tBelow, quad);
                fAbove = quad[0];
                fTangent = quad[0] != quad[1] ? quad[1] : quad[2];
                fBelow = quad[2];
                break;
            case SkPath::kCubic_Verb:
                SkPoint cubic[3];
                CubicSubDivide(fWorkEdge.fPts, tAbove, tBelow, cubic);
                fAbove = cubic[0];
                // FIXME: can't see how quad logic for how tangent is used
                // extends to cubic
                fTangent = cubic[0] != cubic[1] ? cubic[1]
                        : cubic[0] != cubic[2] ? cubic[2] : cubic[3];
                fBelow = cubic[3];
                break;
            default:
                SkASSERT(0);
        }
    }

    void calcLeft(SkScalar y) {
        // OPTIMIZE: put a kDone_Verb at the end of the verb list?
        if (fDone || fBelow.fY > y) {
            return; // nothing to do; use last
        }
        calcLeft();
        if (fAbove.fY == fBelow.fY) {
            SkDebugf("%s edge=%d fAbove.fY != fBelow.fY %1.9g\n", __FUNCTION__,
                    ID(), fAbove.fY);
        }
    }

    void calcLeft() {
        int add = (fTIndex <= fTs->count() - fExplicitTs) - 1;
        double tAbove = t(fTIndex + add);
        double tBelow = t(fTIndex - ~add);
        // OPTIMIZATION: if fAbove, fBelow have already been computed
        //  for the fTIndex, don't do it again
        calcAboveBelow(tAbove, tBelow);
        // For identical x, this lets us know which edge is first.
        // If both edges have T values < 1, check x at next T (fBelow).
        SkASSERT(tAbove != tBelow);
        // FIXME: this can loop forever
        // need a break if we hit the end
        // FIXME: in unit test, figure out how explicit Ts work as well
        while (fAbove.fY == fBelow.fY) {
            if (add < 0 || fTIndex == fTs->count()) {
                add -= 1;
                SkASSERT(fTIndex + add >= 0);
                tAbove = t(fTIndex + add);
            } else {
                add += 1;
                SkASSERT(fTIndex - ~add <= fTs->count() + 1);
                tBelow = t(fTIndex - ~add);
            }
            calcAboveBelow(tAbove, tBelow);
        }
        fTAbove = tAbove;
        fTBelow = tBelow;
    }

    bool done(SkScalar bottom) const {
        return fDone || fYBottom >= bottom;
    }

    void fixBelow() {
        if (fFixBelow) {
            fTBelow = nextT();
            calcAboveBelow(fTAbove, fTBelow);
            fFixBelow = false;
        }
    }

    void init(const InEdge* edge) {
        fWorkEdge.init(edge);
        fDone = false;
        initT();
        fBelow.fY = SK_ScalarMin;
        fYBottom = SK_ScalarMin;
    }

    void initT() {
        const Intercepts& intercepts = fWorkEdge.fEdge->fIntercepts.front();
        SkASSERT(fWorkEdge.verbIndex() <= fWorkEdge.fEdge->fIntercepts.count());
        const Intercepts* interceptPtr = &intercepts + fWorkEdge.verbIndex();
        fTs = &interceptPtr->fTs;
        fExplicitTs = interceptPtr->fExplicit;
  //  the above is conceptually the same as
  //    fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
  //  but templated arrays don't allow returning a pointer to the end() element
        fTIndex = 0;
        if (!fDone) {
            fVerb = fWorkEdge.verb();
        }
        SkASSERT(fVerb > SkPath::kMove_Verb);
    }

    // OPTIMIZATION: record if two edges are coincident when the are intersected
    // It's unclear how to do this -- seems more complicated than recording the
    // t values, since the same t values could exist intersecting non-coincident
    // edges.
    bool isCoincidentWith(const ActiveEdge* edge) const {
        if (fAbove != edge->fAbove || fBelow != edge->fBelow) {
            return false;
        }
        if (fVerb != edge->fVerb) {
            return false;
        }
        switch (fVerb) {
            case SkPath::kLine_Verb:
                return true;
            default:
                // FIXME: add support for quads, cubics
                SkASSERT(0);
                return false;
        }
        return false;
    }

    bool isUnordered(const ActiveEdge* edge) const {
        return fAbove == edge->fAbove && fBelow == edge->fBelow;
    }

//    SkPath::Verb lastVerb() const {
//        return fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb();
//    }

    const SkPoint* lastPoints() const {
        return fDone ? fWorkEdge.lastPoints() : fWorkEdge.points();
    }

    bool noIntersect(const ActiveEdge& ) const {
        // incomplete
        return false;
    }

    // The shortest close call edge should be moved into a position where
    // it contributes if the winding is transitioning to or from zero.
    bool swapClose(const ActiveEdge* next, int prev, int wind, int mask) const {
#if DEBUG_ADJUST_COINCIDENT
        SkDebugf("%s edge=%d (%g) next=%d (%g) prev=%d wind=%d nextWind=%d\n",
                __FUNCTION__, ID(), fBelow.fY, next->ID(), next->fBelow.fY,
                prev, wind, wind + next->fWorkEdge.winding());
#endif
        if ((prev & mask) == 0 || (wind & mask) == 0) {
            return next->fBelow.fY < fBelow.fY;
        }
        int nextWinding = wind + next->fWorkEdge.winding();
        if ((nextWinding & mask) == 0) {
            return fBelow.fY < next->fBelow.fY;
        }
        return false;
    }

    bool swapCoincident(const ActiveEdge* edge, SkScalar bottom) const {
        if (fBelow.fY >= bottom || fDone || edge->fDone) {
            return false;
        }
        ActiveEdge thisWork = *this;
        ActiveEdge edgeWork = *edge;
        while ((thisWork.advanceT() || thisWork.advance())
                && (edgeWork.advanceT() || edgeWork.advance())) {
            thisWork.calcLeft();
            edgeWork.calcLeft();
            if (thisWork < edgeWork) {
                return false;
            }
            if (edgeWork < thisWork) {
                return true;
            }
        }
        return false;
    }

    bool swapUnordered(const ActiveEdge* edge, SkScalar /* bottom */) const {
        SkASSERT(fVerb != SkPath::kLine_Verb
                || edge->fVerb != SkPath::kLine_Verb);
        if (fDone || edge->fDone) {
            return false;
        }
        ActiveEdge thisWork, edgeWork;
        extractAboveBelow(thisWork);
        edge->extractAboveBelow(edgeWork);
        return edgeWork < thisWork;
    }

    bool tooCloseToCall(const ActiveEdge* edge) const {
        int ulps;
        double t1, t2, b1, b2;
        // This logic must be kept in sync with operator <
        if (edge->fAbove.fY < fAbove.fY) {
            t1 = (edge->fTangent.fY - edge->fAbove.fY) * (fAbove.fX - edge->fAbove.fX);
            t2 = (fAbove.fY - edge->fAbove.fY) * (edge->fTangent.fX - edge->fAbove.fX);
        } else if (edge->fAbove.fY > fAbove.fY) {
            t1 = (fTangent.fY - fAbove.fY) * (fAbove.fX - edge->fAbove.fX);
            t2 = (fAbove.fY - edge->fAbove.fY) * (fTangent.fX - fAbove.fX);
        } else {
            t1 = fAbove.fX;
            t2 = edge->fAbove.fX;
        }
        if (edge->fTangent.fY > fTangent.fY) {
            b1 = (edge->fTangent.fY - edge->fAbove.fY) * (fTangent.fX - edge->fTangent.fX);
            b2 = (fTangent.fY - edge->fTangent.fY) * (edge->fTangent.fX - edge->fAbove.fX);
        } else if (edge->fTangent.fY < fTangent.fY) {
            b1 = (fTangent.fY - fAbove.fY) * (fTangent.fX - edge->fTangent.fX);
            b2 = (fTangent.fY - edge->fTangent.fY) * (fTangent.fX - fAbove.fX);
        } else {
            b1 = fTangent.fX;
            b2 = edge->fTangent.fX;
        }
        if (fabs(t1 - t2) > fabs(b1 - b2)) {
            ulps = UlpsDiff((float) t1, (float) t2);
        } else {
            ulps = UlpsDiff((float) b1, (float) b2);
        }
#if DEBUG_ADJUST_COINCIDENT
        SkDebugf("%s this=%d edge=%d ulps=%d\n", __FUNCTION__, ID(), edge->ID(),
                ulps);
#endif
        if (ulps < 0 || ulps > 32) {
            return false;
        }
        if (fVerb == SkPath::kLine_Verb && edge->fVerb == SkPath::kLine_Verb) {
            return true;
        }
        if (fVerb != SkPath::kLine_Verb && edge->fVerb != SkPath::kLine_Verb) {
            return false;
        }

        double ts[2];
        bool isLine = true;
        bool curveQuad = true;
        if (fVerb == SkPath::kCubic_Verb) {
            ts[0] = (fTAbove * 2 + fTBelow) / 3;
            ts[1] = (fTAbove + fTBelow * 2) / 3;
            curveQuad = isLine = false;
        } else if (edge->fVerb == SkPath::kCubic_Verb) {
            ts[0] = (edge->fTAbove * 2 + edge->fTBelow) / 3;
            ts[1] = (edge->fTAbove + edge->fTBelow * 2) / 3;
            curveQuad = false;
        } else if (fVerb == SkPath::kQuad_Verb) {
                ts[0] = fTAbove;
                ts[1] = (fTAbove + fTBelow) / 2;
                isLine = false;
        } else {
            SkASSERT(edge->fVerb == SkPath::kQuad_Verb);
            ts[0] = edge->fTAbove;
            ts[1] = (edge->fTAbove + edge->fTBelow) / 2;
        }
        const SkPoint* curvePts = isLine ? edge->lastPoints() : lastPoints();
        const ActiveEdge* lineEdge = isLine ? this : edge;
        SkPoint curveSample[2];
        for (int index = 0; index < 2; ++index) {
            if (curveQuad) {
                QuadXYAtT(curvePts, ts[index], &curveSample[index]);
            } else {
                CubicXYAtT(curvePts, ts[index], &curveSample[index]);
            }
        }
        return IsCoincident(curveSample, lineEdge->fAbove, lineEdge->fBelow);
    }

    double nextT() const {
        SkASSERT(fTIndex <= fTs->count() - fExplicitTs);
        return t(fTIndex + 1);
    }

    double t() const {
        return t(fTIndex);
    }

    double t(int tIndex) const {
        if (fExplicitTs) {
            SkASSERT(tIndex < fTs->count());
            return (*fTs)[tIndex];
        }
        if (tIndex == 0) {
            return 0;
        }
        if (tIndex > fTs->count()) {
            return 1;
        }
        return (*fTs)[tIndex - 1];
    }

    // FIXME: debugging only
    int ID() const {
        return fWorkEdge.fEdge->fID;
    }

private:
    // utility used only by swapUnordered
    void extractAboveBelow(ActiveEdge& extracted) const {
        SkPoint curve[4];
        switch (fVerb) {
            case SkPath::kLine_Verb:
                extracted.fAbove = fAbove;
                extracted.fTangent = fTangent;
                return;
            case SkPath::kQuad_Verb:
                QuadSubDivide(lastPoints(), fTAbove, fTBelow, curve);
                break;
            case SkPath::kCubic_Verb:
                CubicSubDivide(lastPoints(), fTAbove, fTBelow, curve);
                break;
            default:
                SkASSERT(0);
        }
        extracted.fAbove = curve[0];
        extracted.fTangent = curve[1];
    }

public:
    WorkEdge fWorkEdge;
    const SkTDArray<double>* fTs;
    SkPoint fAbove;
    SkPoint fTangent;
    SkPoint fBelow;
    double fTAbove; // OPTIMIZATION: only required if edge has quads or cubics
    double fTBelow;
    SkScalar fYBottom;
    int fCoincident;
    int fTIndex;
    SkPath::Verb fVerb;
    bool fSkip; // OPTIMIZATION: use bitfields?
    bool fCloseCall;
    bool fDone;
    bool fFixBelow;
    bool fExplicitTs;
};

static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
    size_t count = activeEdges.count();
    for (size_t index = 0; index < count; ++index) {
        if (edge == activeEdges[index].fWorkEdge.fEdge) {
            return;
        }
    }
    ActiveEdge* active = activeEdges.append();
    active->init(edge);
}

// Find any intersections in the range of active edges. A pair of edges, on
// either side of another edge, may change the winding contribution for part of
// the edge.
// Keep horizontal edges just for
// the purpose of computing when edges change their winding contribution, since
// this is essentially computing the horizontal intersection.
static void addBottomT(InEdge** currentPtr, InEdge** lastPtr,
        HorizontalEdge** horizontal) {
    InEdge** testPtr = currentPtr - 1;
    HorizontalEdge* horzEdge = *horizontal;
    SkScalar left = horzEdge->fLeft;
    SkScalar bottom = horzEdge->fY;
    while (++testPtr != lastPtr) {
        InEdge* test = *testPtr;
        if (test->fBounds.fBottom <= bottom || test->fBounds.fRight <= left) {
            continue;
        }
        WorkEdge wt;
        wt.init(test);
        do {
            HorizontalEdge** sorted = horizontal;
            horzEdge = *sorted;
            do {
                double wtTs[4];
                int pts;
                uint8_t verb = wt.verb();
                switch (verb) {
                    case SkPath::kLine_Verb:
                        pts = LineIntersect(wt.fPts, horzEdge->fLeft,
                                horzEdge->fRight, horzEdge->fY, wtTs);
                        break;
                    case SkPath::kQuad_Verb:
                        pts = QuadIntersect(wt.fPts, horzEdge->fLeft,
                                horzEdge->fRight, horzEdge->fY, wtTs);
                        break;
                    case SkPath::kCubic_Verb:
                        pts = CubicIntersect(wt.fPts, horzEdge->fLeft,
                                horzEdge->fRight, horzEdge->fY, wtTs);
                        break;
                }
                if (pts) {
#if DEBUG_ADD_BOTTOM_TS
                    for (int x = 0; x < pts; ++x) {
                        SkDebugf("%s y=%g wtTs[0]=%g (%g,%g", __FUNCTION__,
                                horzEdge->fY, wtTs[x], wt.fPts[0].fX, wt.fPts[0].fY);
                        for (int y = 0; y < verb; ++y) {
                            SkDebugf(" %g,%g", wt.fPts[y + 1].fX, wt.fPts[y + 1].fY));
                        }
                        SkDebugf(")\n");
                    }
                    if (pts > verb) {
                        SkASSERT(0); // FIXME ? should this work?
                        SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]);
                    }
#endif
                    test->add(wtTs, pts, wt.verbIndex());
                }
                horzEdge = *++sorted;
            } while (horzEdge->fY == bottom
                    && horzEdge->fLeft <= test->fBounds.fRight);
        } while (wt.advance());
    }
}

#if DEBUG_ADD_INTERSECTING_TS
static void debugShowLineIntersection(int pts, const WorkEdge& wt,
        const WorkEdge& wn, const double wtTs[2], const double wnTs[2]) {
    if (!pts) {
        return;
    }
    SkPoint wtOutPt, wnOutPt;
    LineXYAtT(wt.fPts, wtTs[0], &wtOutPt);
    LineXYAtT(wn.fPts, wnTs[0], &wnOutPt);
    SkDebugf("%s wtTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
            __FUNCTION__,
            wtTs[0], wt.fPts[0].fX, wt.fPts[0].fY,
            wt.fPts[1].fX, wt.fPts[1].fY, wtOutPt.fX, wtOutPt.fY);
    if (pts == 2) {
        SkDebugf("%s wtTs[1]=%g\n", __FUNCTION__, wtTs[1]);
    }
    SkDebugf("%s wnTs[0]=%g (%g,%g, %g,%g) (%g,%g)\n",
            __FUNCTION__,
            wnTs[0], wn.fPts[0].fX, wn.fPts[0].fY,
            wn.fPts[1].fX, wn.fPts[1].fY, wnOutPt.fX, wnOutPt.fY);
    if (pts == 2) {
        SkDebugf("%s wnTs[1]=%g\n", __FUNCTION__, wnTs[1]);
    }
}
#else
static void debugShowLineIntersection(int , const WorkEdge& ,
        const WorkEdge& , const double [2], const double [2]) {
}
#endif

static void addIntersectingTs(InEdge** currentPtr, InEdge** lastPtr) {
    InEdge** testPtr = currentPtr - 1;
    // FIXME: lastPtr should be past the point of interest, so
    // test below should be  lastPtr - 2
    // that breaks testSimplifyTriangle22, so further investigation is needed
    while (++testPtr != lastPtr - 1) {
        InEdge* test = *testPtr;
        InEdge** nextPtr = testPtr;
        do {
            InEdge* next = *++nextPtr;
            // FIXME: this compares against the sentinel sometimes
            // OPTIMIZATION: this may never be needed since this gets called
            // in two passes now. Verify that double hits are appropriate.
            if (test->cached(next)) {
                continue;
            }
            if (!Bounds::Intersects(test->fBounds, next->fBounds)) {
                continue;
            }
            WorkEdge wt, wn;
            wt.init(test);
            wn.init(next);
            do {
                int pts;
                Intersections ts;
                bool swap = false;
                switch (wt.verb()) {
                    case SkPath::kLine_Verb:
                        switch (wn.verb()) {
                            case SkPath::kLine_Verb: {
                                pts = LineIntersect(wt.fPts, wn.fPts, ts);
                                debugShowLineIntersection(pts, wt, wn,
                                        ts.fT[0], ts.fT[1]);
                                break;
                            }
                            case SkPath::kQuad_Verb: {
                                swap = true;
                                pts = QuadLineIntersect(wn.fPts, wt.fPts, ts);
                                break;
                            }
                            case SkPath::kCubic_Verb: {
                                swap = true;
                                pts = CubicLineIntersect(wn.fPts, wt.fPts, ts);
                                break;
                            }
                            default:
                                SkASSERT(0);
                        }
                        break;
                    case SkPath::kQuad_Verb:
                        switch (wn.verb()) {
                            case SkPath::kLine_Verb: {
                                pts = QuadLineIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            case SkPath::kQuad_Verb: {
                                pts = QuadIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            case SkPath::kCubic_Verb: {
                                // FIXME: promote quad to cubic
                                pts = CubicIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            default:
                                SkASSERT(0);
                        }
                        break;
                    case SkPath::kCubic_Verb:
                        switch (wn.verb()) {
                            case SkPath::kLine_Verb: {
                                pts = CubicLineIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            case SkPath::kQuad_Verb: {
                                 // FIXME: promote quad to cubic
                                pts = CubicIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            case SkPath::kCubic_Verb: {
                                pts = CubicIntersect(wt.fPts, wn.fPts, ts);
                                break;
                            }
                            default:
                                SkASSERT(0);
                        }
                        break;
                    default:
                        SkASSERT(0);
                }
                test->add(ts.fT[swap], pts, wt.verbIndex());
                next->add(ts.fT[!swap], pts, wn.verbIndex());
            } while (wt.bottom() <= wn.bottom() ? wt.advance() : wn.advance());
        } while (nextPtr != lastPtr);
    }
}

static InEdge** advanceEdges(SkTDArray<ActiveEdge>* activeEdges,
        InEdge** currentPtr, InEdge** lastPtr,  SkScalar y) {
    InEdge** testPtr = currentPtr - 1;
    while (++testPtr != lastPtr) {
        if ((*testPtr)->fBounds.fBottom > y) {
            continue;
        }
        if (activeEdges) {
            InEdge* test = *testPtr;
            ActiveEdge* activePtr = activeEdges->begin() - 1;
            ActiveEdge* lastActive = activeEdges->end();
            while (++activePtr != lastActive) {
                if (activePtr->fWorkEdge.fEdge == test) {
                    activeEdges->remove(activePtr - activeEdges->begin());
                    break;
                }
            }
        }
        if (testPtr == currentPtr) {
            ++currentPtr;
        }
    }
    return currentPtr;
}

// OPTIMIZE: inline?
static HorizontalEdge** advanceHorizontal(HorizontalEdge** edge, SkScalar y) {
    while ((*edge)->fY < y) {
        ++edge;
    }
    return edge;
}

// compute bottom taking into account any intersected edges
static SkScalar computeInterceptBottom(SkTDArray<ActiveEdge>& activeEdges,
        SkScalar y, SkScalar bottom) {
    ActiveEdge* activePtr = activeEdges.begin() - 1;
    ActiveEdge* lastActive = activeEdges.end();
    while (++activePtr != lastActive) {
        const InEdge* test = activePtr->fWorkEdge.fEdge;
        if (!test->fContainsIntercepts) {
            continue;
        }
        WorkEdge wt;
        wt.init(test);
        do {
            const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
            if (intercepts.fTopIntercepts > 1) {
                SkScalar yTop = wt.fPts[0].fY;
                if (yTop > y && bottom > yTop) {
                    bottom = yTop;
                }
            }
            if (intercepts.fBottomIntercepts > 1) {
                SkScalar yBottom = wt.fPts[wt.verb()].fY;
                if (yBottom > y && bottom > yBottom) {
                    bottom = yBottom;
                }
            }
            const SkTDArray<double>& fTs = intercepts.fTs;
            size_t count = fTs.count();
            for (size_t index = 0; index < count; ++index) {
                SkScalar yIntercept;
                switch (wt.verb()) {
                    case SkPath::kLine_Verb: {
                        yIntercept = LineYAtT(wt.fPts, fTs[index]);
                        break;
                    }
                    case SkPath::kQuad_Verb: {
                        yIntercept = QuadYAtT(wt.fPts, fTs[index]);
                        break;
                    }
                    case SkPath::kCubic_Verb: {
                        yIntercept = CubicYAtT(wt.fPts, fTs[index]);
                        break;
                    }
                    default:
                        SkASSERT(0); // should never get here
                }
                if (yIntercept > y && bottom > yIntercept) {
                    bottom = yIntercept;
                }
            }
        } while (wt.advance());
    }
#if DEBUG_BOTTOM
    SkDebugf("%s bottom=%1.9g\n", __FUNCTION__, bottom);
#endif
    return bottom;
}

static SkScalar findBottom(InEdge** currentPtr,
        InEdge** edgeListEnd, SkTDArray<ActiveEdge>* activeEdges, SkScalar y,
        bool /*asFill*/, InEdge**& testPtr) {
    InEdge* current = *currentPtr;
    SkScalar bottom = current->fBounds.fBottom;

    // find the list of edges that cross y
    InEdge* test = *testPtr;
    while (testPtr != edgeListEnd) {
        SkScalar testTop = test->fBounds.fTop;
        if (bottom <= testTop) {
            break;
        }
        SkScalar testBottom = test->fBounds.fBottom;
        // OPTIMIZATION: Shortening the bottom is only interesting when filling
        // and when the edge is to the left of a longer edge. If it's a framing
        // edge, or part of the right, it won't effect the longer edges.
        if (testTop > y) {
            bottom = testTop;
            break;
        }
        if (y < testBottom) {
            if (bottom > testBottom) {
                bottom = testBottom;
            }
            if (activeEdges) {
                addToActive(*activeEdges, test);
            }
        }
        test = *++testPtr;
    }
#if DEBUG_BOTTOM
    SkDebugf("%s %d bottom=%1.9g\n", __FUNCTION__, activeEdges ? 2 : 1, bottom);
#endif
    return bottom;
}

static void makeEdgeList(SkTArray<InEdge>& edges, InEdge& edgeSentinel,
        SkTDArray<InEdge*>& edgeList) {
    size_t edgeCount = edges.count();
    if (edgeCount == 0) {
        return;
    }
    int id = 0;
    for (size_t index = 0; index < edgeCount; ++index) {
        InEdge& edge = edges[index];
        if (!edge.fWinding) {
            continue;
        }
        edge.fID = ++id;
        *edgeList.append() = &edge;
    }
    *edgeList.append() = &edgeSentinel;
    QSort<InEdge>(edgeList.begin(), edgeList.end() - 1);
}

static void makeHorizontalList(SkTDArray<HorizontalEdge>& edges,
        HorizontalEdge& edgeSentinel, SkTDArray<HorizontalEdge*>& edgeList) {
    size_t edgeCount = edges.count();
    if (edgeCount == 0) {
        return;
    }
    for (size_t index = 0; index < edgeCount; ++index) {
        *edgeList.append() = &edges[index];
    }
    edgeSentinel.fLeft = edgeSentinel.fRight = edgeSentinel.fY = SK_ScalarMax;
    *edgeList.append() = &edgeSentinel;
    QSort<HorizontalEdge>(edgeList.begin(), edgeList.end() - 1);
}

static void skipCoincidence(int lastWinding, int winding, int windingMask,
            ActiveEdge* activePtr, ActiveEdge* firstCoincident) {
    if (((lastWinding & windingMask) == 0) ^ ((winding & windingMask) != 0)) {
        return;
    }
    // FIXME: ? shouldn't this be if (lastWinding & windingMask) ?
    if (lastWinding) {
#if DEBUG_ADJUST_COINCIDENT
        SkDebugf("%s edge=%d 1 set skip=false\n", __FUNCTION__, activePtr->ID());
#endif
        activePtr->fSkip = false;
    } else {
#if DEBUG_ADJUST_COINCIDENT
        SkDebugf("%s edge=%d 2 set skip=false\n", __FUNCTION__, firstCoincident->ID());
#endif
        firstCoincident->fSkip = false;
    }
}

static void sortHorizontal(SkTDArray<ActiveEdge>& activeEdges,
        SkTDArray<ActiveEdge*>& edgeList, SkScalar y) {
    size_t edgeCount = activeEdges.count();
    if (edgeCount == 0) {
        return;
    }
#if DEBUG_SORT_HORIZONTAL
    const int tab = 3; // FIXME: debugging only
    SkDebugf("%s y=%1.9g\n", __FUNCTION__, y);
#endif
    size_t index;
    for (index = 0; index < edgeCount; ++index) {
        ActiveEdge& activeEdge = activeEdges[index];
        do {
            activeEdge.calcLeft(y);
            // skip segments that don't span y
            if (activeEdge.fAbove != activeEdge.fBelow) {
                break;
            }
            if (activeEdge.fDone) {
#if DEBUG_SORT_HORIZONTAL
                SkDebugf("%*s edge=%d done\n", tab, "", activeEdge.ID());
#endif
                goto nextEdge;
            }
#if DEBUG_SORT_HORIZONTAL
            SkDebugf("%*s edge=%d above==below\n", tab, "", activeEdge.ID());
#endif
        } while (activeEdge.advanceT() || activeEdge.advance());
#if DEBUG_SORT_HORIZONTAL
        SkDebugf("%*s edge=%d above=(%1.9g,%1.9g) (%1.9g) below=(%1.9g,%1.9g)"
                " (%1.9g)\n", tab, "", activeEdge.ID(),
                activeEdge.fAbove.fX, activeEdge.fAbove.fY, activeEdge.fTAbove,
                activeEdge.fBelow.fX, activeEdge.fBelow.fY, activeEdge.fTBelow);
#endif
        activeEdge.fSkip = activeEdge.fCloseCall = activeEdge.fFixBelow = false;
        *edgeList.append() = &activeEdge;
nextEdge:
        ;
    }
    QSort<ActiveEdge>(edgeList.begin(), edgeList.end() - 1);
}

// remove coincident edges
// OPTIMIZE: remove edges? This is tricky because the current logic expects
// the winding count to be maintained while skipping coincident edges. In
// addition to removing the coincident edges, the remaining edges would need
// to have a different winding value, possibly different per intercept span.
static SkScalar adjustCoincident(SkTDArray<ActiveEdge*>& edgeList,
        int windingMask, SkScalar y, SkScalar bottom, OutEdgeBuilder& outBuilder)
{
#if DEBUG_ADJUST_COINCIDENT
    SkDebugf("%s y=%1.9g bottom=%1.9g\n", __FUNCTION__, y, bottom);
#endif
    size_t edgeCount = edgeList.count();
    if (edgeCount == 0) {
        return bottom;
    }
    ActiveEdge* activePtr, * nextPtr = edgeList[0];
    size_t index;
    bool foundCoincident = false;
    size_t firstIndex = 0;
    for (index = 1; index < edgeCount; ++index) {
        activePtr = nextPtr;
        nextPtr = edgeList[index];
        if (firstIndex != index - 1 && activePtr->fVerb > SkPath::kLine_Verb
                && nextPtr->fVerb == SkPath::kLine_Verb
                && activePtr->isUnordered(nextPtr)) {
            // swap the line with the curve
            // back up to the previous edge and retest
            SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
            SkASSERT(index > 1);
            index -= 2;
            nextPtr = edgeList[index];
            continue;
        }
        bool closeCall = false;
        activePtr->fCoincident = firstIndex;
        if (activePtr->isCoincidentWith(nextPtr)
                || (closeCall = activePtr->tooCloseToCall(nextPtr))) {
            activePtr->fSkip = nextPtr->fSkip = foundCoincident = true;
            activePtr->fCloseCall = nextPtr->fCloseCall = closeCall;
        } else if (activePtr->isUnordered(nextPtr)) {
            foundCoincident = true;
        } else {
            firstIndex = index;
        }
    }
    nextPtr->fCoincident = firstIndex;
    if (!foundCoincident) {
        return bottom;
    }
    int winding = 0;
    nextPtr = edgeList[0];
    for (index = 1; index < edgeCount; ++index) {
        int priorWinding = winding;
        winding += activePtr->fWorkEdge.winding();
        activePtr = nextPtr;
        nextPtr = edgeList[index];
        SkASSERT(activePtr == edgeList[index - 1]);
        SkASSERT(nextPtr == edgeList[index]);
        if (activePtr->fCoincident != nextPtr->fCoincident) {
            continue;
        }
        // the coincident edges may not have been sorted above -- advance
        // the edges and resort if needed
        // OPTIMIZE: if sorting is done incrementally as new edges are added
        // and not all at once as is done here, fold this test into the
        // current less than test.
        while ((!activePtr->fSkip || !nextPtr->fSkip)
                && activePtr->fCoincident == nextPtr->fCoincident) {
            if (activePtr->swapUnordered(nextPtr, bottom)) {
                winding -= activePtr->fWorkEdge.winding();
                SkASSERT(activePtr == edgeList[index - 1]);
                SkASSERT(nextPtr == edgeList[index]);
                SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
                if (--index == 0) {
                    winding += activePtr->fWorkEdge.winding();
                    break;
                }
                // back up one
                activePtr = edgeList[index - 1];
                continue;
            }
            SkASSERT(activePtr == edgeList[index - 1]);
            SkASSERT(nextPtr == edgeList[index]);
            break;
        }
        if (activePtr->fSkip && nextPtr->fSkip) {
            if (activePtr->fCloseCall ? activePtr->swapClose(nextPtr,
                    priorWinding, winding, windingMask)
                    : activePtr->swapCoincident(nextPtr, bottom)) {
                winding -= activePtr->fWorkEdge.winding();
                SkASSERT(activePtr == edgeList[index - 1]);
                SkASSERT(nextPtr == edgeList[index]);
                SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
                SkTSwap<ActiveEdge*>(activePtr, nextPtr);
                winding += activePtr->fWorkEdge.winding();
                SkASSERT(activePtr == edgeList[index - 1]);
                SkASSERT(nextPtr == edgeList[index]);
            }
        }
    }
    int firstCoincidentWinding = 0;
    ActiveEdge* firstCoincident = NULL;
    winding = 0;
    activePtr = edgeList[0];
    for (index = 1; index < edgeCount; ++index) {
        int priorWinding = winding;
        winding += activePtr->fWorkEdge.winding();
        nextPtr = edgeList[index];
        if (activePtr->fSkip && nextPtr->fSkip
                && activePtr->fCoincident == nextPtr->fCoincident) {
            if (!firstCoincident) {
                firstCoincident = activePtr;
                firstCoincidentWinding = priorWinding;
            }
            if (activePtr->fCloseCall) {
                // If one of the edges has already been added to out as a non
                // coincident edge, trim it back to the top of this span
                if (outBuilder.trimLine(y, activePtr->ID())) {
                    activePtr->addTatYAbove(y);
            #if DEBUG_ADJUST_COINCIDENT
                    SkDebugf("%s 1 edge=%d y=%1.9g (was fYBottom=%1.9g)\n",
                            __FUNCTION__, activePtr->ID(), y, activePtr->fYBottom);
            #endif
                    activePtr->fYBottom = y;
                }
                if (outBuilder.trimLine(y, nextPtr->ID())) {
                    nextPtr->addTatYAbove(y);
            #if DEBUG_ADJUST_COINCIDENT
                    SkDebugf("%s 2 edge=%d y=%1.9g (was fYBottom=%1.9g)\n",
                            __FUNCTION__, nextPtr->ID(), y, nextPtr->fYBottom);
            #endif
                    nextPtr->fYBottom = y;
                }
                // add missing t values so edges can be the same length
                SkScalar testY = activePtr->fBelow.fY;
                nextPtr->addTatYBelow(testY);
                if (bottom > testY && testY > y) {
            #if DEBUG_ADJUST_COINCIDENT
                    SkDebugf("%s 3 edge=%d bottom=%1.9g (was bottom=%1.9g)\n",
                            __FUNCTION__, activePtr->ID(), testY, bottom);
            #endif
                    bottom = testY;
                }
                testY = nextPtr->fBelow.fY;
                activePtr->addTatYBelow(testY);
                if (bottom > testY && testY > y) {
            #if DEBUG_ADJUST_COINCIDENT
                    SkDebugf("%s 4 edge=%d bottom=%1.9g (was bottom=%1.9g)\n",
                            __FUNCTION__, nextPtr->ID(), testY, bottom);
            #endif
                    bottom = testY;
                }
            }
        } else if (firstCoincident) {
            skipCoincidence(firstCoincidentWinding, winding, windingMask,
                    activePtr, firstCoincident);
            firstCoincident = NULL;
        }
        activePtr = nextPtr;
    }
    if (firstCoincident) {
        winding += activePtr->fWorkEdge.winding();
        skipCoincidence(firstCoincidentWinding, winding, windingMask, activePtr,
                firstCoincident);
    }
    // fix up the bottom for close call edges. OPTIMIZATION: maybe this could
    // be in the loop above, but moved here since loop above reads fBelow and
    // it felt unsafe to write it in that loop
    for (index = 0; index < edgeCount; ++index) {
        (edgeList[index])->fixBelow();
    }
    return bottom;
}

// stitch edge and t range that satisfies operation
static void stitchEdge(SkTDArray<ActiveEdge*>& edgeList, SkScalar
#if DEBUG_STITCH_EDGE
y
#endif
,
        SkScalar bottom, int windingMask, bool fill, OutEdgeBuilder& outBuilder) {
    int winding = 0;
    ActiveEdge** activeHandle = edgeList.begin() - 1;
    ActiveEdge** lastActive = edgeList.end();
#if DEBUG_STITCH_EDGE
    const int tab = 7; // FIXME: debugging only
    SkDebugf("%s y=%1.9g bottom=%1.9g\n", __FUNCTION__, y, bottom);
#endif
    while (++activeHandle != lastActive) {
        ActiveEdge* activePtr = *activeHandle;
        const WorkEdge& wt = activePtr->fWorkEdge;
        int lastWinding = winding;
        winding += wt.winding();
#if DEBUG_STITCH_EDGE
        SkDebugf("%*s edge=%d lastWinding=%d winding=%d skip=%d close=%d"
                " above=%1.9g below=%1.9g\n",
                tab-4, "", activePtr->ID(), lastWinding,
                winding, activePtr->fSkip, activePtr->fCloseCall,
                activePtr->fTAbove, activePtr->fTBelow);
#endif
        if (activePtr->done(bottom)) {
#if DEBUG_STITCH_EDGE
            SkDebugf("%*s fDone=%d || fYBottom=%1.9g >= bottom\n", tab, "",
                    activePtr->fDone, activePtr->fYBottom);
#endif
            continue;
        }
        int opener = (lastWinding & windingMask) == 0;
        bool closer = (winding & windingMask) == 0;
        SkASSERT(!opener | !closer);
        bool inWinding = opener | closer;
        SkPoint clippedPts[4];
        const SkPoint* clipped = NULL;
        bool moreToDo, aboveBottom;
        do {
            double currentT = activePtr->t();
            const SkPoint* points = wt.fPts;
            double nextT;
            SkPath::Verb verb = activePtr->fVerb;
            do {
                nextT = activePtr->nextT();
                // FIXME: obtuse: want efficient way to say
                // !currentT && currentT != 1 || !nextT && nextT != 1
                if (currentT * nextT != 0 || currentT + nextT != 1) {
                    // OPTIMIZATION: if !inWinding, we only need
                    // clipped[1].fY
                    switch (verb) {
                        case SkPath::kLine_Verb:
                            LineSubDivide(points, currentT, nextT, clippedPts);
                            break;
                        case SkPath::kQuad_Verb:
                            QuadSubDivide(points, currentT, nextT, clippedPts);
                            break;
                        case SkPath::kCubic_Verb:
                            CubicSubDivide(points, currentT, nextT, clippedPts);
                            break;
                        default:
                            SkASSERT(0);
                            break;
                    }
                    clipped = clippedPts;
                } else {
                    clipped = points;
                }
                if (inWinding && !activePtr->fSkip && (fill ? clipped[0].fY
                        != clipped[verb].fY : clipped[0] != clipped[verb])) {
#if DEBUG_STITCH_EDGE
                    SkDebugf("%*s add%s %1.9g,%1.9g %1.9g,%1.9g edge=%d"
                            " v=%d t=(%1.9g,%1.9g)\n", tab, "",
                            kUVerbStr[verb], clipped[0].fX, clipped[0].fY,
                            clipped[verb].fX, clipped[verb].fY,
                            activePtr->ID(),
                            activePtr->fWorkEdge.fVerb
                            - activePtr->fWorkEdge.fEdge->fVerbs.begin(),
                            currentT, nextT);
#endif
                    outBuilder.addCurve(clipped, (SkPath::Verb) verb,
                            activePtr->fWorkEdge.fEdge->fID,
                            activePtr->fCloseCall);
                } else {
#if DEBUG_STITCH_EDGE
                    SkDebugf("%*s skip%s %1.9g,%1.9g %1.9g,%1.9g"
                            " edge=%d v=%d t=(%1.9g,%1.9g)\n", tab, "",
                            kUVerbStr[verb], clipped[0].fX, clipped[0].fY,
                            clipped[verb].fX, clipped[verb].fY,
                            activePtr->ID(),
                            activePtr->fWorkEdge.fVerb
                            - activePtr->fWorkEdge.fEdge->fVerbs.begin(),
                            currentT, nextT);
#endif
                }
            // by advancing fAbove/fBelow, the next call to sortHorizontal
            // will use these values if they're still valid instead of
            // recomputing
                if (clipped[verb].fY > activePtr->fBelow.fY
                        && bottom >= activePtr->fBelow.fY
                        && verb == SkPath::kLine_Verb) {
                    activePtr->fAbove = activePtr->fBelow;
                    activePtr->fBelow = activePtr->fTangent = clipped[verb];
                    activePtr->fTAbove = activePtr->fTBelow < 1
                            ? activePtr->fTBelow : 0;
                    activePtr->fTBelow = nextT;
                }
                currentT = nextT;
                moreToDo = activePtr->advanceT();
                activePtr->fYBottom = clipped[verb].fY; // was activePtr->fCloseCall ? bottom :

                // clearing the fSkip/fCloseCall bit here means that trailing edges
                // fall out of sync, if one edge is long and another is a series of short pieces
                // if fSkip/fCloseCall is set, need to recompute coincidence/too-close-to-call
                // after advancing
                // another approach would be to restrict bottom to smaller part of close call
                // maybe this is already happening with coincidence when intersection is computed,
                // and needs to be added to the close call computation as well
                // this is hard to do because that the bottom is important is not known when
                // the lines are intersected; only when the computation for edge sorting is done
                // does the need for new bottoms become apparent.
                // maybe this is good incentive to scrap the current sort and do an insertion
                // sort that can take this into consideration when the x value is computed

                // FIXME: initialized in sortHorizontal, cleared here as well so
                // that next edge is not skipped -- but should skipped edges ever
                // continue? (probably not)
                aboveBottom = clipped[verb].fY < bottom;
                if (clipped[0].fY != clipped[verb].fY) {
                    activePtr->fSkip = false;
                    activePtr->fCloseCall = false;
                    aboveBottom &= !activePtr->fCloseCall;
                }
#if DEBUG_STITCH_EDGE
                 else {
                    if (activePtr->fSkip || activePtr->fCloseCall) {
                        SkDebugf("%s skip or close == %1.9g\n", __FUNCTION__,
                                clippedPts[0].fY);
                    }
                }
#endif
            } while (moreToDo & aboveBottom);
        } while ((moreToDo || activePtr->advance()) & aboveBottom);
    }
}

#if DEBUG_DUMP
static void dumpEdgeList(const SkTDArray<InEdge*>& edgeList,
        const InEdge& edgeSentinel) {
    InEdge** debugPtr = edgeList.begin();
    do {
        (*debugPtr++)->dump();
    } while (*debugPtr != &edgeSentinel);
}
#else
static void dumpEdgeList(const SkTDArray<InEdge*>& ,
        const InEdge& ) {
}
#endif

void simplify(const SkPath& path, bool asFill, SkPath& simple) {
    // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
    int windingMask = (path.getFillType() & 1) ? 1 : -1;
    simple.reset();
    simple.setFillType(SkPath::kEvenOdd_FillType);
    // turn path into list of edges increasing in y
    // if an edge is a quad or a cubic with a y extrema, note it, but leave it
    // unbroken. Once we have a list, sort it, then walk the list (walk edges
    // twice that have y extrema's on top)  and detect crossings -- look for raw
    // bounds that cross over, then tight bounds that cross
    SkTArray<InEdge> edges;
    SkTDArray<HorizontalEdge> horizontalEdges;
    InEdgeBuilder builder(path, asFill, edges, horizontalEdges);
    SkTDArray<InEdge*> edgeList;
    InEdge edgeSentinel;
    edgeSentinel.reset();
    makeEdgeList(edges, edgeSentinel, edgeList);
    SkTDArray<HorizontalEdge*> horizontalList;
    HorizontalEdge horizontalSentinel;
    makeHorizontalList(horizontalEdges, horizontalSentinel, horizontalList);
    InEdge** currentPtr = edgeList.begin();
    if (!currentPtr) {
        return;
    }
    // find all intersections between edges
// beyond looking for horizontal intercepts, we need to know if any active edges
// intersect edges below 'bottom', but above the active edge segment.
// maybe it makes more sense to compute all intercepts before doing anything
// else, since the intercept list is long-lived, at least in the current design.
    SkScalar y = (*currentPtr)->fBounds.fTop;
    HorizontalEdge** currentHorizontal = horizontalList.begin();
    do {
        InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
        SkScalar bottom = findBottom(currentPtr, edgeList.end(),
                NULL, y, asFill, lastPtr);
        if (lastPtr > currentPtr) {
            if (currentHorizontal) {
                if ((*currentHorizontal)->fY < SK_ScalarMax) {
                    addBottomT(currentPtr, lastPtr, currentHorizontal);
                }
                currentHorizontal = advanceHorizontal(currentHorizontal, bottom);
            }
            addIntersectingTs(currentPtr, lastPtr);
        }
        y = bottom;
        currentPtr = advanceEdges(NULL, currentPtr, lastPtr, y);
    } while (*currentPtr != &edgeSentinel);
    // if a quadratic or cubic now has an intermediate T value, see if the Ts
    // on either side cause the Y values to monotonically increase. If not, split
    // the curve at the new T.

    // try an alternate approach which does not split curves or stitch edges
    // (may still need adjustCoincident, though)
    // the idea is to output non-intersecting contours, then figure out their
    // respective winding contribution
    // each contour will need to know whether it is CW or CCW, and then whether
    // a ray from that contour hits any a contour that contains it. The ray can
    // move to the left and then arbitrarily move up or down (as long as it never
    // moves to the right) to find a reference sibling contour or containing
    // contour. If the contour is part of an intersection, the companion contour
    // that is part of the intersection can determine the containership.
    if (builder.containsCurves()) {
        currentPtr = edgeList.begin();
        SkTArray<InEdge> splits;
        do {
            (*currentPtr)->splitInflectionPts(splits);
        } while (*++currentPtr != &edgeSentinel);
        if (splits.count()) {
            for (int index = 0; index < splits.count(); ++index) {
                edges.push_back(splits[index]);
            }
            edgeList.reset();
            makeEdgeList(edges, edgeSentinel, edgeList);
        }
    }
    dumpEdgeList(edgeList, edgeSentinel);
    // walk the sorted edges from top to bottom, computing accumulated winding
    SkTDArray<ActiveEdge> activeEdges;
    OutEdgeBuilder outBuilder(asFill);
    currentPtr = edgeList.begin();
    y = (*currentPtr)->fBounds.fTop;
    do {
        InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
        SkScalar bottom = findBottom(currentPtr, edgeList.end(),
                &activeEdges, y, asFill, lastPtr);
        if (lastPtr > currentPtr) {
            bottom = computeInterceptBottom(activeEdges, y, bottom);
            SkTDArray<ActiveEdge*> activeEdgeList;
            sortHorizontal(activeEdges, activeEdgeList, y);
            bottom = adjustCoincident(activeEdgeList, windingMask, y, bottom,
                outBuilder);
            stitchEdge(activeEdgeList, y, bottom, windingMask, asFill, outBuilder);
        }
        y = bottom;
        // OPTIMIZATION: as edges expire, InEdge allocations could be released
        currentPtr = advanceEdges(&activeEdges, currentPtr, lastPtr, y);
    } while (*currentPtr != &edgeSentinel);
    // assemble output path from string of pts, verbs
    outBuilder.bridge();
    outBuilder.assemble(simple);
}