/*
    This file is part of Msc-generator.
    Copyright (C) 2008-2024 Zoltan Turanyi
    Distributed under GNU Affero General Public License.

    Msc-generator is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Msc-generator is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with Msc-generator.  If not, see <http://www.gnu.org/licenses/>.
*/

/** @file contour_simple.cpp Defines SimpleContour.
 * @ingroup contour_files
 */

#define _CRT_NONSTDC_NO_DEPRECATE

#include <algorithm>
#include <map>
#include <cmath>
#include "contour.h"
#include "contour_debug.h"

namespace contour {

//Do not create degenerate triangles.
//Always create clockwise.
SimpleContour::SimpleContour(XY a, XY b, XY c) : boundingBox(a, b) {
    switch (triangle_dir(a, b, c)) {
    default: //create empty if degenerate triangle
        boundingBox.MakeInvalid();
        area = 0;
        return;
    case CLOCKWISE:
        edges.emplace_back(a, b);
        edges.emplace_back(b, c);
        edges.emplace_back(c, a);
        area = maxa;
        break;
    case COUNTERCLOCKWISE:
        edges.emplace_back(a, c);
        edges.emplace_back(c, b);
        edges.emplace_back(b, a);
        area = maxa; //we make the triangle clockwise, so indicate that
        break;
    }
    boundingBox += c;
}

/** Create an ellipse (or ellipse slice) shape.
 * If not a full ellipse/circle a line segment is added as _the last edge_ to close it.
 *
 * @param [in] c Center point
 * @param [in] radius_x Radius in the y direction. Its absolute value is used.
 * @param [in] radius_y Radius in the y direction (same as `radius_x` if omitted = circle) Its absolute value is used.
 * @param [in] tilt_deg The tilt of the ellipse in degrees. 0 if omitted.
 * @param [in] s_deg The start point of the arc.
 * @param [in] d_deg The endpoint of the arc. If equal to `s_deg` a full ellipse results, else a straight line is added to close the arc.
 * If `radius_x` is zero, we return an empty shape. If `radius_y` is zero, we assume it to be the same as `radius_x` (circle).
*/
SimpleContour::SimpleContour(const XY& c, double radius_x, double radius_y, double tilt_deg, double s_deg, double d_deg) :
    area(maxa), edges(c, radius_x, radius_y, tilt_deg, s_deg, d_deg, true, true)
{
    if (size()==0) {
        area = 0;
        boundingBox.MakeInvalid();
        return;
    } else
        dirty_bb();
}

/** Clear the shape and assign a rectangle shape. */
SimpleContour& SimpleContour::operator =(const Block& b) {
    clear(); //empty bb and zero area
    if (b.IsInvalid() || !test_smaller(b.x.from, b.x.till) || !test_smaller(b.y.from, b.y.till)) return *this;
    edges.push_back(Edge(b.UpperLeft(), XY(b.x.till, b.y.from)));
    edges.push_back(Edge(XY(b.x.till, b.y.from), b.LowerRight()));
    edges.push_back(Edge(b.LowerRight(), XY(b.x.from, b.y.till)));
    edges.push_back(Edge(XY(b.x.from, b.y.till), b.UpperLeft()));
    area = b.x.Spans()*b.y.Spans();
    boundingBox = b;
    return *this;
}

/** Revert the clockwisedness of the shape by reverting, all edges and their order. */
SimpleContour& SimpleContour::Invert() noexcept {
    for (size_t i = 0; i<size(); i++)
        at(i).Invert();
    for (size_t i = 0; i<size()/2; i++)
        std::swap(at(i), at(size()-1-i));
    if (area!=infa) area *= -1;
    return *this;
}

/** Calculate the relation of a point and the shape.
 *
 * @param [in] p The point in question.
 * @param [out] edge If not nullptr it can receive the number of the edge if `p` lies on an edge/vertex.
 * @param [out] pos If not nullptr it can receive the position of `p` on the edge if `p` lies on one.
 * @param [in] strict If false, points close to edges will be reported as on the edge.
 * @returns The relation. Note that for counterclockwise 'inside' is the same as if the
            contour were clockwise, that is in the limited space enclosed.
 */
EPointRelationType SimpleContour::IsWithin(XY p, size_t* edge, double* pos, bool strict) const noexcept {
    if (size()==0 || !GetBoundingBox().IsWithinBool(p)) return WI_OUTSIDE;

    //Follow the contour and see how much it crosses the vertical line going through p
    //count the crossings below us (with larger y)
    //http://softsurfer.com/Archive/algorithm_0103/algorithm_0103.htm
    //1. a leftward edge includes its starting endpoint, and excludes its final endpoint;
    //2. a rightward edge excludes its starting endpoint, and includes its final endpoint;
    //3. vertical edges are excluded; and
    //4. the edge-ray intersection point must be strictly below of the point P.
    //4b: since we say containment also for edge points, if the edge goes through p, we stop
    size_t count = 0;
    size_t e;
    for (e = 0; e<size(); e++) {
        if (edge) *edge = e;      //return value
        if (at(e).GetStart().test_equal(p))
            return WI_ON_VERTEX;
        double y[3], po[3];
        int forward[3];
        int num = at(e).CrossingVertical(p.x, y, po, forward);

        if (num==-1) {
            if (test_equal(at(e).GetEnd().y, p.y)) {
                if (edge) *edge = next(e);
                return WI_ON_VERTEX; //on vertex
            }
            //Equality to at(e).GetStartXYHH is already tested above
            //now test if we are in-between
            if ((p.y < at(e).GetStart().y && at(e).GetEnd().y < p.y) ||
                (at(e).GetStart().y < p.y && p.y < at(e).GetEnd().y)) {
                if (pos) *pos = (p.y-at(e).GetStart().y)/(at(e).GetEnd().y-at(e).GetStart().y);
                return WI_ON_EDGE; //goes through p
            }
            //if we are not between we just continue
            continue;
        }

        for (int u = 0; u<num; u++) {
            if ((strict && y[u] == p.y) ||  //on an edge, we are _not_ approximate here
                (!strict&& test_equal(y[u], p.y))) {  //on an edge, we are approximate here
                //we have tested that at(e).start is not equal to p, so no need to test for that here
                if (test_equal(at(e).GetEnd().x, p.x)) {
                    if (edge) *edge = next(e);
                    return WI_ON_VERTEX;
                } else {
                    if (pos) *pos = po[u];
                    return WI_ON_EDGE; // on an edge, but not vertex
                }
            }
            if (forward[u]!=0 && y[u] > p.y)
                count++;
        }
    }
    return count&1 ? WI_INSIDE : WI_OUTSIDE; //even is out
}


//////////////////////////////////SimpleContour::Combine (Union, intersection, substation) implementation


/** Test if q is on the a->b section.
 * If the section is warped, that is b < a, we test if q is outside (b,a).
 * If q is almost equal to a or b, we return false */
constexpr bool really_between_warp(double q, double a, double b) noexcept {
    if (a<b) return test_smaller(q, b) && !test_smaller(q, a);
    return test_smaller(a, q) || test_smaller(q, b);
}

/** A helper for CheckContainment().
 * We assume 2 non-empty simple contours that have no crosspoints,
 * only touchpoints. So they can be exact equals, one can be fully inside the other
 * or they can be apart.
 * clockwiseness is ignored: the inside of a counterclockwise contour is the limited space
 * it encircles. We assume none of the contours is empty.
 * @param [in] b The other contour (the 'B' contour, we are the 'A' contour)
 * @param [in] threshold How many vertices must be similar (outside or in) before making a verdict.
 *             If 'this' is smaller than threshold, we lower threshold to the size of 'this'
 * @param [in] allow How many vertices may be different from the majority to still not return inconclusive.
 *             Must be lower than threshold. If not, we reduce it to threshold-1.
 * @returns one of the following values
 * - REL_SAME: The two contours are (almost) the same
 * - REL_OVERLAP: One of our vertices is outside 'b', we are either APART of B_INSIDE_A
 * - REL_A_INSIDE_B: One of our vertices is inside 'b', we are A_INSIDE_B.
 * - REL_IN_HOLE_APART: The relation could not have been decided (may be actual overlap)
 *
 * No other value will be returned.
 */
EContourRelationType SimpleContour::CheckContainmentHelper(const SimpleContour& b, size_t threshold, size_t allow) const {
    size_t edge;
    _ASSERT(!b.IsEmpty() && !IsEmpty());
    if (threshold>size()) threshold = size();
    if (allow>=threshold) allow = threshold-1;
    size_t inside = 0, outside = 0;
    for (bool try_edges : {false, true}) {
        inside = 0, outside = 0;
        for (size_t i = 0; i<size(); i++) {
            double pos;
            const XY p = at(i).GetStart();
            //Optimize: check BB here and not in SimpleContour::IsWithin below
            const EPointRelationType bb_rel = b.GetBoundingBox().IsWithin(p);
            if (bb_rel==WI_OUTSIDE)
                outside++;
            else switch (b.IsWithin(p, &edge, &pos, /*strict=*/false)) {
            default:
                _ASSERT(0);
                FALLTHROUGH;
            case WI_OUTSIDE:
                outside++;
                goto check_vertical;
            case WI_INSIDE:
                if (bb_rel==WI_ON_EDGE || bb_rel==WI_ON_VERTEX)
                    //This is clearly a bad result as the point cannot be inside,
                    //if it is on the perimeter of the bounding box. Try another.
                    continue;
                inside++;
            check_vertical:
                //If the previous edge was vertical, then here we were checking
                //being in/out using the same X coordinate. If there were some imprecision
                //before, we may have that, too, so increase the threshold, to force us to check one more.
                if (i>0 && threshold<size() && test_equal(at(i-1).GetStart().x, p.x))
                    threshold++;
                break;
            case WI_ON_VERTEX:
                pos = 0; //if we are on b's vertex, this is the right pos for that edge
                FALLTHROUGH;
            case WI_ON_EDGE:
                if (!try_edges) continue;
                RayAngle prev2 = pos == 0 ? b.at_prev(edge).Angle(true, 1) : b.at(edge).Angle(true, pos);
                RayAngle next2 = pos == 1 ? b.at_next(edge).Angle(false, 0) : b.at(edge).Angle(false, pos);
                //If 'b' has a too sharp vertex (too small angle), we cannot conclude that 'this'
                //is inside (as really_between_warp() is undecided then), go to the next vertex.
                if (prev2.IsSimilar(next2)) continue;
                RayAngle prev1 = at_prev(i).Angle(true, 1);
                RayAngle next1 = at(i).Angle(false, 0);
                //Normalize so that the clockwise next->prev angle transition is the inside of the
                //space enclosed by the paths, irrespective of whether the direction of the
                //simplecontours is clockwise or counterclockwise.
                if (!GetClockWise()) std::swap(prev1, next1); //make angles as if clockwise
                if (!b.GetClockWise()) std::swap(prev2, next2); //make angles as if clockwise

                if (prev1.IsSimilar(prev2)) {
                    //The two contours arrive via overlapping edges to 'p'
                    if (next1.IsSimilar(next2))
                        continue; //...and they also leave via overlapping edges - go check the next vertex of us
                    if (next1.IsSimilar(prev2))
                        continue; // Here prev1~~prev2~~next1, which means a sharp prev1~~next1 vertex. Too risky to form a verdict, continue
                    //The above 2 checks are needed, as the below one signals false if next1~~next2 or next1~~prev2
                    if (really_between_warp(next1, next2, prev2))
                        inside++; //The next1 leaves into the inside of 'b'
                    else
                        outside++; //The next1 leaves away from the circumference of 'b'
                } else if (next1.IsSimilar(next2)) {
                    if (prev1.IsSimilar(next2))
                        continue; // Here prev1~~prev2~~next1, which means a sharp prev1~~next1 vertex. Too risky to form a verdict, continue
                    if (really_between_warp(prev1, next2, prev2))
                        inside++;
                    else
                        outside++;
                } else if (next1.IsSimilar(prev2))
                    outside++;
                else if (prev1.IsSimilar(next2))
                    outside++;
                else if (really_between_warp(prev1, next2, prev2) || really_between_warp(next1, next2, prev2))
                    inside++;
                else
                    outside++;
            }
            if (inside>threshold || outside>threshold) goto finish;
        }
        if (inside>allow && outside>allow) return {};
        if (inside>allow) return REL_A_INSIDE_B;
        if (outside>allow) return REL_OVERLAP;
    }
    if (inside==0 && outside==0) //All our vertices were on an edge of 'b' with all the angles matching
        return REL_SAME;
    if (inside==outside)
        return REL_IN_HOLE_APART; //inconclusive
finish:
    return inside>outside ? REL_A_INSIDE_B : REL_OVERLAP;
}

/** Determines the relation of two simplecontours assuming none of their edges cross.
 *
 * Gives valid result only if the two contours have no crosspoints - but may touch.
 * Can only return REL_SAME, REL_APART, REL_A_INSIDE_B or REL_B_INSIDE_A,
 * REL_A_IS_EMPTY, REL_B_IS_EMPTY, REL_BOTH_EMPTY.
 * Clockwiseness fully ignored: the inside of counterclockwise contours is the
 * limited space they encloses.
 *
 * This one may be called by RelationTo().
 */
EContourRelationType SimpleContour::CheckContainment(const SimpleContour &other) const
{
    //fast path
    if (other.IsEmpty())
        return IsEmpty() ? REL_BOTH_EMPTY : REL_B_IS_EMPTY;
    if (IsEmpty())
        return REL_A_IS_EMPTY;
    if (GetBoundingBox().HasZeroOverlap(other.GetBoundingBox()))
        return REL_APART;
    //If 'this' is smaller or same area than 'other', check if they are the same or if
    //'this' is inside 'other'.
    if (fabs(GetArea())<fabs(other.GetArea())*1.01)
        switch (CheckContainmentHelper(other)) {
        case REL_SAME:       _ASSERT_PRINT(fabs(GetArea())*0.99 < fabs(other.GetArea()) && fabs(other.GetArea())<fabs(GetArea())*1.01, *this, other, "REL_SAME diff area"); return REL_SAME;
        case REL_A_INSIDE_B: _ASSERT_PRINT(fabs(GetArea())<fabs(other.GetArea())*1.01, *this, other, "REL_A_INSIDE_B bad area"); return REL_A_INSIDE_B;
        default: _ASSERT(0); FALLTHROUGH;
        case REL_OVERLAP: break;
        }
    switch (other.CheckContainmentHelper(*this)) {
    default: _ASSERT(0); return REL_APART;
    case REL_SAME:       _ASSERT_PRINT(fabs(GetArea())*0.99 < fabs(other.GetArea()) && fabs(other.GetArea())<fabs(GetArea()*1.01), *this, other, "REL_SAME diff area"); return REL_SAME; //If one dir measured it very similar, lets go with it.
    case REL_OVERLAP:    return REL_APART;
    case REL_A_INSIDE_B: _ASSERT_PRINT(fabs(other.GetArea())<fabs(GetArea())*1.01, *this, other, "REL_A_INSIDE_B bad area"); return REL_B_INSIDE_A; //other is in us
    }
}

/** Swap data with `b`. */
void SimpleContour::swap(SimpleContour& b) noexcept
{
    std::swap(boundingBox, b.boundingBox);
    std::swap(area, b.area);
    edges.swap(b.edges);
}


void SimpleContour::assign_dont_check(std::span<const XY> v)
{
    if (v.size() < 2) {
        clear();
        return;
    }
    dirty_bb();
    area = infa;
    edges.clear();
    for (size_t i = 0; i<v.size(); i++)
        edges.push_back(Edge(v[i], v[(i+1)%v.size()]));
    Sanitize();  //includes CalculateBoundingBox() and CalculateClockwise()
}

void SimpleContour::assign_dont_check(std::initializer_list<XY> v)
{
    if (v.size() < 2) {
        clear();
        return;
    }
    dirty_bb();
    area = infa;
    edges.clear();
    for (auto i= v.begin(), j=std::next(i); j!=v.end(); ++i, ++j)
        edges.emplace_back(*i, *j);
    edges.emplace_back(*std::prev(v.end()), *v.begin());
    Sanitize();  //includes CalculateBoundingBox() and CalculateClockwise()
}

bool SimpleContour::IsRectangle() const noexcept {
    if (edges.size()!=4) return false;
    return (edges[0].IsHorizontal() && edges[1].IsVertical() && edges[2].IsHorizontal() && edges[3].IsVertical())
        || (edges[1].IsHorizontal() && edges[2].IsVertical() && edges[3].IsHorizontal() && edges[0].IsVertical());
}


/** Fix problems in a shape (edges not joint, etc).
 *
 * @returns true if we were sane, false if changes were needed.
 */
bool SimpleContour::Sanitize(double tolerance)
{
    bool ret = true;
    if (size()==0) return true;
    if (size()==1) goto clear;
    for (size_t u=0; u<size(); /*nope*/)
        if (at(u).IsDot(tolerance)) {
            at_next(u).SetStartOnly(at_prev(u).GetEnd());
            edges.erase(edges.begin()+u);
            ret = false;
        } else
            u++;
    switch (size()) {
    case 0: return false;
    case 1: goto clear;
    case 2:
        if (at(0).IsStraight() && at(1).IsStraight())
            goto clear;
        FALLTHROUGH;
    default:
        //ensure that edges connect
        for (size_t u=0; u<size(); u++)
            if (!at(u).GetEnd().test_equal(at_next(u).GetStart()))
                goto clear;
            else at(u).SetEndOnly(at_next(u).GetStart());
    }
    if (!ret) { dirty_bb(); dirty_area(); } //we think clockwiseness remains just by deleting a few edges.
    return ret;
clear:
    clear();
    return false;
}

/** Calculate the boundingBox of the whole shape.
 *  Assumes the bounding box of curved edges is already calculated.*/
const Block &SimpleContour::CalculateBoundingBox() const noexcept
{
    boundingBox.MakeInvalid();
    for(const auto &e : edges)
        if (e.IsStraight())
            boundingBox += e.GetStart();
        else
            boundingBox += e.CreateBoundingBox();
    return boundingBox;
}

/** This takes a sane shape and adds an edge to the end. Leaves a sane shape.
 *
 * If the edge to insert starts at the last point, we replace the last edge to it.
 * (makes sense if we replace a straight edge to a curve or vice versa)
 * If the last edge is a curve, we anyway replace the edge to be inserted.
 * If we insert a curve, which does not.
 * Finally, if the edge does not end at the first vertex, we add a line to close the shape.
 * @param [in] edge The edge to insert.
 * @returns true if edge inserted successfully. False if it would have leaved the shape flopped.
 */
bool SimpleContour::AddAnEdge(const Edge &edge)
{
    XY dum1[8];
    double dum2[8];
    SimpleContour ret(*this);
    if (size()) {
        //see if we need to replace the last edge, drop it
        if (edge.GetStart().test_equal(ret[size()-1].GetStart()))
            ret.edges.pop_back();
        else         //set the last edge to straight
            ret[size()-1] = Edge(ret[size()-1].GetStart(), edge.GetStart());
    }
    //insert edge
    ret.edges.push_back(edge);

    size_t num_to_check = 1;
    //if edge is curvy and does not end in at(0).start, insert a straight edge afterwards
    if (!edge.IsStraight()) {
        if (!edge.GetEnd().test_equal(ret[0].GetStart())) {
            ret.edges.push_back(Edge(edge.GetEnd(), ret[0].GetStart()));
            //check if this edge to insert do not cross the previously inserted edge
            if (ret[ret.size()-2].Crossing(ret[ret.size()-1], true, dum1, dum2, dum2))
                return false;
            num_to_check = 2;
        }
    }
    //now check if inserted edges cross any of the ones before
    //check if edge->end is crossing any existing edges
    for (size_t i = 0; i<ret.size()-num_to_check-1; i++)
        for (size_t j = ret.size()-num_to_check-1; j<ret.size(); j++)
            if (ret[i].Crossing(ret[j], next(i)==j, dum1, dum2, dum2))
                return false;
    //OK, we can have these edges inserted
    dirty_bb();
    area = infa;
    swap(ret);
    return true;
}

/** Calculate the circumference length of the shape.
 *
 * @param [in] include_hidden If false, we leave out edges marked as non-visible.
 * @returns Length of the circumference.
 */
double SimpleContour::GetCircumference(bool include_hidden) const noexcept
{
    double ret = 0;
    for (const auto &e : edges)
        if (include_hidden || e.IsVisible())
            ret += e.GetLength();
    return ret;
}

/** Returns the coordinates of the centroid of the shape multiplied by its (signed) area. */
XY SimpleContour::CentroidUpscaled() const noexcept
{
    XY ret (0,0);
    for (const auto &e : edges)
        ret += e.GetCentroidAreaAboveUpscaled();
    return ret;
}

/** Calculates the touchpoint of tangents drawn from a given point.
 *
 * Given the point `from` draw tangents to the shape (two can be drawn)
 * and calculate where these tangents touch the shape.
 * Tangent is anything touching the shape (at a vertex, at the middle of a curvy edge
 * or going along, in parallel with a full straight edge).
 * In this context the *clockwise tangent* is the one which is traversed from
 * `from` towards the shape touches the shape in the clockwise direction.
 * @param [in] from The point from which the tangents are drawn.
 * @param [out] clockwise The point where the clockwise tangent touches the shape.
 * @param [out] cclockwise The point where the counterclockwise tangent touches the shape.
 * @returns True if success, false if `from` is inside or on the shape.
 */
bool SimpleContour::TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const
{
    if (size()==0 || IsWithin(from)!=WI_OUTSIDE) return false;
    auto c = at(0).TangentFrom<false>(from);
    clockwise = c.first.second;
    cclockwise = c.second.second;
    for (unsigned u = 1; u<size(); u++) {
        c = at(u).TangentFrom<false>(from);
        clockwise = minmax_clockwise(from, clockwise, c.first.second, true);
        cclockwise = minmax_clockwise(from, cclockwise, c.second.second, false);
    }
    return true;
}

/** Calculates the touchpoint of tangents drawn to touch two shapes.
 *
 * Given the two shapes, four such tangents can be drawn, here we focus on the two
 * outer ones, the ones that touch either both shapes clockwise or both of them
 * counterclockwise, but not mixed.
 * Tangent is anything touching the shape (at a vertex, at the middle of a curvy edge
 * or going along, in parallel with a full straight edge).
 * @param [in] from The other shape to the tangents are drawn.
 * @param [out] clockwise The points where the clockwise tangent touches our shape
 *                        (clockwise[0]) and `from` (clockwise[1]).
 * @param [out] cclockwise The points where the counterclockwise tangent touches our shape
 *                         (cclockwise[0]) and `from` (cclockwise[1]).
 * @returns True if success, false if `from` is inside us.
 */
bool SimpleContour::TangentFrom(const SimpleContour &from, XY clockwise[2], XY cclockwise[2]) const
{
    if (size()==0 || from.size()==0) return false;
    clockwise[0] = cclockwise[0] = at(0).GetStart();
    clockwise[1] = cclockwise[1] = from.at(0).GetStart();
    for (const auto &e : edges)
        for (const auto &f : from.edges)
            e.TangentFrom<false>(f, clockwise, cclockwise);
    return true;
}


//////////////////////////////////SimpleContour::CreateExpand implementation


/** Helper that creates a half circle for EXPAND_ROUND or EXPAND_MITER_ROUND type expansion,
 * where expanded edges are parallel. All added edges will have mark=true set.
 *
 * @param [in] center The center of the circle of the arc (likely the original vertex)
 *                    Passed by value as we may modify it if not in between start&end
 * @param [in] start The start of the arc (end of the previous expanded edge)
 * @param [in] end The end of the arc (the start of the next previous edge)
 * @param [in] clockwise The clockwisedness of the original shape.
 * @param [out] insert_to Insert the resulting (series of) Edge(s) to this list...
 * @param [in] here ...before this location.
 * @param [in] visible Set the visibility of the newly inserted edges to this.
 */
void SimpleContour::CreateRoundForExpand(XY center, const XY &start, const XY &end, bool clockwise,
    std::list<ExpandMetaData> &insert_to, std::list<ExpandMetaData>::iterator here,
    bool visible)
{
    if (!test_equal(center.DistanceSqr(start), center.DistanceSqr(end))) {
        if (start==end) return;
        if (start.DistanceSqr(end)<0.001) {
            insert_to.emplace(here, EXPAND_ROUND, Edge(start, end));
            return;
        }
        center = (start+end)/2;
    }
    const double radius = center.Distance(start);
    double s_deg = acos(std::max(-1., std::min(1., (start.x-center.x)/radius)))*180/M_PI;
    if (start.y<center.y) s_deg = 360-s_deg;
    double d_deg = acos(std::max(-1., std::min(1., (end.x-center.x)/radius)))*180/M_PI;
    if (end.y<center.y) d_deg = 360-d_deg;

    //We do not know if the resulting contour will be clockwise or not
    //But, if we use the clockwiseness of the original contour, we are safe
    //since for positive originals we will keep only positive results,
    //so if we miss and we will be part of a negative contour, that
    //will get dropped anyway. (Likewise for negative originals.)
    SimpleContour round(center, radius, radius, 0, clockwise ? s_deg : d_deg, clockwise ? d_deg : s_deg);
    //delete the straight edge
    if (round.edges.back().IsStraight())
        round.edges.pop_back();
    if (!clockwise)
        round.Invert();
    //Make sure the construct starts *exactly* at 'start' and 'end'
    //The result is not precise, we simply set the star/endpoint (keeping control points)
    _ASSERT(round.edges.front().GetStart().DistanceSqr(start)<1);
    _ASSERT(round.edges.back().GetEnd().DistanceSqr(end)<1);
    round.edges.front().SetStartOnly(start);
    round.edges.back().SetEndOnly(end);
    for (auto &e: round.edges) {
        if (!visible)
            e.SetVisible(false);
        e.SetInternalMark(true);
        insert_to.emplace(here, EXPAND_ROUND, e); //all inserted edges marked as trivially connecting with ROUND type
    }
}


/** Expands a shape.
 *
 * Expansion aims to create a shape whose edges run parallel along the edges of the
 * original, but at `gap` pixels away. Positive `gap` values result in actual expansion
 * negative values in shrinkage (the new shape is inside the original).
 * The function works for counterclockwise shapes, the meaning of the sign of `gap` is
 * reversed.
 *
 * After the expansion, we check if there are edges crossing each other and
 * remove them by walking. In this space we also may end up in multiple separate
 * SimpleContours. (E.g., shrinking a dog-bone shape may lead us to two circles,
 * like `O=O` becomes `o o`.) This is why the result is stored in a full Contour
 * which may hold multiple SimpleContours.
 *
 * @param [in] type There are multiple typical ways to handle edge joins, see EExpandType.
 * @param [in] gap The amount of expansion/shrinkage. No-op if zero.
 * @param [in] miter_limit Tells us that with EXPAND_MITER_* how long the miter edges can be
 *                    in terms of the `gap`.
 *                    E.g., miter_limit=2 says they can be as long as `2*gap`.
 *                    The rest is cut away with a bevel-like edge.
 * @param [in] tolerance Edges shorter than this are ignored.
 * @param [in] always_simplify_result If set to yes, we merge resulting edges no matter what.
 *             If false, we only merge (and remove) edges that have no corresponding edge in
 *             the original contour.
 * @returns A list of expanded edges, which form a (perhaps tangled, but) continuous simplecontour.*/
SimpleContour::MetaDataList SimpleContour::ExpandHelper(EExpandType type, double gap, double miter_limit,
                                                        double tolerance, bool always_simplify_result) const
{
    _ASSERT(IsSane());
    //Return us if nothing to do.
    _ASSERT(gap);
    MetaDataList r;

    //if we shrink beyond our bounding box, we become empty
    const double shrink_by = GetClockWise() ? -gap : gap;
    if (size()<=1 || GetBoundingBox().x.Spans() < 2*shrink_by || GetBoundingBox().y.Spans() < 2*shrink_by)
        return r; //empty "res" assumed

    bool replace_cp_inverse_to_extended = false;

    //Expand all the edges
    EdgeList<Edge> tmp;
    std::optional<XY> prev_non_dot_end; //end of the last non-dot edge, if there was a dot before us.
    //Find the last non-dot edge backwards
    for (size_t u = size(); u; u--)
        if (!at(u-1).IsDot(tolerance)) {
            if (u!=size())
                prev_non_dot_end = at(u-1).GetEnd();
            break;
        }
    for (size_t u = 0; u<size(); u++) {
        if (at(u).IsDot(tolerance)) {
            if (!prev_non_dot_end)
                prev_non_dot_end = at(u).GetStart(); //we have removed a previous edge, as well
            continue;
        }
        tmp.clear();
        XY prev_tangent, next_tangent;
        bool success;
        if (prev_non_dot_end) {
            //This is a bit cumbersome, but we optimize for the likely case of no dots
            Edge adjusted(at(u));
            adjusted.SetStartOnly(prev_non_dot_end.value());
            success = adjusted.CreateExpand(gap, tmp, prev_tangent, next_tangent);
            if (success)
                prev_non_dot_end.reset(); //we wont need it later
        } else
            success = at(u).CreateExpand(gap, tmp, prev_tangent, next_tangent);
        if (!success) {
            //false is returned if a bezier degenerates - replace with a line
            if (prev_non_dot_end) {
                Edge(prev_non_dot_end.value(), at(u).GetEnd(), at(u).IsVisible()).CreateExpand(gap, tmp, prev_tangent, next_tangent);
                prev_non_dot_end.reset();
            } else {
                Edge(at(u).GetStart(), at(u).GetEnd(), at(u).IsVisible()).CreateExpand(gap, tmp, prev_tangent, next_tangent);
            }
        }
        if (tmp.size()==0) {
            //All edges may have been dots.
            return r; //empty
        }
        bool first = true;
        for (auto t: tmp) {
            r.emplace_back(type, t, int(u));
            if (first) {
                r.back().prev_tangent = prev_tangent;
                first = false;
            }
        }
        r.back().next_tangent = next_tangent;
        //all edges are marked as trivially connecting
        //the relation of the last of the generated edges with the next:
        //mark this as a relation needed to be computed
        r.back().cross_type = Edge::CP_INVERSE;
        //if this is a concave edge join, set join type to bevel
        if (IsConcaveJoin(u) == (gap>0))
            r.back().join_type = EXPAND_BEVEL;
    }

    //Calculate actual max miter length.
    const double gap_limit_to_use = fabs(miter_limit) < MaxVal(miter_limit) ? gap*miter_limit : MaxVal(gap_limit_to_use);
    //Calculate the square of it to test on a DistanceSqr() - to avoid sqrt() calls.
    //Make it a bit bigger (2 pixels) - apply miter only if miter would be somewhat larger than
    //limit. This is to avoid very small chopped miters.
    const double gap_limit_sqr_to_test = fabs(miter_limit) < MaxVal(miter_limit) ? gap*gap*miter_limit*miter_limit + 2: MaxVal(gap_limit_sqr_to_test);

    bool need_to_process, has_remaining_cp_inverse;

#ifdef _DEBUG
    if (0) {
        contour::debug::Snapshot("test.pdf", EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5, r);
        contour::debug::Snapshot("test_o.pdf", EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5, *this);
    }
#endif

    do {
        need_to_process = false;
        has_remaining_cp_inverse = false;
        //Find how and where expanded edges meet
        //do not do this for edge segments generated by expansion above or for cps where
        //we have already sorted things in a previous run of this do-while cycle
        for (auto i = r.begin(); i!=r.end(); i++)
            if (i->cross_type==Edge::CP_TRIVIAL)
                continue;
            else if (i->cross_type==Edge::CP_INVERSE) {
                i->cross_type = i->FindExpandedEdgesCP(*r.next(i), i->cross_point,
                    i->next_tangent, r.next(i)->prev_tangent,
                    !at(i->original_edge).IsStraight(), !at(r.next(i)->original_edge).IsStraight(),
                    i->us_end_pos, i->next_start_pos);
                _ASSERT(!std::isnan(i->cross_point.x) && !std::isnan(i->cross_point.y));
                //note if we have changed the status of at least one such CP
                //this means we may need to do another pass
                if (i->cross_type!=Edge::CP_INVERSE)
                    need_to_process = true;
                else
                    has_remaining_cp_inverse = true;
            } else
                need_to_process = true; //any join that is not CP_TRIVIAL or CP_INVERSE

        //Adjust the start and end of the edges to the crosspoints
        //and insert additional edges, if needed
        //Ignore&keep CP_INVERSE edge joins for now

        //Calculate if we start with a miter limited in length
        bool need_miter_limit_bevel = IsMiter(r.back().join_type) &&
            r.back().cross_type == Edge::CP_EXTENDED &&
            r.back().cross_point.DistanceSqr(r.back().GetEnd()) > gap_limit_sqr_to_test;
        XY prev_miter_limit_bevel_point;
        if (need_miter_limit_bevel)
            prev_miter_limit_bevel_point = r.front().GetStart() +
                    (r.back().cross_point-r.front().GetStart()).Normalize()*gap_limit_to_use;

        //cycle through the edges if we have any connections that is not CP_TRIVIAL or CP_INVERSE
        if (need_to_process) {
            for (auto i = r.begin(); i!=r.end(); /*nope*/) {
                const auto prev_i = r.prev(i);
                //fast path: both ends either trivial or inverse
                if ((prev_i->cross_type == Edge::CP_TRIVIAL || prev_i->cross_type == Edge::CP_INVERSE) &&
                    (i->cross_type==Edge::CP_TRIVIAL || i->cross_type==Edge::CP_INVERSE)) {
                    i++;
                    continue;
                }

                //The new start and endpoint for us (edge #i)
                XY new_start, new_end;
                if (prev_i->cross_type==Edge::CP_REAL)
                    new_start = prev_i->cross_point;
                else if (prev_i->cross_type==Edge::CP_EXTENDED && IsMiter(prev_i->join_type))
                    new_start = prev_i->cross_point;
                else
                    new_start = i->GetStart();

                if (i->cross_type==Edge::CP_REAL)
                    new_end = i->cross_point;
                else if (i->cross_type==Edge::CP_EXTENDED && IsMiter(i->join_type))
                    new_end = i->cross_point;
                else
                    new_end = i->GetEnd();

                if (need_miter_limit_bevel || (i->cross_type == Edge::CP_EXTENDED && IsMiter(i->join_type))) {
                    //If we connected to previous edge via a too long miter, we limit its length
                    //The bevel needed in this case was added when we processed the previous
                    if (need_miter_limit_bevel)
                        new_start = prev_miter_limit_bevel_point;
                    //Check if we connect to the next edge via a too long miter.
                    need_miter_limit_bevel = IsMiter(i->join_type) && i->cross_type == Edge::CP_EXTENDED &&
                        new_end.DistanceSqr(i->GetEnd()) > gap_limit_sqr_to_test;
                    //Adjust the endpoint of us to where the miter shall end.
                    if (need_miter_limit_bevel)
                        new_end = i->GetEnd() + (new_end-i->GetEnd()).Normalize()*gap_limit_to_use;
                    //The value of 'need_miter_limit_bevel' will also be re-used next
                    _ASSERT(fabs(new_start.length()) < 100000);
                    _ASSERT(!std::isnan(new_end.x) && !std::isnan(new_end.y));
                    _ASSERT(fabs(new_end.length()) < 100000);
                }

                //if (new_start.test_equal(new_end)) {
                //    //We got degenerate, remove
                //    prev_i->cross_point = new_end = new_start; //Use this from now on.
                //    r.erase(i++);
                //    continue;
                //}

                const auto next_i = r.next(i);
                const bool done = next_i == r.begin();

                //If we did not degenerate to a point, adjust start/end points
                //for beziers we may have to add straight lines as continuation.
                if (i->IsStraight()) {
                    i->SetStartOnly(new_start);
                    i->SetEndOnly(new_end);
                } else {
                    //add straight edge before the inserted bezier for miters
                    if (IsMiter(prev_i->join_type) && prev_i->cross_type==Edge::CP_EXTENDED)
                        r.emplace(i, prev_i->join_type, new_start, i->GetStart(), i->IsVisible()); //mark=true
                    if (prev_i->cross_type == Edge::CP_REAL && i->cross_type == Edge::CP_REAL)
                        i->SetStartEndIgn(new_start, new_end, prev_i->next_start_pos, i->us_end_pos);
                    else if (prev_i->cross_type == Edge::CP_REAL)
                        i->SetStartIgn(new_start, prev_i->next_start_pos);
                    else if (i->cross_type == Edge::CP_REAL)
                        i->SetEndIgn(new_end, i->us_end_pos);

                    //add straight edge after the bezier for miters
                    //insert _after_ i (before next_i)
                    if (IsMiter(i->join_type) && i->cross_type ==Edge::CP_EXTENDED)
                        //make sure we copy the metadata, as well ('cross_type' and afterwards)
                        //beacuse the next iteration will rely on these, when adding
                        r.emplace(next_i, i->join_type, i->GetEnd(), new_end, i->IsVisible(),
                                          i->cross_type, i->cross_point, double(), i->next_start_pos); //mark=true
                }

                //For miters Add bevel if the miter would be too long. (can be true only for miters)
                if (need_miter_limit_bevel) {
                    _ASSERT(IsMiter(i->join_type));
                    //If we are last, we cannot use next_i->GetStartXYHH in claculating the miter
                    //end, since that has been already modified. In fact it is already the
                    //right miter end, so we can just re-use it.
                    XY bevel_end = next_i->GetStart();
                    if (i != --r.end())
                        bevel_end += (i->cross_point-next_i->GetStart()).Normalize()*gap_limit_to_use;
                    _ASSERT(bevel_end.length() < 100000);
                    if (bevel_end != new_end)
                        //make sure we copy the metadata, as well ('cross_type' and afterwards)
                        //beacuse the next iteration will rely on these, when adding
                        r.emplace(next_i, i->join_type, new_end, bevel_end, i->IsVisible()&& next_i->IsVisible(),
                                          i->cross_type, i->cross_point, double(), i->next_start_pos); //mark=true
                    prev_miter_limit_bevel_point = bevel_end;
                }

                //What we did so far:
                //- We have adjusted the start/endpoint of the edge and handled everything around
                //  the start of the edge.
                //- for miters we have 1) added the straight line needed for beziers and CP_EXTENDED;
                //  2) added the bevel if the miter would have been too long; basically we handled
                //  everything, except the parallel case
                //- for round/bevel expand types we just added the edge, no join specifics
                //Below we add whatever line join is needed.
                //Note that we do nothing for CP_REAL, TRIVIAL or CP_INVERSE, as for the first two
                //nothing needs to be done, for the latter we do not know what to do.
                //Now even if we have inserted 'i' still points to the original edge.

                if (i->cross_type == Edge::CP_EXTENDED) {
                    if (i->join_type == EXPAND_BEVEL) {
                        //Add a bevel from new_end and the next start
                        r.emplace(next_i, i->join_type, new_end, next_i->GetStart(), i->IsVisible()&& next_i->IsVisible()); //mark=true
                    } else if (i->join_type == EXPAND_ROUND) {
                        //Add a circle, from new_end and the next start
                        CreateRoundForExpand(at(i->original_edge).GetEnd(), new_end, next_i->GetStart(), gap>0,
                            r, next_i, i->IsVisible()&& next_i->IsVisible());
                    }
                }
                //We have done everything for non-parallel joins
                if (i->cross_type != Edge::NO_CP_PARALLEL) {
                    i = next_i;
                    if (done) break;
                    else continue;
                }

                /* Here we handle CP_PARALLEL. */
                //Find where the expanded version of this edge begins and where does the next one end.
                //This is so that we can remove loops
                auto start_orig_us = i;
                auto end_orig_next = next_i;
                while (start_orig_us!=r.begin() && start_orig_us->original_edge==i->original_edge)
                    start_orig_us--;
                //step back to the first element
                if (start_orig_us->original_edge!=i->original_edge)
                    start_orig_us++;
                while (end_orig_next!=r.end() && end_orig_next->original_edge==next_i->original_edge)
                    end_orig_next++;
                //leave to point beyond the last element

                //Now determine which direction the round/bevel/miter needs to be added.
                //This is done by updating i->cross_point.
                //Note that FindExpandedEdgesCP() calculates a CP as below
                //- if one is straight, the cp is on the straight line. This is the right
                //  (original) direction as ends of straight edges do not change direction when expanded.
                //- if both are bezier, the calculated cp is calcualted based on the direction
                //  of the first edge (in "i")
                //- if both of the edges are straight, the cp is as for the two bezier cases.
                //  This can happen if the last segment of a bezier has turned to a line during expansion.
                //NOTE that FindExpandedEdgesCP() uses the tangents of the *original* edge, thus the cp
                //is in the direction of the original edge ends even if a bezier edge changed direction
                //during expansion.


                ////Thus, we need to alter the cp, if 1) neither of them is straight and 2) the edge "i"
                ////changed direction.
                //if (!i->IsStraight() && !next_i->IsStraight() &&
                //    !Edge::IsSameDir(at(i->original_edge).end, at(i->original_edge).NextTangentPoint(1.0),
                //    i->end, i->next_tangent))
                //    //in this case flip cp to the original vertex
                //    i->cross_point = 2*at(i->original_edge).end - i->cross_point;

                //If we are at the end of the list (and next_i & end_orig_next
                //are at the beginning, we copy them to the end, so RemoveLoop
                //can operate on a contiguous series of edges.
                //(RemoveLoop does not do circular iterator increments/decrements)
                if (done) {
                    r.splice(r.end(), r, next_i, end_orig_next);
                    end_orig_next = r.end();
                }

                //Below we add the line joins.
                //We do not copy metadata, all new edges will have CP_TRIVIAL joins to their
                //following edge. This is just as well, since we add edges such that the last
                //edge ends exactly in next_i->start - so we form a trivial join.
                if (i->join_type == EXPAND_MITER) {
                    //We add two lines (two miters)
                    //Remember, i->cross_point contains the point in between the two lines.
                    //We do not limit these miters, even if miter_limit is not infinite.
                    r.emplace(next_i, i->join_type, new_end, i->cross_point, i->IsVisible()); //mark=true
                    auto ii = r.emplace(next_i, i->join_type, i->cross_point, next_i->GetStart(), next_i->IsVisible());//mark=true
                    Edge::RemoveLoop(r, start_orig_us, ii);
                    Edge::RemoveLoop(r, ii, end_orig_next);
                } else if (IsRound(i->join_type)) {
                    //Add a circle, from new_end and the next start
                    CreateRoundForExpand(at(i->original_edge).GetEnd(), new_end, next_i->GetStart(), gap>0,
                        r, next_i, i->IsVisible() && next_i->IsVisible());
                    Edge::RemoveLoop(r, start_orig_us, next_i);
                    //i and next_i may be destroyed here. Search loops from start_orig_us again
                    Edge::RemoveLoop(r, start_orig_us, end_orig_next);
                } else if (IsBevel(i->join_type)) {
                    //Add a bevel from new_end and the next start
                    //r.emplace(next_i, new_end, next_i->start, i->visible && next_i->visible);
                    const XY tangent = (at(i->original_edge).NextTangentPoint(1.0) -
                                        at(i->original_edge).GetEnd()).Normalize();
                    const XY cp = (new_end + next_i->GetStart())/2 +
                                    tangent * (new_end - next_i->GetStart()).length()/2;
                    r.emplace(next_i, i->join_type, new_end, cp, i->IsVisible() && next_i->IsVisible()); //mark=true
                    auto ii = r.emplace(next_i, i->join_type, cp, next_i->GetStart(), i->IsVisible() && next_i->IsVisible()); //mark=true
                    Edge::RemoveLoop(r, start_orig_us, ii);
                    Edge::RemoveLoop(r, ii, end_orig_next);
                } else if (i->join_type == EXPAND_MITER_SQUARE) {
                    //Here we add 3 edges of a half-square
                    const XY &next_start = next_i->GetStart();
                    const double dist = new_end.Distance(next_start)/2;
                    const XY tangent = (at(i->original_edge).NextTangentPoint(1.0) -
                                        at(i->original_edge).GetEnd()).Normalize()*dist;
                    r.emplace(next_i, i->join_type, new_end, new_end+tangent, i->IsVisible() && next_i->IsVisible()); //mark=true
                    r.emplace(next_i, i->join_type, new_end+tangent, next_start+tangent, i->IsVisible() && next_i->IsVisible());  //mark=true
                    r.emplace(next_i, i->join_type, next_start+tangent, next_start, i->IsVisible() && next_i->IsVisible()); //mark=true
                    Edge::RemoveLoop(r, start_orig_us, next_i);
                    //i and next_i may be destroyed here. Search loops from start_orig_us again
                    Edge::RemoveLoop(r, start_orig_us, end_orig_next);
                }

                i = --end_orig_next; //step the cycle
                if (done)
                    break;
            } //for cycle through edges

            //mark all non-inverse edges as trivial
            for (auto &e : r)
                if (e.cross_type != Edge::CP_INVERSE)
                    e.cross_type = Edge::CP_TRIVIAL;
        } else if (replace_cp_inverse_to_extended && has_remaining_cp_inverse) {
            //this branch is called if need_to_process is false (so we did not
            //resolve any CP_INVERSE since the last run) but we do have CP_INVERSE joins left
            //This means we cannot resolve these, so we try doing them as if they were CP_EXPAND
            for (auto &e : r)
                if (e.cross_type == Edge::CP_INVERSE)
                    e.cross_type = Edge::CP_EXTENDED;
            need_to_process = true;
        }

        ////avoid round in subsequent rounds, as metadata values are no longer valid
        ////and cannot be used during CreateRoundForExpand(). If this does not work,
        ////the we will need to compute a suitable center for such rounded edges by re-Expanding
        ////the edges in question and finding their crosspoints (if any), but that is probably expensive.
        //if (type == EExpandType::EXPAND_MITER_ROUND) type = EExpandType::EXPAND_MITER_BEVEL;
        //else if (type == EExpandType::EXPAND_ROUND) type = EExpandType::EXPAND_BEVEL;
    } while (need_to_process && r.size());

#ifdef _DEBUG
    if (0)
        contour::debug::Snapshot("test2.pdf", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5, r);
#endif

    //Insert straight segments for CP_INVERSE joins (not visible) & try to combine edges
    for (auto i = r.begin(); i!=r.end(); i++)
        if (i->cross_type == Edge::CP_INVERSE) {
            const auto next_i = r.next(i);
            r.emplace(next_i, i->join_type, i->GetEnd(), next_i->GetStart(), false); //mark=true
        }
    for (auto i = r.begin(); i!=r.end(); i++) {
        const auto next_i = r.next(i);
        if (always_simplify_result || next_i->original_edge<0)
            if (i->CheckAndCombine(*next_i))
                r.erase(next_i);
    }
    //Ok, now we have the expanded contour in 'r', hopefully all its edges connected to the
    //next one.
    return r;
}

//p1->p0->p2 procession is convex/concave
//-1: concave, +1 convex, 0: could not determine
int determine_concave(const XY&p1, const XY&p0, const XY&p2)
{
    switch (triangle_dir(p1, p0, p2)) {
    default: _ASSERT(0); FALLTHROUGH;
    case ETriangleDirType::CLOCKWISE: return 1;
    case ETriangleDirType::COUNTERCLOCKWISE: return -1;
    case ETriangleDirType::IN_LINE:
        //if 'start' and 'next.end' are at the same side of 'end' this is a concave join.
        if (fabs(p0.y-p1.y)<fabs(p0.x-p1.x)) {
            if ((p1.x-p0.x<0) != (p2.x-p0.x<0)) return 1;
        } else {
            if ((p1.y-p0.y<0) != (p2.y-p0.y<0)) return 1;
        }
        FALLTHROUGH; //if the tangents point to the same dir - if curvy the angles will decide
    case ETriangleDirType::A_EQUAL_C: //not a sign of degenerate edges
        return 0; //could not decide
    }
}

/** Determines if vertex u (edge u and edge u+1) join in a convex or concave fashion.
 * In-line joins, when next is the continuation of us counts as convex.
 * On error, such as degenerate edges, we return true.*/
bool SimpleContour::IsConcaveJoin(size_t u) const
{
    _ASSERT(at(u).GetEnd().test_equal(at_next(u).GetStart()));
    XY p1 = at(u).PrevTangentPoint(1);
    XY p2 = at_next(u).NextTangentPoint(0);
    //determine cosine of the angle
    //from cosine c2 = a2+b2 - 2abcosY
    const XY p0 = at(u).GetEnd();
    double cos = (p0.DistanceSqr(p1)+p0.DistanceSqr(p2) - p1.DistanceSqr(p2))/(sqrt(p0.DistanceSqr(p1)*p0.DistanceSqr(p2))*2);
    //if cos is not close to 1 (0.999 is around 2.5 degrees), we have a differing enough vertex
    if (cos<0.999) switch (determine_concave(p1,p0,p2)) {
    case -1: return true;
    case +1: return false;
    }
    //determine with angles or with taking more edges
    //test if we cross each other
    XY r[9];
    double pos_my[9], pos_other[9];
    const int num = at(u).HullOverlap(at_next(u), true)
        ? at(u).CrossingWithOverlap(at_next(u), true, r, pos_my, pos_other) //num<0 if the two edges overlap
        : 0;
    if (num<0) return true; //overlapping edges are concave by default.
    if (num) {
        //if we have crosspoints, take the one closes to the vertex, halven the positions
        const double * pm = std::max_element(pos_my, pos_my+abs(num));
        p1 = at(u).Pos2Point(*pm/2+0.5);
        p2 = at_next(u).Pos2Point(pos_other[pm-pos_my]/2);
    } else {
        //try determining from endpoints
        p1 = at(u).GetStart();
        p2 = at_next(u).GetEnd();
    }
    switch (determine_concave(p1, p0, p2)) {
    case -1: return true;
    case +1: return false;
    case 0: return true; //one of the edges is too short, we give up.
    }
    return true;
}

/** Expands a shape.
 *
 * Expansion aims to create a shape whose edges run parallel along the edges of the
 * original, but at `gap` pixels away. Positive `gap` values result in actual expansion
 * negative values in shrinkage (the new shape is inside the original).
 * The function works for counterclockwise shapes, the meaning of the sign of `gap` is
 * reversed.
 *
 * After the expansion, we check if there are edges crossing each other and
 * remove them by walking. In this space we also may end up in multiple separate
 * SimpleContours. (E.g., shrinking a dog-bone shape may lead us to two circles,
 * like `O=O` becomes `o o`.) This is why the result is stored in a full Contour
 * which may hold multiple SimpleContours.
 *
 * @param [in] type There are multiple typical ways to handle edge joins, see EExpandType.
 * @param [in] gap The amount of expansion/shrinkage. No-op if zero.
 * @param miter_limit Tells us that with EXPAND_MITER_* how long the miter edges can be
 *                    in terms of the `gap`.
 *                    E.g., miter_limit=2 says they can be as long as `2*gap`.
 *                    The rest is cut away with a bevel-like edge.
 * @returns The expanded shape (completely untangled)*/
Contour SimpleContour::CreateExpand(EExpandType type, double gap, double miter_limit) const
{
    if (gap==0 || size()==0)
        return Contour{ *this }; //no change
    MetaDataList r = ExpandHelper(type, gap, miter_limit, 0.01);
    if (r.size() == 0) return {}; //empty shape
    Contour sp_r;
    sp_r.first.outline.dirty_bb();
    sp_r.first.outline.area = infa;
    sp_r.first.outline.edges.reserve(r.size());
    for (auto &e : r)
        sp_r.first.outline.edges.push_back(e);

#ifdef _DEBUG
    SimpleContour sp_r2 = SimpleContour::UnsafeMake(Path(sp_r.first.outline.GetEdges())); //take a copy
#endif

    sp_r.first.outline.Sanitize(EXPAND_TOLERANCE); //remove degenerate or very short edges
    //OK, now untangle, this does not need a bounding box, avoid calculating one.
    Contour ret;
    ret.Operation(GetClockWise() ? Contour::EXPAND_POSITIVE : Contour::EXPAND_NEGATIVE, std::move(sp_r));
    _ASSERT(ret.IsEmpty() || !ret.GetBoundingBox().IsInvalid());

#ifdef _DEBUG
    if (0) {
        contour::debug::Snapshot("test.pdf", EC::STRINGFORMAT, "\\mn(1)", EC::START, "%n", EC::LINEWIDTH, 0.1, EC::MARKERWIDTH, 0.5,
                                 ColorType(255, 0, 0), *this, ColorType(0, 0, 255), sp_r2, ColorType(0, 127, 0), ret[0].Outline());
        std::string r = edges.Dump(true);
        std::string a = sp_r2.edges.Dump(true);
        std::string b = ret.Dump(true);
    }
#endif
    return ret;
}


/** A helper to Expand2D.
 * It connects two edges, which ended up disconnected after being Expand2D'd.
 * @param [in] gap Specifies how much to expand.
 * @param a The series of edges on which to work.
 * @param [in] original_last The index of the first edge in 'a' to connect.
 * @param [in] next The index of the second edge in 'a' to connect. Edges used to connect
 *                  will be inserted before this edge.
 * @param [in] my_index The index of the edge in 'this' corresponding to 'a[next]'.
 * @param [in] last_type The ending direction of 'a[original_last]'
 * @param [in] stype The starting direction of 'a[next]'
 */
void SimpleContour::Expand2DHelper(const XY &gap, Path &a,
                                   size_t original_last, size_t next, size_t my_index,
                                   int last_type, int stype) const
{
    XY cp;
    double pos_a, pos_b;
    if (Edge::CP_REAL == a[original_last].FindExpandedEdgesCP(a[next], cp,
                         a[original_last].NextTangentPoint(1),
                         a[next].PrevTangentPoint(0), true, true, pos_a, pos_b)) {
        a[original_last].SetEndIgn(cp, pos_a);
        a[next].SetStartIgn(cp, pos_b);
        return;
    }
    const bool turn_clwise = CLOCKWISE == triangle_dir(PrevTangentPoint({my_index, 0}),
                                                       at(my_index).GetStart(),
                                                       NextTangentPoint({my_index, 0}));
    if (last_type && !(last_type & 1) && stype == -last_type && turn_clwise) {
        cp = a[original_last].GetEnd();
        //insert an extra point only if we have a sharp convex vertex
        switch (last_type) {
        case +2: cp.x += 2*gap.x; break;
        case -2: cp.x -= 2*gap.x; break;
        case +4: cp.y -= 2*gap.y; break;
        case -4: cp.y += 2*gap.y; break;
        default: _ASSERT(0); break;
        }
        const Edge A(a[original_last].GetEnd(), cp, !!a[original_last].IsVisible());
        const Edge B(cp, a[next].GetStart(), !!a[next].IsVisible());
        a.insert(a.begin()+original_last+1, A);
        a.insert(a.begin()+original_last+2, B);
        return;
    }
    const Edge C(a[original_last].GetEnd(), a[next].GetStart(), a[original_last].IsVisible() && a[next].IsVisible());
    a.insert(a.begin()+original_last+1, C);
}

/** Expand2D the shape.
 *
 * See the contour library definition on what Expand2D means.
 *
 * @param [in] gap The rectangle used for the expansion.
 * @returns The result.
 */
Contour SimpleContour::CreateExpand2D(const XY &gap) const
{
    //if we do nothing, we return us
    if (gap.x==0 && gap.y==0)
        return Contour{ *this };
    //if we shrink too much we return empty
    if ((GetBoundingBox().x.Spans()<-2*gap.x && GetClockWise()) ||
        (GetBoundingBox().y.Spans()<-2*gap.y && GetClockWise()) ||
        (GetBoundingBox().x.Spans()<2*gap.x && !GetClockWise()) ||
        (GetBoundingBox().y.Spans()<2*gap.y && !GetClockWise()) ||
        IsEmpty())
        return Contour();

    Contour r2;
    int first_type, last_type;
    at(0).CreateExpand2D(gap, r2.first.outline.edges, first_type, last_type);
    for (size_t u=1; u<size(); u++) {
        int stype, etype;
        const size_t original_last = r2.first.outline.edges.size()-1;
        at(u).CreateExpand2D(gap, r2.first.outline.edges, stype, etype);
        Expand2DHelper(gap, r2.first.outline.edges, original_last, original_last+1, u, last_type, stype);
        last_type = etype;
    }
    //Now meld last and first
    Expand2DHelper(gap, r2.first.outline.edges, r2.first.outline.edges.size()-1, 0, 0, last_type, first_type);

    _ASSERT(r2.first.outline.edges.size()==0 || r2.first.outline.edges.front().GetStart()==r2.first.outline.edges.back().GetEnd());

    r2.first.outline.Sanitize(); //remove degenerate or very short edges, make them connect
    if (r2.first.outline.size()==0) return Contour();
    r2.first.outline.dirty_bb();
    r2.first.outline.area = infa;
   //OK, now untangle, this does not need a bounding box, avoid calculating one.
    Contour res;
    res.Operation(GetClockWise() ? Contour::EXPAND_POSITIVE : Contour::EXPAND_NEGATIVE, std::move(r2));
    _ASSERT(res.IsEmpty() || !res.GetBoundingBox().IsInvalid());
    //XXX Fix this better:
    if ((gap.x<0) == GetClockWise()) {
        //Kill those cwh's which are not entirely inside the original
        Contour tmp;
        for (HoledSimpleContour& hsc : res)
            if (RelationTo(hsc.outline) == REL_B_INSIDE_A)
                tmp.append(std::move(hsc));
        res.swap(tmp);
    }
    return res;
}

/** Determines the relation of two shapes with no prior assumptions. */
EContourRelationType SimpleContour::RelationTo(const SimpleContour &c) const
{
    if (c.IsEmpty())
        return IsEmpty() ? REL_BOTH_EMPTY : REL_B_IS_EMPTY;
    if (IsEmpty())
        return REL_A_IS_EMPTY;
    if (!GetBoundingBox().Overlaps(c.GetBoundingBox())) return REL_APART;
    std::vector<CPData> cps;
    //Crosspoints returns no cps, but true for exactly the same contours
    if (CrossPoints(cps, c.edges.begin(), c.edges.end(), true, &c.GetBoundingBox(), CalcRelation))
        return REL_SAME;
    //If there is any crossing CP, we must overlap
    if (std::ranges::any_of(cps, [](const CPData& d) { return d.type.rel==CPRel::Cross; }))
        return REL_OVERLAP;
    //Perhaps here we could evaluate the touching crosspoints to see relation for touching
    //contours as an optimization. Left as exercise to the reader for now.
    //The two shapes are apart or one inside the other, safe to call this
    return CheckContainment(c);
}

/** Determine the relative vertical distance between two shapes.
 *
 * This is a helper for OffsetBelow, that is called if the x range of bounding boxes
 * actually overlap and there is a chance of resulting in an offset less than infinity.
 */
double SimpleContour::do_offsetbelow(const SimpleContour &below, double &touchpoint, double offset) const
{
    if (GetBoundingBox().x.Overlaps(below.GetBoundingBox().x) &&
              GetBoundingBox().y.till+offset > below.GetBoundingBox().y.from)
        for (size_t i = 0; i<size(); i++)
            for (size_t j = 0; j<below.size(); j++)
                offset = at(i).OffsetBelow(below.at(j), touchpoint, offset);
    return offset;
}

/** Creates a cross-section of the SimpleContour along a vertical line.
 *
 * @param [in] x The x coordinate of the vertical line
 * @param section A DoubleMap containing true for points inside the shape, false for outside. May contain entries already.
 */
void SimpleContour::VerticalCrossSection(double x, DoubleMap<bool> &section) const
{
    double y[3], pos[3];
    int forward[3];
    for (size_t i=0; i<size(); i++) {
        const int num = at(i).CrossingVertical(x, y, pos, forward);
        for (int j = 0; j<num; j++)
            if (forward[j])
                section.Set(y[j], forward[j] == (GetClockWise()?1:-1));
    }
}

/** Creates a 'gap' wide mesh gradient at our perimeter, but completely within us.
  * 'gap' is the width of the gradient, {r,g,b,a}1 is the inner color, {r,g,b,a}2 is the outer.
  * If shrinking us makes us fall into pieces, we return nullptr as sign of failure.
  * We also return the inner edge of the mesh, so that it can be filled solid.*/
std::pair<cairo_pattern_t *, Contour>
SimpleContour::CairoMeshGradient(double gap, double r1, double g1, double b1, double a1,
                                 double r2, double g2, double b2, double a2,
                                 EExpandType type, double miter_limit) const
{
    _ASSERT(gap>0);
    std::pair<cairo_pattern_t *, Contour> ret = { nullptr, CreateExpand(type, -gap, miter_limit) };
    //if both original shape and its shrunken version is a single contiguous contour,
    //we can do a mesh gradient shadow
    if (ret.second.size()!=1 || ret.second[0].HasHoles())
        return ret;
    cairo_pattern_t *pattern = cairo_pattern_create_mesh();
    const SimpleContour &sh = ret.second[0].outline;
    const MetaDataList r = sh.ExpandHelper(type, gap, miter_limit, 0.1, false);
    //search the last edge corresponding to an original edge
    auto i = std::find_if(r.rbegin(), r.rend(), [](auto &e) { return e.original_edge>=0; });
    if (i==r.rend()) //no edge with any correspondence to original edges. Also handles the case of r.size()==0
        return ret;
    XY last_vertex = sh[i->original_edge].GetEnd();
    for (auto &e : r) {
        cairo_mesh_pattern_begin_patch(pattern);
        cairo_mesh_pattern_move_to(pattern, e.GetStart().x, e.GetStart().y);
        if (e.IsStraight())
            cairo_mesh_pattern_line_to(pattern, e.GetEnd().x, e.GetEnd().y);
        else
            cairo_mesh_pattern_curve_to(pattern, e.GetC1().x, e.GetC1().y,
                                                 e.GetC2().x, e.GetC2().y,
                                                 e.GetEnd().x, e.GetEnd().y);
        if (e.original_edge>=0) {
            const Edge &o = sh[e.original_edge];
            cairo_mesh_pattern_line_to(pattern, o.GetEnd().x, o.GetEnd().y);
            if (o.IsStraight())
                cairo_mesh_pattern_line_to(pattern, o.GetStart().x, o.GetStart().y);
            else
                cairo_mesh_pattern_curve_to(pattern, o.GetC2().x, o.GetC2().y,
                                                     o.GetC1().x, o.GetC1().y,
                                                     o.GetStart().x, o.GetStart().y);
            last_vertex = o.GetEnd();
        } else {
            cairo_mesh_pattern_line_to(pattern, last_vertex.x, last_vertex.y);
            cairo_mesh_pattern_line_to(pattern, last_vertex.x, last_vertex.y);
        }
        cairo_mesh_pattern_line_to(pattern, e.GetStart().x, e.GetStart().y);
        cairo_mesh_pattern_set_corner_color_rgba(pattern, 0, r2, g2, b2, a2);
        cairo_mesh_pattern_set_corner_color_rgba(pattern, 1, r2, g2, b2, a2);
        cairo_mesh_pattern_set_corner_color_rgba(pattern, 2, r1, g1, b1, a1);
        cairo_mesh_pattern_set_corner_color_rgba(pattern, 3, r1, g1, b1, a1);
        cairo_mesh_pattern_end_patch(pattern);
    }
    ret.first = pattern;
    return ret;
}

/** Calculates the distance between two shapes by finding their two closest points.
 *
 * @param [in] o The other shape to take the distance from.
 * @param ret We return the distance of the two closest points and the two points themselves.
 *            Distance is negative one is inside the other, zero if partial overlap only (two points equal)
 *            'Inside' here ignores clockwiseness. Distance is `CONTOUR_INFINITY` if one of the shapes is empty.
 *            Note that `ret` can contain the result of previous searches, we update it if we find two points
 *            with a smaller distance (in absolute value).
 *
 * Note that we do not check if the bounding box of the two shapes are further apart than the distance received in
 * `ret`. We assume caller does not call us in that case, but only if needed.
 */
void SimpleContour::Distance(const SimpleContour &o, DistanceType &ret) const noexcept
{
    if (IsEmpty() || o.IsEmpty()) return;
    DistanceType running = edges.Distance(o.edges);
    //now check if one is in the other - they cannot cross each other or else d would be 0
    _ASSERT(RelationTo(o)!=REL_OVERLAP || running.distance==0);
    if (IsWithin(o[0].GetStart()) == WI_INSIDE || o.IsWithin(at(0).GetStart()) == WI_INSIDE)
        running.MakeAllInside();
    ret.Merge(running);
}

/** Calculates the distance between a point and us by finding our closest point.
 * @param [in] o The point to take the distance from.
 * @param [out] ret We return the point on our contour closes to `o`.
 * @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty. */
double SimpleContour::Distance(const XY &o, XY &ret) const noexcept
{
    if (IsEmpty()) return CONTOUR_INFINITY;
    DistanceType d = edges.Distance(o);
    ret = d.point_on_me;
    return IsWithin(o) == WI_INSIDE ? -d.distance : d.distance;
}

/** Calculates the distance between a point and us by finding our closest point and returns two tangent points.
 * Same as Distance(const XY &o, XY &ret), but in addition we return two tangent points
 * from the tangent of the shape at `ret`. See @ref contour for a description of tangents.
 *
 * @param [in] o The point to take the distance from.
 * @param [out] ret We return the point on our contour closes to `o`.
 * @param [out] t1 The forward tangent point.
 * @param [out] t2 The backward tangent point.
 * @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty.*/
double SimpleContour::DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const
{
    if (IsEmpty()) return CONTOUR_INFINITY;
    DistanceType d = edges.Distance(o);
    ret = d.point_on_me;
    t1 = PrevTangentPoint(d.pos_on_me);
    t2 = NextTangentPoint(d.pos_on_me);
    return IsWithin(o) == WI_INSIDE ? -d.distance : d.distance;
}


/** Returns a cut of the shape along an edge.
 *
 * Takes all crosspoints of a (finite length, possibly curvy) edge and the shape
 * and returns the two outermost. We return `pos` values corresponding to the edge,
 * thus both values are between 0 and 1. It is possible that a single value is
 * returned if the edge touches the shape or if its start is inside the shape, but
 * its end is outside. If the edge does not cross or touch the shape, an invalid
 * range is returned.*/
Range SimpleContour::Cut(const Edge &s) const
{
    Range ret;
    ret.MakeInvalid();
    XY dummy1[9];
    double pos[9], dummy2[9];
    for (unsigned u = 0; u<size(); u++)
        for (int i = at(u).Crossing(s, false, dummy1, dummy2, pos)-1; i>=0; i--)
            ret += pos[i];
    return ret;
}


/** Returns a cut of the shape along an edge with edge indices and pos.
 *
 * Takes all crosspoints of a (finite length, possibly curvy) edge and the shape
 * and returns the two outermost. We return `pos_other` values corresponding to the edge,
 * thus both values are between 0 and 1. It is possible that a single value is
 * returned (first==second) if the edge touches the shape or if its start is inside the shape, but
 * its end is outside. If the edge does not cross or touch the shape, an invalid
 * range of [first.other.pos> second.other.pos] is returned.*/
std::array<CPData, 2> SimpleContour::CutEx(const Edge &s) const
{
    std::array<CPData, 2> ret;
    auto cps = edges.CrossPoints(true, &GetBoundingBox(), &s, &s+1);
    if (cps.size()) {
        auto [min, max] = std::minmax_element(cps.begin(), cps.end(), [](const CPData &a, const CPData &b) { return a.other.pos<b.other.pos; });
        ret[0] = *min;
        ret[1] = *max;
    } else {
        //invalidate
        ret[0].other.pos = 1;
        ret[1].other.pos = 0;
    }
    return ret;
}

/** Return the crosspoints of 'e' with us.*/
bool SimpleContour::CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do, bool swap) const
{
    const size_t orig_ret_size = ret.size();
    bool r = edges.CrossPoints(ret, true, is_bb_dirty() ? nullptr : &boundingBox,
                               &e, &e+1, false, is_bb_dirty() ? nullptr : &static_cast<const Block &>(e.CreateBoundingBox()), to_do, swap);
    for (size_t i = orig_ret_size; i<ret.size(); i++)
        (swap ? ret[i].contour_other : ret[i].contour_me) = this;
    return r;
}


/** Chop the Contour into paths at points. We return an empty vector if
 * a PathPos is invalid or you give us only one.
 * We take pos by value because we sort it.
 * If two pos in the vector are equal, we return an empty Path there, unless 'remove_empty'*/
std::vector<Path> SimpleContour::SplitAt(std::vector<PathPos> pos, bool remove_empty) const
{
    std::vector<Path> ret;
    if (pos.size()<2) return ret;
    for (auto& p : pos)
        if (p.pos == 1)
            p = {.edge = next(p.edge), .pos = 0};
    std::sort(pos.begin(), pos.end());
    ret.reserve(pos.size());
    for (unsigned u = 0; u<pos.size(); u++) {
        ret.emplace_back();
        const unsigned uu = pos.size()<=(u+1) ? 0 : u+1;
        if (pos[u].edge==pos[uu].edge) {
            ret.back().emplace_back(at(pos[u].edge), pos[u].pos, pos[uu].pos);
        } else {
            if (pos[u].pos<1)
                ret.back().emplace_back(at(pos[u].edge), pos[u].pos, 1);
            for (size_t v = next(pos[u].edge); v!=pos[uu].edge; v = next(v))
                ret.back().push_back(at(v));
            if (pos[uu].pos>0)
                ret.back().emplace_back(at(pos[uu].edge), 0, pos[uu].pos);
        }
        if (remove_empty && ret.back().size()==0)
            ret.pop_back();
    }
    return ret;
}

/** Returns a cut of the shape along an edge.
 *
 * Same as Cut(const Edge &s), but also returns the coordinates of the positions
 * returned and a forward tangent point for them. In case the edge cuts the shape
 * at a non-smooth vertex, then we take the tangent line of both edges of the vertex
 * average them and return a point from this middle line in the forward direction.
 *
 * @param [in] e The edge to cut with.
 * @param [out] from Values correspond to the position returned in `from`. `first` is the coordinate of the point, `second` is the forward tangent point.
 * @param [out] till Values correspond to the position returned in `till`. `first` is the coordinate of the point, `second` is the forward tangent point.
 * @return The `pos` value corresponding to the two outmost crosspoints.
 */
Range SimpleContour::CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const
{
    Range ret;
    ret.MakeInvalid();
    if (!GetBoundingBox().Overlaps(e.CreateBoundingBox())) return ret;
    XY point[2];
    double pos[2], scpos[2];
    for (unsigned u = 0; u<size(); u++)
        for (int i = at(u).Crossing(e, false, point, scpos, pos)-1; i>=0; i--) {
            if (ret.IsWithin(pos[i])) continue;
            XY tangent = at(u).NextTangentPoint(scpos[i]);
            if (scpos[i] == 0) {
                const XY prev_t_norm = (at_prev(u).NextTangentPoint(1.0)- at(u).GetStart()).Normalize();
                const XY tangen_norm = (tangent - at(u).GetStart()).Normalize();
                const XY delta = prev_t_norm+tangen_norm;
                if (delta.length_sqr()<1e-8)
                    tangent.Rotate90CW();
                else
                    tangent = at(u).GetStart() + delta/2;
            }
            if (ret.from > pos[i]) {
                ret.from = pos[i];
                from.first = point[i];
                from.second = tangent;
            }
            if (ret.till < pos[i]) {
                ret.till = pos[i];
                till.first = point[i];
                till.second = tangent;
            }
        }
    return ret;
}


/** Creates a cross-section (or cut) of the SimpleContour along a straight line.
 *
 * We return all crosspoints of an infinite line specified by `A` and `B`.
 * We return them in `pos` terms, that is, at `A` we return 0, at `B` we return 1 and
 * linearly interpolate and extrapolate between them and outside, respectively.
 *
 * @param [in] A One point of the infinite line.
 * @param [in] B Another point of the infinite line.
 * @param [out] map A DoubleMap containing true for points inside the shape, false for outside.
 */
void SimpleContour::Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const
{
    const Range ret = GetBoundingBox().Cut(A, B); //also tests for A==B or invalid bb, which catches empty "this"
    if (ret.IsInvalid()) return;
    const Edge s(A+(B-A)*(ret.from-0.1), A+(B-A)*(ret.till+0.1));
    DoubleMap<bool> local;
    XY dummy1[2];
    double pos[2], dummy2[2];
    for (unsigned u = 0; u<size(); u++)
        for (int i = at(u).Crossing(s, false, dummy1, dummy2, pos)-1; i>=0; i--)
            if (fabs(A.x-B.x) > fabs(A.y-B.y)) {
                const double p = s.GetStart().x + (s.GetEnd().x-s.GetStart().x)*pos[i];
                local.Set((p-A.x)/(B.x-A.x), true);
            } else { //do it in y coordinates
                const double p = s.GetStart().y + (s.GetEnd().y-s.GetStart().y)*pos[i];
                //p is now the y coordinate of the two CP, convert to "pos" on A-B
                local.Set((p-A.y)/(B.y-A.y), true);
            }
    if (local.size()<=1) return;
    const bool clockwise = GetClockWise();
    for (DoubleMap<bool>::const_iterator i=++local.begin(), j=local.begin(); i!=local.end(); j=i,i++)
        if (inside(IsWithin(A+(B-A)*(i->first+j->first)/2)))
            map.Set(Range(j->first, i->first), clockwise);
}

/** Creates a C++ representation of this simple contour as a Contour.
 * Calls Edge->Dump(precise).
 * @param [in] precise When set, we emit the hex representation of doubles, else a human readable one.
 * @returns the full string.*/
std::string SimpleContour::Dump(bool precise) const {
    Path p;
    AppendToPath(p);
    return "SimpleContour::UnsafeMake("+p.Dump(precise, "Path", "    ")+")";
}

SimpleContour SimpleContour::UnsafeMake(Path&& v) {
    SimpleContour ret;
    ret.dirty_bb();
    ret.area = infa;
    ret.edges.swap(v);
    return ret;
}

} //namespace contour bezier
