/*
    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_path.h Declares templates EdgeList and EdgeVector and class Path, plus
 * a host of nice algorithms that work on list of edges represented by an iterator range.
 * @ingroup contour_files
 */

#if !defined(CONTOUR_PATH_H)
#define CONTOUR_PATH_H

#include <vector>
#include <list>
#include <iterator>
#include <optional>
#include "contour_edge.h"
#include "cairo.h"

namespace contour {


class SimpleContour;
class ContourList;
class HoledSimpleContour;
class Contour;

/** Little helper to move append one series of edges to another.
 * We merge the connecting edges if possible at the end of c1.
 * If, as a result we end up with a dot-like edge, so be it.*/
template <typename Container1, typename Container2>
void MoveAppend(Container1 &c1, Container2 &&c2)
{
    auto i = c2.begin();
    while (c1.size() && i!=c2.end())
        if (c1.back().CheckAndCombine(*i))
            i++;
        else
            break;
    std::move(i, c2.end(), std::inserter<Container1>(c1, c1.end()));
    //use this if both c1 and c2 are lists: c1.splice(c1.end(), c2);
}


/** A type used when initializing a contour from a path */
enum class ECloseType
{
    IGNORE_OPEN_PATH,       ///<Do not close all open connected sub-path
    CLOSE_ALL_CONNECTED,    ///<Close all connected sub-path
    CLOSE_INVISIBLE         ///<Close all connected sub-path with invisible edges
};

class SimpleContour;

/** @defgroup CrossPoints A family of functions searching for crosspoints of edges, paths and contours.
 * @ingroup contour
 *
 * 'Edges', 'EdgeVector<>'s, 'Path's, 'SimpleContour's and 'Contour's have a
 * set of functions called CrossPoints() that return a set of crosspoints
 * (or append to a suppled vector). A "crosspoint" here is any point shared by
 * the two paths. Calculating these crosspoints is quite expensive, especially
 * for beziers.
 *
 * These functions have a set of common parameters.
 *  - booleans whether the relevant paths are closed. This is only
 *    relevant if the path starts and ends on the same coordinate and
 *    neither of the first & last edges are invisible (or we do not ignore
 *    invisible edges). In this case we merge any crosspoints at the
 *    start/end of the given path and remove them if they are part of an
 *    overlap section. These booleans are automatically supplied by
 *    contours (which are closed by definition).
 *  - optional bounding boxes for the two paths: this is performance
 *    optimization to avoid searching crosspoints if not even the bounding
 *    boxes overlap. The bounding boxes are automatically supplied by
 *    contours if we have them computed.
 *  - Instructions on what to compute about crosspoints (CPTasks), such as
 *    ignoring invisible edges, removing inner crosspoints along an
 *    overlapping section of the two paths (only the start and end of the
 *    overlapping section kept); and to calculate the relation of the two
 *    paths (which side of each other they are or in which direction do
 *    they cross each other).
 *    Use the contour::{VisibleOnly,OverlapOnly,KeepInsideOverlap,CalcRelation}
 *    flags (possibly combined with the + sign).
 *
 * They either return a set of CPData structures - one for each crosspoint
 * or they append to a suppled vector of these and return a boolean. The latter
 * is true if and only if the two paths are both closed and completely
 * overlapping, that is, they include exactly the same points on the surface.
 * They may be in opposite direction (clockwise & counterclockwise) and/or
 * their starting vertex may be different; or their edges may not even be the
 * same (e.g., segmented into smaller edges). The reason to return true is to
 * show that the whole of the two paths are one overlapping segment and there
 * is no start/end point for this segment and by default all crosspoints are
 * inside overlapping segments are removes - which would be all of them in this
 * case. To differentiate this case from the "no crosspoints at all" case we
 * return all the crosspoints and true. */

/** Flag values for CPRel */
enum class Tri : unsigned char {
    True,     ///<The flag is true (same dir or left side)
    False,    ///<The flag is false (opposite dir or right side)
    Undefined ///<The value of the flag cannot be computed, is not meaningful or its computation is not requested
};

inline std::string to_string(Tri t) { return t==Tri::True ? "True" : t==Tri::False ? "False" : "Undefined"; }

constexpr Tri operator !(Tri t) noexcept { return t==Tri::True ? Tri::False : t==Tri::False ? Tri::True : Tri::Undefined; }

/** Describes how two path behave around a crosspoint in relation to each other.
 * @ingroup CrossPoints */
struct CPRel {
    /** Enumerates how the two path may relate at a crosspoint.
     * If one of the paths end/start in the CP, we will only use
     * Touch and Join/Leave and set me_end and/or other_end.*/
    enum CPRelType : unsigned char {
        /** The two paths touch here (they have a single point in common and
         * either remain on the same side of each other or one of them end
         * here). 'me_left_side' is defined only the 'me' path does not end
         * or start here: then it is True if the other path is fully on the left
         * side of the 'me' path around this CP, 'other_left_side' likewise.
         * If neither paths end here then 'same_dir' shows if the two path's
         * fw dir are the same at the touchpoint; else Undefined.
         * If one or both of the paths is degenerate here (their fw and bw
         * angle is the same such as for (0,0)->(1,1)->(0,0), both left_side
         * and the same_dir flags are set to Undefined.*/
        Touch,
        /** The two paths cross here (they have a single point in common,
         * change sides and none of them ends here). 'my_left_side' is True
         * if the 'other' path crosses the 'me' from the right side of 'me' to
         * the left side of it (i.e., it ends on the left side of 'me'),
         * likewise 'other_left_side' - but these must always be opposite
         * if you think about it. For closed contours a true 'X_left_side'
         * means going from the inside to the outside.
         * 'same_dir' is left Undefined.*/
        Cross,
        /** This is a crosspoint at the end of an overlapping segment between
         * the two paths, with none of the paths ending here. 'same_dir' is
         * always defined and is False if the two path go opposite direction
         * in the overlapping segment. 'my_left_side' is True if the other path
         * is on the left side of the 'me' path (naturally only on the non-
         * overlapping side of the crosspoint). 'other_left_side' likewise.
         * me/other_overlap_fw is always defined and tells us if we have the
         * overlap in the fw direction (or in the backward one if False).
         * If one or both of the paths is degenerate here (their fw and bw
         * angle is the same such as for (0,0)->(1,1)->(0,0), both left_side
         * and the same_dir flags are set to Undefined. In this case we
         * do set overlap_fw, but even if it is True, we may have an overlap
         * also in the backward direction.*/
        Fork,
        /** This crosspoint is either in the middle of an overlapping
         * segment between the two paths or at the end of it with one of the
         * paths starting or ending here. Neither of 'my_left_side' nor
         * 'other_left_side' is defined. 'same_dir' is always defined for
         * good paths and is False if the two paths go opposite direction in
         * the overlapping segment. me/other_overlap_fw is always defined
         * and tells us if we have an overlap in the fw direction (not very
         * useful here, can only be false if the path ends in this cp).
         * If one or both of the paths is degenerate here (their fw and bw
         * angle is the same such as for (0,0)->(1,1)->(0,0), both left_side
         * and the same_dir flags are set to Undefined.*/
        Overlap
    };
    /** The relation of the two paths.
     * if 'had_rel_computed' is unset, then touchpoints and
     * crosspoints are not distinguished and both are indicated
     * as 'Cross'.*/
    CPRelType rel : 3;
    /** If the second path is on the left side of the first or if
     * it crosses from right-to-left through the first path.
     * Defined only if 'had_rel_computed' is set and only for
     * Cross and for Touch/Fork if the first path does not end
     * in the CP. */
    Tri me_left_side : 2;
    /** If the first path is on the left side of the first or if
     * it crosses from right-to-left through the second path.
     * Defined only if 'had_rel_computed' is set and only for
     * Cross and for Touch/Fork if the second path does not end
     * in the CP.*/
    Tri other_left_side : 2;
    /** If the two paths goes the same dir (if they touch or have
     * overlaps). Defined only if 'had_rel_computed' is set and
     * only for Fork and for Touch/Overlap if none of the paths
     * end here.*/
    Tri same_dir : 2;
    /** Set if the 'me' path overlaps the other path in its
     * forward direction. Has really meaning with Fork only
     * as in that case we know there is overlap in one dir,
     * and this field tells us which.*/
    Tri me_overlap_fw : 2;
    /** Set if the other path overlaps the 'me' path in its
     * forward direction. Has really meaning with Fork only
     * as in that case we know there is overlap in one dir,
     * and this field tells us which.*/
    Tri other_overlap_fw : 2;
    /** If the first path starts/ends here.
     * Note that an invisible edge may also cause a start/end
     * (if ignored) and non-closed path start/end even if the
     * coordinates of the start/endpoint are the same.*/
    bool me_end : 1;
    /** If the second path starts/ends here.
     * Note that an invisible edge may also cause a start/end
     * (if ignored) and non-closed path start/end even if the
     * coordinates of the start/endpoint are the same.*/
    bool other_end : 1;
    /** If unset, then toucpoints are erroneously categorized
     * as Cross unless one of the paths end here.
     * Also, my_left_side, other_left_side and same_dir are not
     * computed. Note they may still be undefined if meaningless,
     * e.g., for Overlap there is no meaning in left/right side and for
     * Cross there is no meaning in same_dir, etc.*/
    bool had_rel_computed : 1;

    constexpr CPRel inverse() const noexcept { return CPRel{rel, other_left_side, me_left_side, same_dir, other_overlap_fw, me_overlap_fw, other_end, me_end, had_rel_computed}; }

    friend std::string to_string(CPRelType r) {
        switch (r) {
        case Touch: return "touch";
        case Cross: return "cross";
        case Fork: return "fork";
        case Overlap: return "overlap";
        default: return "invalid";
        }
    }
};

/** This prints only the relevant info. */
inline std::string to_string(CPRel d) {
    using namespace std::string_literals;
    std::string ret = to_string(d.rel);
    if (d.had_rel_computed) switch (d.rel) {
    case CPRel::Fork:
        ret.append(d.me_overlap_fw==Tri::True ? ":fw" : d.me_overlap_fw==Tri::False ? ":bw" : ":-");
        ret.append(d.other_overlap_fw==Tri::True ? ":fw" : d.other_overlap_fw==Tri::False ? ":bw" : ":-");
        FALLTHROUGH;
    case CPRel::Touch:
        ret.append(d.me_left_side==Tri::True ? ":l" : d.me_left_side==Tri::False ? ":r" : ":-");
        ret.append(d.other_left_side==Tri::True ? ":l" : d.other_left_side==Tri::False ? ":r" : ":-");
        FALLTHROUGH;
    case CPRel::Overlap:
        if (d.same_dir==Tri::False) ret.append(":opp");
        break;
    case CPRel::Cross:
        ret.append(d.me_left_side==Tri::True ? ":r->l" : d.me_left_side==Tri::False ? ":l->r" : ":?->?");
        ret.append(d.other_left_side==Tri::True ? ":r->l" : d.other_left_side==Tri::False ? ":l->r" : ":?->?");
        break;
    }
    if (d.me_end && d.other_end) ret.append(":both_end");
    else if (d.me_end) ret.append(":p1_ends");
    else if (d.other_end) ret.append(":p2_ends");
    return ret;
}

/** A structure holding information about a crosspoint between two paths.
 * For crosspoints on a vertex joining two connected edges, the path
 * positions ('me' or 'other') may return either the end of the previous edge
 * or the beginning of the subsequent one in an undefined manner. However,
 * for crosspoints on vertices at the end of a segment (either caused by
 * a disconnected or ignored invisible prev/next edge or by the start/endpoint
 * of a path not marked as closed (even if the start/endpoint is the same))
 * we always use the correct edge.
 * Note that this data structure is not invertible. It describes the relation
 * of one path to the other, but one cannot always compute the relation
 * in the reverse direction using only this structure. Specifically, in the
 * case when one of the paths (me) ends at the crosspoint and the edges don't
 * overlap, we would not know from a CPData, which side of the 'other' path
 * 'me' is on. In this case the 'type.left_side' is also undefined, since
 * 'other' is on no specific side of 'me' at me's end.)
 * @ingroup CrossPoints */
struct CPData {
    XY xy;         ///<The coordinates of the crosspoint
    PathPos me;    ///<The position of the crosspoint on the path whose CrossPoints() fn was called
    PathPos other; ///<The position of the crosspoint on the other path
    CPRel type; ///<The type of the crosspoint from the perspective of 'me', the first path
    const SimpleContour *contour_me = nullptr;
    const SimpleContour *contour_other = nullptr;

    /** Returns true, if the 'other' path goes into 'me'.
     * If 'me' is not closed, then it returns true if 'other' crosses 'me'
     * from the left side of 'me' to the right side of 'me'. (It can
     * return Undefined, if 'me' ends in this cross-point.)
     * If 'me' is counterclockwise (i.e., a hole) this returns true if
     * the 'other' goes from the enclosed space (which is considered
     * 'outside' for holes) to the infinity beyond.
     * Only works, if relations for this cp have been computed. Else
     * we return Undefined.
     * If this CP is inside an overlapping segment between 'me' and 'other',
     * we return False.
     * If 'other' starts in this CP towards the inside of 'me', we return
     * False if boundary_included is set (as the CP itself is considered
     * 'inside'), and True if unset.
     * @param [in] boundary_included If true, the boundary (edges) of the
     *        'me' path is considered 'inside'. Thus any 'other' edge
     *        coming from the outside and only touching will return true.
     *        If false, then the 'other' path must fully enter into 'me'.
     *        E.g, if 'other' touches 'me' from the left side (outside),
     *        we return True, if boundary_included is set, False otherwise.
     *        (Note that for non-ending touchpoints both enters() and leaves()
     *        are true if boundary_included is set and 'other' is outside 'me',
     *        (except this touch point); and both enters() and leaves() is
     *        true if boundary_included is unset and 'others' is inside 'me'.)*/
    constexpr Tri enters(bool boundary_included = false) const noexcept {
        if (!type.had_rel_computed) return Tri::Undefined;
        switch (type.rel) {
        case CPRel::Fork:
            if (type.other_overlap_fw==Tri::True  &&  boundary_included) return  type.me_left_side; //'other' may join the boundary from the outside -> counts as enter if boundary is     included in 'me'
            if (type.other_overlap_fw==Tri::False && !boundary_included) return !type.me_left_side; //'other' may leave the boundary to the inside   -> counts as enter if boundary is not included in 'me'
            return Tri::False;
        case CPRel::Cross: return !type.me_left_side; //'other' crosses inside 'me'
        case CPRel::Touch:
            if (type.me_end) return Tri::Undefined;
            if (type.other_end) {
                if (other.pos==0) return !boundary_included ? !type.me_left_side : Tri::False; //'other' starts into 'me' here
                if (other.pos==1) return  boundary_included ?  type.me_left_side : Tri::False; //'other' ends from outside 'me' here
            }
            return boundary_included ? type.me_left_side : !type.me_left_side;
        case CPRel::Overlap: return Tri::False;
        default: return Tri::Undefined;
        }
    }
    constexpr Tri leaves(bool boundary_included = false) const noexcept {
        if (!type.had_rel_computed) return Tri::Undefined;
        switch (type.rel) {
        case CPRel::Fork:
            if (type.other_overlap_fw==Tri::True && !boundary_included) return !type.me_left_side; //'other' may join the boundary from the inside -> counts as leave if boundary is not included in 'me'
            if (type.other_overlap_fw==Tri::False && boundary_included) return  type.me_left_side; //'other' may leave the boundary to the outside -> counts as leave if boundary is     included in 'me'
            return Tri::False;
        case CPRel::Cross: return type.me_left_side; //'other' crosses outside 'me'
        case CPRel::Touch:
            if (type.me_end) return Tri::Undefined;
            if (type.other_end) {
                if (other.pos==0) return  boundary_included ?  type.me_left_side : Tri::False; //'other' starts away from 'me' here
                if (other.pos==1) return !boundary_included ? !type.me_left_side : Tri::False; //'other' ends into 'me' from inside here
            }
            return boundary_included ? type.me_left_side : !type.me_left_side;
        case CPRel::Overlap: return Tri::False;
        default: return Tri::Undefined;
        }
    }
    std::string Dump(bool precise) const { return xy.Dump(precise)+"["+me.Dump()+","+other.Dump()+","+to_string(type)+"]"; }
};


/** Proscribes what the CrossPoints search algorithms should do.*/
struct CPTasks {
    /** If set, we simply ignore invisible edges as if not part of
     * the paths we search crosspoints of. It may significantly
     * reduce the computational effort if there are a lot of
     * invisible edges.*/
    bool ignore_invisible_edges : 1 = false;
    /** If this is set, we remove crosspoints that are not part of
     * a longer, overlapping segments between the paths.
     * Setting it does not much improve performance, just that we
     * handle more crosspoints in return.*/
    bool remove_non_overlap : 1 = false;
    /** If this is unset, we remove crosspoints that are inside a
     * longer overlapping segments. No crosspoints will be of
     * type 'Overlap' after this. Unsetting it causes some work,
     * but only if there were overlapping segments. */
    bool keep_inside_overlap : 1 = false;
    /** If this is set, we calculate how the two paths cross.
     * It takes some power to calculate, especially for more CPs.
     * (So setting 'calculate_overlap' is a good idea.)
     * 'type.same_dir' and 'type.left_side' will not be calculated
     * if this is not set. The 'type.had_rel_computed' is set
     * for CPs detected with this on.*/
    bool calculate_relation : 1 = false;

    constexpr CPTasks operator +(const CPTasks &o) const noexcept {
        return CPTasks{
            ignore_invisible_edges||o.ignore_invisible_edges,
            remove_non_overlap||o.remove_non_overlap,
            keep_inside_overlap||o.keep_inside_overlap,
            calculate_relation||o.calculate_relation
        };
    }
};
/** Use this flag in the CrossPoints() family of functions to
 * ignore invisible edges. In this case the path is assumed to be
 * segmented by invisible edges and you may get 'path end' flags
 * for crosspoints at the end of these segments. You can combine
 * this flag using the + operator with other flags.
 * @ingroup CrossPoints */
constexpr static inline CPTasks VisibleOnly{true, false, false, false};
/** Use this flag in the CrossPoints() family of functions to
 * ignore crosspoints that are not part of an overlapping segment.
 * You can combine this flag using the + operator with other flags.
 * @ingroup CrossPoints */
constexpr static inline CPTasks OverlapOnly{false, true, false, false};
/** Use this flag in the CrossPoints() family of functions to
 * keep and return crosspoints that are inside an overlapping
 * segment. (By default these are removed.)
 * You can combine this flag using the + operator with other flags.
 * @ingroup CrossPoints */
constexpr static inline CPTasks KeepInsideOverlap{false, false, true, false};
/** Use this flag in the CrossPoints() family of functions to
 * calculate the relation of the two paths around each crosspoint.
 * This will fill the 'CPData::type.same_dir' and 'CPData::type.left_side'
 * members and set the 'CPData::type.had_rel_computed' flag. It will
 * also result in a distinction between touchpoints (when the two paths
 * remain on the same side of each other) and actual crosspoints (when
 * the two paths get to the other side of the other at this cp).
 * You can combine this flag using the + operator with other flags.
 * @ingroup CrossPoints */
constexpr static inline CPTasks CalcRelation{false, false, false, true};

constexpr bool same_pos(const PathPos &p1, const PathPos &p2, size_t n, bool closed) noexcept {
    if (p1.edge==p2.edge) return fabs(p1.pos-p2.pos)<1e-6;
    if ((p1.edge+1==p2.edge)
           || (closed && p1.edge==n-1 && p2.edge==0))
        return p1.pos==1 && p2.pos==0;
    if ((p1.edge==p2.edge+1)
           || (closed && p1.edge==0 && p2.edge==n-1))
        return p1.pos==0 && p2.pos==1;
    return false;
}

/** Collects crosspoints between two ranges.
 * The returned crosspoints will contain the zero-based index of the edges.
 * We remove crosspoints at vertices if found twice.
 * We report overlapping segments only at their begin and end by default,
 * but this can be changed in 'to_do'.
 * When closed1 or closed2 are false, we treat the respective path as not
 * closed even if last connects to first; and report overlaps around this
 * place as two independent overlay section separated by first.start==last.end.
 * @param [out] add_to Crosspoints found will be appended to this vector
 *        using push_back() sorted by their 'me' path position.
 *        The 'me' position refers to the first path, the 'other' to the
 *        second.
 * @param [in] first1 The begin iterator of the first range of Edges
 * @param [in] last1 The end iterator of the first range of Edges
 * @param [in] closed1 If the first range wraps around
 * @param [in] bb1 The bounding box of the first path (if known)
 * @param [in] first2 The begin iterator of the second range of Edges
 * @param [in] last2 The end iterator of the second range of Edges
 * @param [in] closed2 If the second range wraps around
 * @param [in] bb2 The bounding box of the second path (if known)
 * @param [in] to_do Governs what edges and crosspoints do we ignore
 *             and what do we calculate for each crosspoint found.
 * @returns True, if a) the two paths are exactly the same (occupy the same
 *          points on the 2D surface irrespective of how their edges are
 *          ordered, sorted or split); and b) both are connected and closed.
 *          In this case we return all the crosspoints found (not removing
 *          any inside the circular overlapping segment of the two paths).
 *
 * @ingroup CrossPoints */
template <typename iter1, typename iter2>
bool CrossPoints(std::vector<CPData> &add_to,
                 iter1 first1, iter1 last1, bool closed1, const Block *bb1,
                 iter2 first2, iter2 last2, bool closed2, const Block *bb2,
                 CPTasks to_do) {
    //Fast exit: if we know both path's bounding boxes and they dont overlap, we are done.
    if (bb1 && bb2 && !bb1->Overlaps(*bb2)) return false;
    if (first1==last1 || first2==last2) return false;
    //If first/last edges do not connect, clear 'closed' flags
    closed1 &= first1->GetStart()==std::prev(last1)->GetEnd();
    closed2 &= first2->GetStart()==std::prev(last2)->GetEnd();
    XY xy[9];
    double my_pos[9], other_pos[9];
    const iter1 first1_save = first1;
    const iter2 first2_save = first2;
    struct CPDataEx : CPData {
        bool me_overlap_fw = false;    //If this CP is part of an overlap segment, this tells if the overlap continues in the forward direction on the first path
        bool me_overlap_bw = false;    //If this CP is part of an overlap segment, this tells if the overlap continues in the backward direction on the first path
        bool other_overlap_fw = false; //If this CP is part of an overlap segment, this tells if the overlap continues in the forward direction on the second path
        bool other_overlap_bw = false; //If this CP is part of an overlap segment, this tells if the overlap continues in the forward direction on the second path
        bool del = false;              //When set, this CP is marked for deletion as it is in the middle of an overlap section between the two paths.
        iter1 me_fw, me_bw;
        iter2 other_fw, other_bw;
        constexpr bool all_overlap() const noexcept { return me_overlap_fw && me_overlap_bw && other_overlap_fw && other_overlap_bw; }
    };
    std::vector<CPDataEx> cps;
    unsigned u=0, v=0; //after the cycle these show the number of edges in each path
    bool had_overlap = false;
    auto prev1 = closed1 ? std::prev(last1) : last1;
    for (u = 0; first1!=last1; ++u, prev1 = first1, ++first1)
        if (!to_do.ignore_invisible_edges || first1->IsVisible()) {
            const Block T = first1->IsStraight() ? Block(first1->GetStart(), first1->GetEnd()) : first1->GetBezierHullBlock();
            first2 = first2_save;
            auto prev2 = closed2 ? std::prev(last2) : last2;
            for (v = 0; first2!=last2; ++v, prev2 = first2, ++first2)
                if (!to_do.ignore_invisible_edges || first2->IsVisible()) {
                    const Block O = first2->IsStraight() ? Block(first2->GetStart(), first2->GetEnd()) : first2->GetBezierHullBlock();
                    if (!HullOverlap(T, O, false)) continue;
                    const int num = first1->CrossingWithOverlap(*first2, false, xy, my_pos, other_pos); //num<0 if overlap
                    if (num==0) continue;
                    if (num>0 && to_do.remove_non_overlap) continue;
                    had_overlap |= num<0;
                    for (int i = abs(num)-1; i>=0; i--) {
                        auto &cp = cps.emplace_back();
                        cp.xy = xy[i];
                        cp.me = {u, my_pos[i]};
                        cp.other = {v, other_pos[i]};

                        cp.me_bw = my_pos[i] > 0. ? first1 //CP is in the middle of the edge - the relevant edge in the bw direction is us.
                            : prev1!=last1 && prev1->GetEnd()==first1->GetStart() && (prev1->IsVisible() || !to_do.ignore_invisible_edges) ? prev1 //connected, visible previous edge
                            : last1; //this is the start of the path - no previous edge
                        const auto next1 = std::next(first1) != last1 ? std::next(first1)
                            : closed1 && first1->GetEnd() == first1_save->GetStart() && first1_save->IsVisible() ? first1_save
                            : last1;
                        cp.me_fw = my_pos[i] < 1. ? first1 : next1;

                        cp.other_bw = other_pos[i] > 0. ? first2 //CP is in the middle of the edge - the relevant edge in the bw direction is us.
                            : prev2!=last2 && prev2->GetEnd()==first2->GetStart() && (prev2->IsVisible() || !to_do.ignore_invisible_edges) ? prev2 //connected, visible previous edge
                            : last2; //this is the start of the path - no previous edge
                        const auto next2 = std::next(first2) != last2 ? std::next(first2)
                            : closed1 && first2->GetEnd() == first2_save->GetStart() && first2_save->IsVisible() ? first2_save
                            : last2;
                        cp.other_fw = other_pos[i] < 1. ? first2 : next2;

                        if (num<0) { //two edges overlap, we assume num==-2
                            (my_pos[i]<my_pos[1-i] ? cp.me_overlap_fw : cp.me_overlap_bw) = true;
                            (other_pos[i]<other_pos[1-i] ? cp.other_overlap_fw : cp.other_overlap_bw) = true;
                        }

                    }
                }
        }
    if (cps.empty())
        return false;
    //If we have any crosspoints at vertices, those are present potentially twice
    //Once for each edge. Merge these. Do it that CPs end up sorted by me's pos (and by other's pos).
    if (cps.size()>1) {
    //Here we are already sorted by me.edge, so that at joins me=(X,1)->me=(X+1,0); other=(Y,Z)->other=(Y,Z), we delete the (X,1)
        std::ranges::stable_sort(cps, [](const CPData &a, const CPData &b) { return a.other<b.other; });
        for (auto j = cps.begin(), i = std::next(j); j!=cps.end(); j++, ++i == cps.end() ? i = cps.begin() : i)
            //Below we test that the CPs are indeed on the same coordinate and same position on both paths
            //PathPos equals if edge+pos are the same *or* a vertex with 2 connected edges.
            if (j->xy==i->xy
                    && same_pos(j->me, i->me, u, closed1) && same_pos(j->other, i->other, v, closed2)
                    && !i->del) { //i->del can be true only around the start/end wrap. This prevents deleting all CPs if we only have one at the start/end
                i->me_overlap_fw |= j->me_overlap_fw;
                i->me_overlap_bw |= j->me_overlap_bw;
                i->other_overlap_fw |= j->other_overlap_fw;
                i->other_overlap_bw |= j->other_overlap_bw;
                j->del = true;
            }
        std::erase_if(cps, [&](const CPDataEx &cp) { return cp.del; });
        if (cps.size()>1) {
            std::ranges::stable_sort(cps, [](const CPData &a, const CPData &b) { return a.me<b.me; });
            for (auto j = cps.begin(), i = std::next(j); j!=cps.end(); j++, ++i == cps.end() ? i = cps.begin() : i)
                if (j->xy==i->xy
                        && same_pos(j->me, i->me, u, closed1) && same_pos(j->other, i->other, v, closed2)
                        && !i->del) {
                    i->me_overlap_fw |= j->me_overlap_fw;
                    i->me_overlap_bw |= j->me_overlap_bw;
                    i->other_overlap_fw |= j->other_overlap_fw;
                    i->other_overlap_bw |= j->other_overlap_bw;
                    j->del = true;
                }
            std::erase_if(cps, [&](const CPDataEx &cp) { return cp.del; });
        }
    }

    bool ret = false;
    if (had_overlap && !to_do.keep_inside_overlap) {
        //Now we have a lot of crosspoints that may be part of a contiguous span of overlap.
        //Note, we report a span of overlap only if it is contiguous in both paths.
        //That is if the two paths run overlapped from point A->B then one of them departs and
        //comes back to B and the two paths run overlapped from B->C, then we report A->B and B->C
        //as separate overlapped sections.
        //We can remove crosspoints that have been marked as part of an overlap in both directions
        //in both paths

        //If the two paths are the same, but not self-crossing, we may have 2 or 4 CPs
        //- 2 if the start/end point XY of the 2 paths are the same, we only have 2 CPs: this very point twice
        //  once as start once as end of an overlap region. If any of the paths is marked closed, we have marked
        //  these two CPs as all overlapping. Note the two paths may still go in the opposite direction.
        //- 4 if the start/end point XY of the two paths is different (they are the same edges rotated).
        //  if both are marked closed, all 4 will be marked for deletion. If only one of them, then only 2 of them.
        //- There may be more crosspoints if the paths are the same but have extra vertices, like an edge split
        //  into two by adding a vertex (which could be removed and keep the path the same).
        //Conclusion: if all the remaining CPs are in overlap both fw and bw, we don't delete them but return true.
        //Else, we delete the ones marked and return false.
        ret = std::ranges::all_of(cps, [](const CPDataEx &cp) {return cp.all_overlap(); });
        if (!ret)
            std::erase_if(cps, [&](const CPDataEx &cp) { return cp.all_overlap(); });
    }

    for (auto &cp: cps) {
        //If one path is marked overlapped in any dir, it shall also be overlapped on another
        _ASSERT((cp.me_overlap_fw || cp.me_overlap_bw) == (cp.other_overlap_fw || cp.other_overlap_bw));

        //Note: in general the markings of XXX_overlap_YY are more precise than comparing angles
        //Thus we use these decisions even if we calculate angles below.
        //Calculating angles is thought to be somewhat expensive.
        cp.type.had_rel_computed = to_do.calculate_relation;
        cp.type.me_end = cp.me_bw == last1 || cp.me_fw == last1;
        cp.type.other_end = cp.other_bw == last2 || cp.other_fw == last2;
        if (cp.me_overlap_fw && cp.me_overlap_bw && cp.other_overlap_fw && cp.other_overlap_bw) //In the middle of an overlap segment, none of the paths end
            cp.type.rel = CPRel::Overlap;
        else if (cp.me_overlap_fw || cp.me_overlap_bw) //We have overlap, but at the end of a segment. If any of the paths end here, we are Overlap, else Fork
            cp.type.rel = cp.type.me_end || cp.type.other_end ? CPRel::Overlap : CPRel::Fork;
        else
            cp.type.rel = CPRel::Cross;

        //Note it is possible to have only 3 of cp.me_overlap_fw && cp.me_overlap_bw &&
        //cp.other_overlap_fw && cp.other_overlap_bw true: for degenerate cases, such as
        //when the fw and bw Angle is the same in a path:
        //Path1 = (0,0)->(1,1)->(0,0); Path2=(0,0)->(1,1)->(2,2); In this case we have two crosspoints
        // - (0,0), which is an Overlap with both paths end and me_overlap_fw = other_overlap_fw = true
        // - (1,1), which is degenerate, because the fw and bw Angle of path 1 equal. me_overlap_fw = me_overlap_bw = true,
        //   but for path2 only other_overlap_bw is true. This must be a Fork crosspoint, but we still cannot determine
        //   sides or same_dir.
        // Likewise if two such degenerate paths touch:
        //Path1 = (0,0)->(1,1)->(0,0) Path2 = (2,2)->(1,1)->(2,2)
        //In this case there is no overlap, but again there is no chance to calculate sides or dir

        if (cp.type.had_rel_computed) {
            RayAngle rays[2][2]; //first index: 0=me, 1=other; second index: 0=bw, 1=fw
            if (cp.me_bw != last1)    rays[0][0] = cp.me_bw->Angle(true, cp.me.pos==0. ? 1. : cp.me.pos);
            if (cp.other_bw != last2) rays[1][0] = cp.other_bw->Angle(true, cp.other.pos==0. ? 1. : cp.other.pos);
            if (cp.me_fw != last1)    rays[0][1] = cp.me_fw->Angle(false, cp.me.pos==1. ? 0. : cp.me.pos);
            if (cp.other_fw != last2) rays[1][1] = cp.other_fw->Angle(false, cp.other.pos==1. ? 0. : cp.other.pos);
            cp.type.me_overlap_fw = cp.me_overlap_fw ? Tri::True : Tri::False;
            cp.type.other_overlap_fw = cp.other_overlap_fw ? Tri::True : Tri::False;
            switch (cp.type.rel) {
            default:
                _ASSERT(0);
                FALLTHROUGH;
            case CPRel::Cross:  //This may be a touchpoint or a crosspoint
                if (cp.type.me_end && cp.type.other_end) {
                    cp.type.rel = CPRel::Touch;
                    goto degenerate;
                } else if (cp.type.me_end) {
                    if (rays[1][0].ExactEqual(rays[1][1])) {
                        cp.type.rel = CPRel::Touch;
                        goto degenerate;
                    }
                    const bool me_good_dir = cp.me_bw==last1; //0 for bw, 1 fw
                    if (rays[0][me_good_dir].IsSimilar(rays[1][0])) {
                        cp.type.rel = CPRel::Overlap;
                        cp.type.other_left_side = Tri::Undefined;
                        cp.type.me_left_side = Tri::Undefined;
                        cp.type.same_dir = me_good_dir==0 ? Tri::True : Tri::False;
                    } else if (rays[0][me_good_dir].IsSimilar(rays[1][1])) {
                        cp.type.rel = CPRel::Overlap;
                        cp.type.other_left_side = Tri::Undefined;
                        cp.type.me_left_side = Tri::Undefined;
                        cp.type.same_dir = me_good_dir==1 ? Tri::True : Tri::False;
                    } else {
                        cp.type.rel = CPRel::Touch;
                        cp.type.other_left_side = really_between_warp(rays[0][me_good_dir], rays[1][0], rays[1][1]) ? Tri::True : Tri::False;
                        cp.type.same_dir = Tri::Undefined;
                        cp.type.me_left_side = Tri::Undefined;
                    }
                } else if (cp.type.other_end) {
                    if (rays[0][0].ExactEqual(rays[0][1])) {
                        cp.type.rel = CPRel::Touch;
                        goto degenerate;
                    }
                    const bool other_good_dir = cp.other_bw==last2; //0 for bw, 1 fw
                    if (rays[1][other_good_dir].IsSimilar(rays[0][0])) {
                        cp.type.rel = CPRel::Overlap;
                        cp.type.other_left_side = Tri::Undefined;
                        cp.type.me_left_side = Tri::Undefined;
                        cp.type.same_dir = other_good_dir==0 ? Tri::True : Tri::False;
                    } else if (rays[1][other_good_dir].IsSimilar(rays[0][1])) {
                        cp.type.rel = CPRel::Overlap;
                        cp.type.other_left_side = Tri::Undefined;
                        cp.type.me_left_side = Tri::Undefined;
                        cp.type.same_dir = other_good_dir==1 ? Tri::True : Tri::False;
                    } else {
                        cp.type.rel = CPRel::Touch;
                        cp.type.me_left_side = really_between_warp(rays[1][other_good_dir], rays[0][0], rays[0][1]) ? Tri::True : Tri::False;
                        cp.type.same_dir = Tri::Undefined;
                        cp.type.other_left_side = Tri::Undefined;
                    }
                } else {
                    if (rays[0][0].ExactEqual(rays[0][1])) goto degenerate;
                    if (rays[1][0].ExactEqual(rays[1][1])) goto degenerate;
                    if (rays[1][0].IsSimilar(rays[0][0])) {
                        cp.type.same_dir = Tri::True;
                        if (rays[1][1].IsSimilar(rays[0][1])) {
                            cp.type.rel = CPRel::Overlap;
                            cp.type.other_left_side = Tri::Undefined;
                            cp.type.me_left_side = Tri::Undefined;
                        } else {
                            cp.type.rel = CPRel::Fork;
                            const bool fw_left = really_between_warp(rays[1][1], rays[0][0], rays[0][1]); //the fw of 'other' is on the left side of 'me'
                            cp.type.other_left_side = fw_left ? Tri::False : Tri::True;
                            cp.type.me_left_side = fw_left ? Tri::True : Tri::False;
                        }
                    } else if (rays[1][0].IsSimilar(rays[0][1])) {
                        cp.type.same_dir = Tri::False;
                        if (rays[1][1].IsSimilar(rays[0][0])) {
                            cp.type.rel = CPRel::Overlap;
                            cp.type.other_left_side = Tri::Undefined;
                            cp.type.me_left_side = Tri::Undefined;
                        } else {
                            cp.type.rel = CPRel::Fork;
                            const bool fw_left = really_between_warp(rays[1][1], rays[0][0], rays[0][1]); //the fw of 'other' is on the left side of 'me'
                            cp.type.other_left_side
                                = cp.type.me_left_side = fw_left ? Tri::True : Tri::False;
                        }
                    } else if (rays[1][1].IsSimilar(rays[0][1])) {
                        cp.type.same_dir = Tri::True;
                        cp.type.rel = CPRel::Fork;
                        const bool bw_left = really_between_warp(rays[1][0], rays[0][0], rays[0][1]); //the bw of 'other' is on the left side of 'me'
                        cp.type.other_left_side = bw_left ? Tri::False : Tri::True;
                        cp.type.me_left_side = bw_left ? Tri::True : Tri::False;
                    } else if (rays[1][1].IsSimilar(rays[0][0])) {
                        cp.type.same_dir = Tri::False;
                        cp.type.rel = CPRel::Fork;
                        const bool bw_left = really_between_warp(rays[1][0], rays[0][0], rays[0][1]); //the bw of 'other' is on the left side of 'me'
                        cp.type.other_left_side
                            = cp.type.me_left_side = bw_left ? Tri::True : Tri::False;
                    } else {
                        //none of the rays are similar
                        const bool bw_left = really_between_warp(rays[1][0], rays[0][0], rays[0][1]); //the bw of 'other' is on the left side of 'me'
                        const bool fw_left = really_between_warp(rays[1][1], rays[0][0], rays[0][1]); //the fw of 'other' is on the left side of 'me'
                        if (bw_left==fw_left) {
                            cp.type.rel = CPRel::Touch;
                            const bool same_dir = bw_left == really_between_warp(rays[1][1], rays[1][0], rays[0][1]);
                            cp.type.same_dir = same_dir ? Tri::True : Tri::False;
                            cp.type.me_left_side = bw_left ? Tri::True : Tri::False;
                            cp.type.other_left_side = bw_left != same_dir ? Tri::True : Tri::False; //if same dir, the sides are opposite
                        } else {
                            //We are a crosspoint, sides are opposite
                            cp.type.me_left_side = bw_left ? Tri::False : Tri::True;
                            cp.type.other_left_side = bw_left ? Tri::True : Tri::False;
                            cp.type.same_dir = Tri::Undefined;
                        }
                    }
                }
                break;
            case CPRel::Fork:
                if (cp.me_overlap_fw+cp.me_overlap_bw+cp.other_overlap_fw+cp.other_overlap_bw == 3)
                    goto degenerate;
                cp.type.same_dir = cp.me_overlap_fw == cp.other_overlap_fw ? Tri::True : Tri::False;
                _ASSERT((cp.me_overlap_fw == cp.other_overlap_fw) == (cp.me_overlap_bw == cp.other_overlap_bw));
                cp.type.me_left_side = really_between_warp(rays[1][cp.other_overlap_bw], rays[0][0], rays[0][1]) ? Tri::True : Tri::False;
                cp.type.other_left_side = really_between_warp(rays[0][cp.me_overlap_bw], rays[1][0], rays[1][1]) ? Tri::True : Tri::False;
                break;
            case CPRel::Overlap:
                const int me_valid_dir = cp.me_bw != last1 ? 0 : 1;
                const int other_valid_dir = cp.other_bw != last2 ? 0 : 1;
                cp.type.same_dir = rays[0][me_valid_dir].IsSimilar(rays[1][other_valid_dir]) == (me_valid_dir==other_valid_dir) ? Tri::True : Tri::False;
                cp.type.me_left_side = Tri::Undefined;
                cp.type.other_left_side = Tri::Undefined;
                break;
            }
        } else { //!cp.type.had_rel_computed
            cp.type.me_overlap_fw = Tri::Undefined;
            cp.type.other_overlap_fw = Tri::Undefined;
            if (cp.type.rel == CPRel::Cross && (cp.type.me_end || cp.type.other_end))
                cp.type.rel = CPRel::Touch;
        degenerate:
            cp.type.same_dir = Tri::Undefined;
            cp.type.me_left_side = Tri::Undefined;
            cp.type.other_left_side = Tri::Undefined;
        }
        add_to.push_back(cp);
    }
    return ret;
}

/** Calculates the distance between two edge series by finding their two closest points.
 *
 * @param ret We return the distance of the two closest points and the two points themselves.
 *            Distance is always nonnegative, zero if they cross (two points equal).
 *            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).
 * @param [in] first1 The first edge of the first path
 * @param [in] last1 iterator beyond the last edge of the first path
 * @param [in] first2 The first edge of the second path
 * @param [in] last2 iterator beyond the last edge of the second path
 *
 * 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.
 */
template <typename iter1, typename iter2>
void Distance(DistanceType &ret,
              iter1 first1, iter1 last1,
              iter2 first2, iter2 last2) noexcept
{
    if (first1==last1 || first2==last2) return;
    DistanceType running = ret, tmp;
    running.MakeAllOutside();
    //both running and tmp are positive through this call, except at the very end
    for (unsigned u = 0; first1!=last1; ++first1, ++u) {
        unsigned v = 0;
        iter2 f2 = first2;
        for (v = 0, f2=first2; f2!=last2; ++f2, ++v) {
            if (first1->IsStraight() && f2->IsStraight())
                tmp = first1->Distance(*f2);
            else if (running.ConsiderBB(fabs(first1->CreateBoundingBox().Distance(f2->CreateBoundingBox()))))
                //if not both are straight try to avoid calculation if bbs are far enough
                tmp = first1->Distance(*f2);
            else continue;
            tmp.pos_on_me.edge = u;
            tmp.pos_on_other.edge = v;
            running.Merge(tmp);
            if (running.IsZero()) goto final_merge;
        }
    }
final_merge:
    ret.Merge(running);
}

/** Calculates the distance between a point and us by finding our closest point.
 * @param ret We return the distance of the closest points and the point itself.
 *            Distance is always nonnegative, zero if they cross (two points equal).
 *            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).
 * @param [in] first The first edge of the path
 * @param [in] last iterator beyond the last edge of the path
 * @param [in] o The point to take the distance from.
 * @return The distance, `CONTOUR_INFINITY` if we are empty. */
template <typename iter>
void Distance(DistanceType &ret, iter first, iter last, const XY &o) noexcept
{
    XY tmp;
    double pos;
    for (unsigned u = 0; first!=last && ret.distance!=0; ++first, ++u) {
        double d = first->Distance(o, tmp, pos);
        if (ret.ConsiderBB(d))
            ret.Merge(d, tmp, o, {u, pos}, {0, 0});
    }
}



/** Calculates the bounding box of edges between first and last. Moderately expensive.*/
template <typename iter>
Block CalculateBoundingBox(iter first, iter last) noexcept
{
    Block ret(false);
    for (; first!=last; first++)
        ret += first->CreateBoundingBox();
    return ret;
}

/** Calculate the length of the [first, last).
 * @param [in] first From this iterator...
 * @param [in] last ..up to, but not including this one.
 * @param [in] include_hidden If false, we leave out edges marked as non-visible.
 * @returns Length of the path (non connected segments combined.*/
template <typename iter>
double GetLength(iter first, iter last, bool include_hidden = false) noexcept
{
    double ret = 0;
    for (; first!=last; first++)
        if (include_hidden || first->IsVisible())
            ret += first->GetLength();
    return ret;
}

/** Return true if [first, last) is a single, connected segment.
 * Ignores edge visibility.*/
template <typename iter>
bool IsConnected(iter first, iter last) noexcept
{
    if (first==last)
        return true; //empty sequences are connected
    for (iter next = std::next(first); next!=last; ++first, ++next)
        if (first->GetEnd() != next->GetStart())
            return false;
    return true;
}

/** Return true if [first, last) is a single, connected and closed segment.
* Ignores edge visibility.*/
template <typename iter>
bool IsClosed(iter first, iter last) noexcept
{
    if (first==last)
        return true; //empty sequences are closed
    return first->GetStart()==std::prev(last)->GetEnd() && IsConnected(first, last);
}

template <typename iter>
void Shift(iter first, iter last, const XY &xy) noexcept { for (; first!=last; ++first) first->Shift(xy); } ///<Translates the path.
template <typename iter>
void Scale(iter first, iter last, double sc) noexcept { for (; first!=last; ++first) first->Scale(sc); } ///<Scale the path.
template <typename iter>
void Scale(iter first, iter last, const XY &sc) noexcept { for (; first!=last; ++first) first->Scale(sc); } ///<Scale the path.
template <typename iter>
void SwapXY(iter first, iter last) noexcept { for (; first!=last; ++first) first->SwapXY(); } ///<Transpose the path: swap XY coordinates
template <typename iter>
void Rotate(iter first, iter last, double cos, double sin) noexcept { for (; first!=last; ++first) first->Rotate(cos, sin); } ///<Rotate the path by `radian`. `sin` and `cos` are pre-computed values.
template <typename iter>
void Rotate(iter first, iter last, double degree) noexcept { Rotate(first, last, cos(degree/180*M_PI), sin(degree/180*M_PI)); } ///<Rotate path around origin
template <typename iter>
void RotateAround(iter first, iter last, const XY&c, double cos, double sin) noexcept { for (; first!=last; ++first) first->RotateAround(c, cos, sin); } ///<Rotate the path by `radian` around `c`. `sin` and `cos` are pre-computed values.
template <typename iter>
void RotateAround(iter first, iter last, const XY&c, double degree) noexcept { RotateAround(first, last, c, cos(degree/180*M_PI), sin(degree/180*M_PI)); } ///<Rotat path around 'c'
template <typename iter>
void Transform(iter first, iter last, const TRMatrix & M) noexcept {_ASSERT(!M.IsDegenerate() && !M.IsIdentity()); for (;first!=last; ++first) first->Apply(M);} ///<Applies a transform. If it is an inverting transform, we do not invert the edge series.

template <typename iter>
void SetVisible(iter first, iter last, bool visible) noexcept { for (; first!=last; ++first) first->SetVisible(visible); }
template <typename iter>
void SetInternalMark(iter first, iter last, bool imark) noexcept { for (; first!=last; ++first) first->SetInternalMark(imark); }
template <typename iter>
void Apply(iter first, iter last, Edge::Update u) noexcept { for (; first!=last; ++first) first->Apply(u); }


/**Take those consecutive segments that are closed and put them to EdgeVectors.
 * No untangle is done here.
 * @param [in] first The first edge in the range to process.
 * @param [in] last The one beyond the last edge in the range to process.
 * @param [in] close Governs what to do with open sub-paths.*/
template <typename Container, typename iter>
std::vector<Container> ConvertToClosed(iter first, iter last, ECloseType close = ECloseType::IGNORE_OPEN_PATH)
{
    std::vector<Container> ret;
    if (first==last) return ret;
    ret.emplace_back();
    for (; first!=last; ++first) {
        if (ret.back().size() && ret.back().back().GetEnd()!=first->GetStart()) {
            //Close last run. First see if it ended up closed
            if (close != ECloseType::IGNORE_OPEN_PATH && ret.back().front().GetStart()!=ret.back().back().GetEnd()) {
                const XY st = ret.back().back().GetEnd();
                const XY en = ret.back().front().GetStart();
                ret.back().emplace_back(st, en, close!=ECloseType::CLOSE_INVISIBLE);  //invisible if false
            }
            ret.emplace_back();
        }
        ret.back().push_back(*first);
    }
    //check if last segment needs to be closed
    if (close != ECloseType::IGNORE_OPEN_PATH && ret.back().front().GetStart()!=ret.back().back().GetEnd()) {
        const XY st = ret.back().back().GetEnd();
        const XY en = ret.back().front().GetStart();
        ret.back().emplace_back(st, en, close!=ECloseType::CLOSE_INVISIBLE);
    }
    return ret;
}

/** Split the path to connected segments.
 *  If remove_hidden is true, we remove hidden edges before the split - so they also cause a split.*/
template <class Container, typename iter>
std::vector<Container> Split(iter first, iter last, bool remove_hidden = false)
{
    std::vector<Container>ret(1);
    for (; first!=last; ++first) {
        if (ret.back().size() &&
            ((remove_hidden && !first->IsVisible()) || ret.back().back().GetEnd()!=first->GetStart()))
            //close last path and open a new one
            ret.emplace_back();
        ret.back().push_back(*first);
    }
    if (ret.back().size()==0)
        ret.pop_back();
    return ret;
}

/** Split the path to connected segments.
 *  If remove_hidden is true, we remove hidden edges before the split - so they also cause a split.*/
template <typename iter> requires(std::contiguous_iterator<iter>)
auto Split(iter first, iter last, bool remove_hidden = false)
{
    using value = std::remove_reference_t<decltype(*first)>;
    using span = std::span<value>;
    std::vector<span> ret;
    if (first==last) return ret;
    iter begin = first;
    if (remove_hidden) while (begin!=last && !begin->IsVisible()) ++begin;
    for (first=std::next(begin); first!=last; /*nope*/)
        if ((remove_hidden && !first->IsVisible()) || std::prev(first)->GetEnd()!=first->GetStart()) {
            //close last path and open a new one
            ret.push_back(span(begin, first));
            begin = first++;
            if (remove_hidden) while (begin!=last && !begin->IsVisible()) first = ++begin;
        } else
            ++first;
    if (begin!=last)
        ret.push_back(span(begin, last));
    return ret;
}

/** Creates a circle, ellipse or a segment of it.
 * @param [out] append_to append (via emplace_back) the resulting edge(s) to this collection.
 * @param [in] c The center of the circle/ellipse.
 * @param [in] radius_x The radius along the x axis. Must be positive.
 * @param [in] radius_y The radius along the x axis. Must be nonnegative.
 *                      If zero, we use radius_x instead.
 * @param [in] tilt_deg Degrees by which we rotate the ellipse clockwise
 *                      (ignored if radius_x==radius_y)
 * @param [in] s_deg Degrees where to start the arc.
 * @param [in] d_deg Degrees where to end the arc. Both d_deg and s_deg are
 *                   counted from the "right" direction clockwise.
 * @param [in] clockwise If true the arc goes clockwise from s_deg to d_deg.
 * @param [in] ensure_connect If true we add a straight line from the end of append_to to the
 *                            circle inserted.
 * @param [in] add_closing_line If true we add a closing line - from the *start of append_to* to
 *                              the end of the appended edges. Even if radius is 0, and we have
 *                              added no circle.
 * @returns an iterator to the first element appended. Points to append_to.end() if none.*/
template <typename Container>
typename Container::iterator
AppendEllipse(Container &append_to, const XY &c, double radius_x, double radius_y,
              double tilt_deg, double s_deg, double d_deg, bool clockwise,
              bool ensure_connect, bool add_closing_line)
{
    const size_t original_size = append_to.size();
    if (!clockwise)
        std::swap(s_deg, d_deg);
    s_deg = fmod_negative_safe(s_deg, 360.)/90;
    d_deg = fmod_negative_safe(d_deg, 360.)/90;
    if (d_deg<=s_deg) d_deg += 4;
    _ASSERT(radius_x>0 && radius_y>=0);
    if (radius_y==0)
        radius_y = radius_x;
    if (radius_x>0 && radius_y>0) {
        //first define the unit circle
        //http://hansmuller-flex.blogspot.hu/2011/04/approximating-circular-arc-with-cubic.html
        const double magic_number = 0.5522847498; //4/3 * (sqrt(2)-1)
        const XY Q[4][4] = {{XY(1, 0), XY(0, 1), XY(1, magic_number), XY(magic_number, 1)},
                            {XY(0, 1), XY(-1, 0), XY(-magic_number, 1), XY(-1, magic_number)},
                            {XY(-1, 0), XY(0, -1), XY(-1, -magic_number), XY(-magic_number, -1)},
                            {XY(0, -1), XY(1, 0), XY(magic_number, -1), XY(1, -magic_number)}};
        unsigned u = unsigned(s_deg);
        const unsigned u_till = unsigned(d_deg);
        std::array<Edge, 6> edges;
        unsigned num = 0;
        edges[num++] = Edge(Q[u][0], Q[u][1], Q[u][2], Q[u][3]);
        if (u==u_till) {
            //single quadrant
            edges[0].Chop(s_deg-floor(s_deg), d_deg-floor(d_deg));
        } else {
            //multiple quadrants: first needs to be chopped only from its start
            edges[0].Chop(s_deg-floor(s_deg), 1);
            for (u++; u!=u_till; u++)
                edges[num++] = Edge(Q[u%4][0], Q[u%4][1], Q[u%4][2], Q[u%4][3]);
            if (d_deg!=floor(d_deg)) {
                //d_deg is not exactly at the start of a quadrant, we append a last fractional quadrant
                edges[num++] = Edge(Q[u%4][0], Q[u%4][1], Q[u%4][2], Q[u%4][3]);
                edges[num-1].Chop(0, d_deg-floor(d_deg));
            }
        }
        //Now we have (part of) a unit circle.
        for (unsigned s = 0; s<num; s++) {
            //Distort to make an ellipse
            edges[s].Scale(XY(radius_x, radius_y));
            //next, tilt
            edges[s].Rotate(cos(tilt_deg/180*M_PI), sin(tilt_deg/180*M_PI));
            //next move
            edges[s].Shift(c);
            if (!clockwise)
                edges[s].Invert();
        }
        //(now i points to the first edge added)
        //Reverse if counterclockwise is needed
        if (!clockwise)
            std::reverse(edges.begin(), edges.begin()+num);

        //OK, now start adding edges to the output container.
        if (ensure_connect && append_to.size() && edges[0].GetStart()!=append_to.back().GetEnd()) {
            XY end = append_to.back().GetEnd();
            append_to.emplace_back(end, edges[0].GetStart());
        }
        std::copy(edges.begin(), edges.begin()+num, std::insert_iterator<Container>(append_to, append_to.end()));
    }
    if (add_closing_line && append_to.size()) {
        //Add a segment if not a full ellipse
        const XY start = append_to.front().GetStart();
        const XY end = append_to.back().GetEnd();
        if (start!=end)
            append_to.emplace_back(end, start);
    }
    return append_to.end() - (append_to.size() - original_size);
}


/** Appends a line cap.
 * It assumes that prev and next are expanded version of the original line and they follow
 * a clockwise direction.
 * EXPAND_BEVEL and EXPAND_MITER_BEVEL adds an arrow-like protrusion, like: =>
 * EXPAND_ROUND and EXPAND_MITER_ROUND adds a circle, like =)
 * EXPAND_MITER_SQUARE adds a closing line, like =|
 * EXPAND_MITER adds no cap, returns append_to.end()
 * @returns an iterator to the first element appended. Points to end() if none.*/
template <template <typename Edgeish> class Container, typename Edgeish>
typename Container<Edgeish>::iterator
AppendLineCap(Container<Edgeish> &append_to, EExpandType type,
              const XY prev_end, const XY next_start, bool visible)
{
    const size_t original_size = append_to.size();
    switch (type) {
    case contour::EXPAND_MITER:
        return append_to.end();
    case contour::EXPAND_MITER_SQUARE:
    default:
        append_to.emplace_back(prev_end, next_start, visible);
        break;
    case contour::EXPAND_BEVEL:
    case contour::EXPAND_MITER_BEVEL:
    {
        const XY center = (next_start + prev_end)/2;
        const XY tip = center + (next_start - prev_end).Rotate90CCW()/2;
        append_to.emplace_back(prev_end, tip, visible);
        append_to.emplace_back(tip, next_start, visible);
        break;
    }
    case contour::EXPAND_ROUND:
    case contour::EXPAND_MITER_ROUND:
        const XY center = (next_start + prev_end)/2;
        const double radius = next_start.Distance(prev_end)/2;
        double s_deg = acos(std::max(-1., std::min(1., (prev_end.x-center.x)/radius)))*180/M_PI;
        if (prev_end.y<center.y) s_deg = 360-s_deg;
        double d_deg = acos(std::max(-1., std::min(1., (next_start.x-center.x)/radius)))*180/M_PI;
        if (next_start.y<center.y) d_deg = 360-d_deg;
        auto first_appended = AppendEllipse(append_to, center, radius, radius, 0, s_deg, d_deg, true, true, false);
        //Make sure the construct starts *exactly* at 'start' and 'end'
        //The result is not precise, we simply set the start/endpoint (keeping control points)
        first_appended->SetStartOnly(prev_end);
        append_to.back().SetEndOnly(next_start);
        //set visibility and user_data
        if (!visible)
            for (; first_appended!=append_to.end(); ++first_appended)
                first_appended->SetInternalMark(false);
    }
    //append_to may be a vector with iterators invalidated on insert.
    //calculate first element inserted at the end
    auto first_inserted = append_to.end();
    std::advance(first_inserted, original_size-append_to.size()); //negative, i points to the first edge inserted
    return first_inserted;
}

/** Create join elements when expanding.
 * @param type The type of expand we do.
 * @param center The original vertex, now the center of round joins.
 * @param prev The previous edge (already expanded), our generated edged connect to the end of it.
 * @param next The next edge (already expanded), our generated edged connect to the start of it.
 * @param visible If we want the generated edges visible or not.
 * @param prev_fw_tangent A forward tangent point of the previous edge. This is needed since the
 *                        expanded previous edge may have been flattened and its forward tangent may not be
 *                        parallel with that of its original. This tangent point was calculated based on the
 *                        finishing direction of the original edge.
 * @param next_bw_tangent A backward tangent of the next edge (at its start point).
 * @param prev_was_bezier True if the original of 'prev' was a bezier.
 * @param next_was_bezier True if the original of 'next' was a bezier.
 * @param miter_limit The miter limit to apply for EXPAND_MITER types.
 * @returns A container in which you want the resulting edges.*/
template <typename Container>
Container CreateJoinForExpand(EExpandType type, XY center, Edge &prev, Edge &next, bool visible,
                              const XY &prev_fw_tangent, const XY &next_bw_tangent, bool prev_was_bezier, bool next_was_bezier,
                              double miter_limit)
{
    _ASSERT(type != EXPAND_ROUND || test_equal(center.DistanceSqr(prev.GetEnd()), center.DistanceSqr(next.GetStart())));

    Container res;
    XY cp;
    double prev_pos, next_pos;
    bool biggerthan180 = false;
    Edge::EExpandCPType t = prev.FindExpandedEdgesCP(next, cp, prev_fw_tangent,
                                                     next_bw_tangent, prev_was_bezier, next_was_bezier, prev_pos, next_pos);
    switch (t) {
    case Edge::EExpandCPType::CP_REAL:
        //we have a crosspoint, chop the original edges
        prev.SetEnd(cp, prev_pos);
        next.SetStart(cp, next_pos);
        FALLTHROUGH;
    case Edge::EExpandCPType::CP_DEGENERATE:
    case Edge::EExpandCPType::CP_TRIVIAL:
        return res; //no special joint edge(s) need to be added
    case Edge::EExpandCPType::NO_CP_PARALLEL:
        switch (type) {
        case EExpandType::EXPAND_MITER:
            //set up a cp here, to have a 90 degree tip for parallel edges
            cp = (prev.GetEnd() + next.GetStart())/2;
            cp += XY(prev_fw_tangent).Normalize() * prev.GetEnd().Distance(next.GetStart())/2;
            FALLTHROUGH;
        case EExpandType::EXPAND_MITER_BEVEL:
        case EExpandType::EXPAND_MITER_SQUARE:
            type = EExpandType::EXPAND_BEVEL;
            FALLTHROUGH;
        default:
            break; //TODO: do real squares: case EExpandType::EXPAND_MITER_ROUND: type = EExpandType::EXPAND_ROUND; break;
        case EExpandType::EXPAND_MITER_ROUND:
            type = EExpandType::EXPAND_ROUND;
            center = (prev.GetEnd() + next.GetStart())/2;
            break;
        }
        FALLTHROUGH; //we have exactly 180 degree edges.
    case Edge::EExpandCPType::CP_EXTENDED:
        biggerthan180 = false;
        break;
    case Edge::EExpandCPType::CP_INVERSE:
        res.emplace_back(prev.GetEnd(), next.GetStart(), visible);
        return res;
        biggerthan180 = true;
    }

    switch (type) {
    default:
        _ASSERT(0);
        return res;
    case EExpandType::EXPAND_BEVEL:
        res.emplace_back(prev.GetEnd(), next.GetStart(), visible);
        return res;
    case EExpandType::EXPAND_MITER_ROUND:
    case EExpandType::EXPAND_MITER_BEVEL:
    case EExpandType::EXPAND_MITER_SQUARE:
    case EExpandType::EXPAND_MITER:
        if (cp.DistanceSqr(prev.GetEnd()) > miter_limit*miter_limit) {
            const XY m1 = prev.GetEnd() + (cp-prev.GetEnd()).Normalize()*miter_limit;
            const XY m2 = next.GetStart() + (cp-next.GetStart()).Normalize()*miter_limit;
            res.emplace_back(prev.GetEnd(), m1, visible);
            res.emplace_back(m1, m2, visible);
            res.emplace_back(m2, next.GetStart(), visible);
        } else {
            res.emplace_back(prev.GetEnd(), cp, visible);
            res.emplace_back(cp, next.GetStart(), visible);
        }
        return res;
    case EExpandType::EXPAND_ROUND:
        double radius = center.Distance(prev.GetEnd());
        double s_deg = acos(std::max(-1., std::min(1., (prev.GetEnd().x-center.x)/radius)))*180/M_PI;
        if (prev.GetEnd().y<center.y) s_deg = 360-s_deg;
        double d_deg = acos(std::max(-1., std::min(1., (next.GetStart().x-center.x)/radius)))*180/M_PI;
        if (next.GetStart().y<center.y) d_deg = 360-d_deg;

        const bool clockwise = (fmod(d_deg-s_deg+360, 360.0) > 180) == biggerthan180;
        auto first_inserted = AppendEllipse(res, center, radius, radius, 0, s_deg, d_deg, clockwise, false, false);
        //Make sure the construct starts *exactly* at 'start' and 'end'
        //The result is not precise, we simply set the star/endpoint (keeping control points)
        if (first_inserted!=res.end()) {
            _ASSERT(first_inserted->GetStart().DistanceSqr(prev.GetEnd())<1);
            first_inserted->SetStartOnly(prev.GetEnd());
        }
        _ASSERT(res.back().GetEnd().DistanceSqr(next.GetStart())<1);
        res.back().SetEndOnly(next.GetStart());
        if (!visible)
            SetVisible(first_inserted, res.end(), false);
    }
    return res;
}


/** Create join elements when doing linear expand.
 * This function is effectively the same as CreateJoinForExpand(), but
 * without the 'center' parameter.
 * It is effectively doing a better round shape for linearly extended edges.
 * @param type The type of expand we do.
 * @param positive_gap True, if the expansion is done with a positive number at the join point.
 *                     This is used to determine, which side the round curve should go.
 * @param prev The previous edge (already expanded), our generated edged connect to the end of it.
 * @param next The next edge (already expanded), our generated edged connect to the start of it.
 * @param visible If we want the generated edges visible or not.
 * @param prev_fw_tangent A forward tangent point of the previous edge. This is needed since the
 *                        expanded previous edge may have been flattened and its forward tangent may not be
 *                        parallel with that of its original. This tangent point was calculated based on the
 *                        finishing direction of the original edge.
 * @param next_bw_tangent A backward tangent of the next edge (at its startpoint).
 * @param prev_was_bezier True if the original of 'prev' was a bezier.
 * @param next_was_bezier True if the original of 'next' was a bezier.
 * @param miter_limit The miter limit to apply for EXPAND_MITER types.
 * @returns the created edges.*/
template <typename Container>
Container CreateJoinForLinearExpand(EExpandType type, bool positive_gap, Edge &prev, Edge &next, bool visible,
                                    const XY &prev_fw_tangent, const XY &next_bw_tangent,
                                    bool prev_was_bezier, bool next_was_bezier,
                                    double miter_limit)
{
    XY center;
    _ASSERT((center = XY(0, 0)).y==0); //to silence 'uninitialized' variable warnings
    if (type==EXPAND_ROUND) {
        //This is not theoretically possible to connect the two edges with a circle.
        // Maybe use an ellipsys later...
        //Now we take the point on the half-section-line closest to the one with the
        //right angle.
        auto cross = crossing_line_line(
            prev.end(), XY(prev_fw_tangent).RotateAround(prev.end(), 0, +1),
            next.GetStart(), XY(next_bw_tangent).RotateAround(next.start(), 0, -1),
            center);
        if (cross == ELineCrossingType::PARALLEL) {
            _ASSERT(0); // this would happen if the two edges are direct continuation of one another
            type = EXPAND_BEVEL;
        } else {
            //here center is the point for which center->prev.end is perpendicular to prev
            //and center->next.start is perpendicular to next.
            //this is the best point to center a circle from an angle point of view
            const XY mid = (prev.end() + next.GetStart()) / 2;
            const XY p1 = XY(prev.end()).RotateAround(mid, 0, +1);
            const XY p2 = XY(next.GetStart()).RotateAround(mid, 0, +1);
            //now p1-p2 is the line, all points of which are equidistant from prev.end & next.start
            center = center.ProjectOntoLine(p1, p2);
        }
        //OK, if we have to do a strange thing, switch to bevel - that is safe
        XY cp; //dummy
        double prev_pos, next_pos; //dummy
        Edge::EExpandCPType t = prev.FindExpandedEdgesCP(next, cp, prev_fw_tangent,
            next_bw_tangent, prev_was_bezier, next_was_bezier, prev_pos, next_pos);
        if (t!=Edge::CP_TRIVIAL && t!=Edge::CP_REAL && t!=Edge::CP_EXTENDED)
            type = EXPAND_BEVEL;

    }
    return CreateJoinForExpand<Container>(type, center, prev, next, visible, prev_fw_tangent, next_bw_tangent, prev_was_bezier, next_was_bezier, miter_limit);
}

/** Creates a path that is 'gap' distance from the original.
 * Negative gap shifts to the other direction.
 * If 'circular' is true, we also handle the case of an equal start and endpoint.
 * Note that the result is NOT untangled (even for closed paths), that is only
 * done for SimpleContour::SimpleExpand().*/
template <typename iter, class Container = std::vector<typename std::iterator_traits<iter>::value_type>>
Container
CreateSimpleExpand(iter first, iter last, double gap, bool circular = true,
                   EExpandType type = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY)
{
    Container ret;
    if (first==last)
        return ret;
    if (gap==0) {
        std::copy(first, last, std::insert_iterator<Container>(ret, ret.end()));
        return ret;
    }
    iter last_ndx = last, first_ndx = last;
    XY last_next_tangent, first_prev_tangent;
    for (; first!=last; ++first) {
        std::list<typename std::iterator_traits<iter>::value_type> local_res;
        XY u_prev_tangent, u_next_tangent;
        if (!first->CreateExpand(gap, local_res, u_prev_tangent, u_next_tangent))
            continue;
        if (last_ndx==last) {
            first_prev_tangent = u_prev_tangent;
            first_ndx = first;
        }
        //if we had a previous edge and are connected, test for good crosspoints & insert joining edges
        if (last_ndx!=last && last_ndx->GetEnd().test_equal(first->GetStart())) {
            //cannot insert to 'ret' directly, to preserve 'ret.back()' reference
            Container local_res2 =
                CreateJoinForExpand<Container>(type, first->GetStart(), ret.back(), local_res.front(),
                                               last_ndx->IsVisible() && first->IsVisible(),
                                               last_next_tangent, u_prev_tangent,
                                               !last_ndx->IsStraight(), !first->IsStraight(), miter_limit);
            _ASSERT(ret.size()==0 || local_res2.size()==0 ||
                    ret.back().GetEnd().test_equal(local_res2.front().GetStart()));
            if (ret.size() && local_res2.size())
                ret.back().SetEndOnly(local_res2.front().GetStart());
            MoveAppend(ret, std::move(local_res2));
        }
        _ASSERT(ret.size()==0 || local_res.size()==0 ||
                ret.back().GetEnd().test_equal(local_res.front().GetStart()));
        if (ret.size() && local_res.size())
            ret.back().SetEndOnly(local_res.front().GetStart());
        MoveAppend(ret, local_res);
        last_ndx = first;
        last_next_tangent = u_next_tangent;
    }
    //See if last edge connects to the first one
    if (last_ndx!=last && circular && last_ndx->GetEnd().test_equal(first_ndx->GetStart())) {
        Container local_res2 =
            CreateJoinForExpand<Container>(type, last_ndx->GetEnd(), ret.back(), ret.front(),
                                           last_ndx->IsVisible() && first_ndx->IsVisible(),
                                           last_next_tangent, first_prev_tangent,
                                           !last_ndx->IsStraight(), !first_ndx->IsStraight(), miter_limit);
        _ASSERT(ret.size()==0 || local_res2.size()==0 ||
                ret.back().GetEnd().test_equal(local_res2.front().GetStart()));
        if (ret.size() && local_res2.size())
            ret.back().SetEndOnly(local_res2.front().GetStart());
        MoveAppend(ret, std::move(local_res2));
        while (ret.size() && ret.back().CheckAndCombine(ret.front()))
            ret.erase(ret.begin());
    }
    _ASSERT(IsConnected(ret.begin(), ret.end()));
    return ret;
}

/** Find a common tangent for two edges, which have no inflection points.
 * @returns two positions, one for each edge. If you
 * connect the two corresponding points with a straight segment, you get:
 * - smooth connection to both edges and
 * - Edge(0, pos1) -> straight line -> Edge(pos2, 1) is the convex hull of
 *     the two edges on the left (if left == true) or right side.
 * The two edges may connect, but otherwise they are assumed not to cross.
 * (What is the convex hull of two segments that cross?)
 * If the two edges form their own convex hull, we return (1, 0).
 * For generic edges, split at the points returned by Edge::InflectionPoints
 * to get edges that have no inflection points => Use Edge::TangentFrom()
 * Also note that Tangent4Hull(A,B) != Tangent4Hull(B,A).
 * This is a helper for ConvexHull.*/
std::pair<double, double> Tangent4Hull(bool left, const Edge &e1, const Edge &e2) noexcept;


/** Takes a path and creates a convex hull of it on the left or
 * the right side. 'Container' must be a container of Edge descendants,
 * in which you get back the result. We do not handle closed paths.*/
template <typename Container, typename iter>
Container ConvexHull(bool left, iter first, iter last)
{
    Container new_path;
    //First split edges to parts that have no inflexion points
    double infl[2];
    for (; first!=last; ++first)
        switch (first->InflectionPoints(infl)) {
        default:
            _ASSERT(0);
            FALLTHROUGH;
        case 0:
            new_path.push_back(*first);
            break;
        case 1:
            new_path.push_back(*first);
            new_path.back().SetEnd(infl[0]);
            new_path.push_back(*first);
            new_path.back().SetStart(infl[0]);
            break;
        case 2:
            new_path.push_back(*first);
            new_path.back().SetEnd(infl[0]);
            new_path.push_back(*first);
            new_path.back().Chop(infl[0], infl[1]);
            new_path.push_back(*first);
            new_path.back().SetStart(infl[1]);
            break;
        }
    //Now check if any of the edges can be replaced by a straight line by itself
    for (auto &e : new_path)
        if (!e.IsStraight() && !max_clockwise(left, e.GetStart(), e.Pos2Point(0.5), e.GetEnd()))
            e.SetC1C2(e.GetStart(), e.GetEnd()); //this makes it straight


    for (auto itr = new_path.begin(); itr !=new_path.end() && itr!=--new_path.end(); ++itr) {
        auto best_itr = std::next(itr);
        std::pair<double, double> best_pos{1, 0};
        std::pair<XY, XY> best_xy = {itr->GetEnd(), itr->NextTangentPoint(1)};
        for (auto i = std::next(itr); i!=new_path.end(); i++) {
            const auto pp = Tangent4Hull(left, *itr, *i);
            if (max_clockwise(!left, best_xy.first, best_xy.second, i->Pos2Point(pp.second))) {
                best_itr = i;
                best_pos = pp;
                best_xy = {itr->Pos2Point(pp.first), i->Pos2Point(pp.second)};
            }
        }
        //if we did not find a better line than the one connecting source and dest, we
        //continue - unless these do not connect. In this case we add this line.
        if (best_pos.first==1 && best_pos.second==0 && best_itr == std::next(itr) &&
            itr->GetEnd() == best_itr->GetStart())
            continue;
        //set beginning
        auto del_from = std::next(itr);
        if (best_pos.first==0)
            del_from = itr;
        else if (best_pos.first<1)
            itr->SetEnd(best_pos.first);
        //set ending
        auto del_to = best_itr;
        if (best_pos.second==1)
            del_to = std::next(best_itr);
        else if (best_pos.second>0)
            best_itr->SetStart(best_pos.second);
        best_itr = new_path.erase(del_from, del_to);
        if (best_xy.first!=best_xy.second)
            best_itr = new_path.emplace(best_itr, best_xy.first, best_xy.second);
        if (best_pos.first>0) //itr may have been invalidated if Container is a vector.
            itr = std::prev(best_itr);
        else
            itr = best_itr; //we have deleted the full former 'itr'
        if (best_xy.first!=best_xy.second)
            best_itr = std::next(best_itr); //step beyond the newly inserted line - no need to check that again.
    }
    return new_path;
}

/** Replace a path section in container 'in' to a section in container 'from'.
 * If 'connected_tolerance' is nonnegative, the two sections are assumed to start/end on the same coordinate
 * (or similar) and are snapped to equal values (Checked in connected_tolerance Manhattan distance and if failed,
 * we _ASSERT and return in.end() and don't change 'in'.) If negative, then we just cut and paste.
 * Containers are supposed to hold Edge descendants, one convertible to the other.
 * We handle the case when any of the pairs <first,last> or <from_first,from_last> warp around
 * the end of the container.
 * @returns the first edge inserted and the one after the last one inserted. If the thing to insert was of zero
 * length, we insert the latter twice.*/
template <typename Container1, typename Container2>
std::optional<std::pair<typename Container1::iterator, typename Container1::iterator>>
Replace(Container1 &in, typename Container1::iterator first, double first_pos, typename Container1::iterator last, double last_pos,
        const Container2 &from, typename Container2::const_iterator from_first, double from_first_pos, typename Container2::const_iterator from_last, double from_last_pos,
        double connected_tolerance = 0.01)
{
    _ASSERT(0<=first_pos && first_pos<=1);
    _ASSERT(0<=last_pos && last_pos<=1);
    //We gracefully handle the case when both 'from' iterators are at from.end()
    if (from_first==from.end() && from_last==from.end())
        return std::pair<typename Container1::iterator, typename Container1::iterator>{last, last}; //nothing inserted
    _ASSERT(0<=from_first_pos && from_first_pos<=1);
    _ASSERT(0<=from_last_pos && from_last_pos<=1);
    const XY start = first->Pos2Point(first_pos), end = last->Pos2Point(last_pos);
    if (connected_tolerance>=0) {
        const XY from_start = from_first->Pos2Point(from_first_pos), from_end = from_last->Pos2Point(from_last_pos);
        if (fabs(start.x-from_start.x)>connected_tolerance || fabs(start.y-from_start.y)>connected_tolerance ||
            fabs(end.x-from_end.x)>connected_tolerance || fabs(end.y-from_end.y)>connected_tolerance) {
            _ASSERT(0);
            return {};
        }
    }
    //First delete the unneeded edges from 'in'
    if (first==last) {
        last = in.insert(std::next(last), *last); // in case of vector 'first' is invalidated
        first = std::prev(last);
        if (first_pos>last_pos)
            std::swap(first, last);
    }
    if (first_pos) {
        first->SetEnd(start, first_pos);
        first++;
    }
    if (last_pos<1)
        last->SetStart(end, last_pos);
    else
        last++;
    //delete [first,last)
    //first and last here may both be in.end();
    //Do deletion in two big steps, to ease the burden on vectors
    auto till = first;
    while (till!=last && till!=in.end()) till++;
    if (till == last) {
        //just delete between first and till - both may be equal to in.end()
        last = in.erase(first, till); //recompute 'last' - we will insert before it
    } else {
        //two phase delete: from till->in.end() then from begin()->last
        _ASSERT(till==in.end());
        in.erase(first, till); //in this case last is *before* first and till & thus remains valid.
        last = in.erase(in.begin(), last);
    }
    //Now we need to insert before 'last'
    //Sanitize input
    if (from_first_pos==1) {
        from_first++;
        if (from_first==from.end()) from_first = from.begin();
        from_first_pos = 0;
    }
    if (from_last_pos==0) {
        if (from_last==from.begin())
            from_last = std::prev(from.end());
        else
            from_last--;
        from_last_pos = 1;
    }
    auto from_till = from_first;
    size_t steps = 0;
    if (from_first==from_last) {
        if (from_first_pos==from_last_pos)
            //we effectively insert nothing. Good to go.
            return std::pair<typename Container1::iterator, typename Container1::iterator>{last, last};
        if (from_first_pos<from_last_pos) {
            //We insert a single edge
            first = in.insert(last, *from_first);
            if (connected_tolerance>=0)
                first->SetStartEndIgn(start, end, from_first_pos, from_last_pos);
            else
                first->SetStartEndIgn(from_first_pos, from_last_pos); //actual endpoints may be different from start/end
            return std::pair<typename Container1::iterator, typename Container1::iterator>{first, std::next(first)};
        }
        //continue if from_first_pos > from_last_pos, so that we have to insert almost the entire 'from'
        while (from_till!=from.end()) from_till++, steps++; //in this case we copy till the end
    } else {
        while (from_till!=from_last && from_till!=from.end()) from_till++, steps++;
    }
    if (from_till == from_last) {
        //insert in one go
        first = last = in.insert(last, from_first, std::next(from_till));
        std::advance(last, steps);
    } else {
        //insert in two steps
        size_t steps2 = 0;
        from_till = from.begin();
        while (from_till!=from_last && from_till!=from.end()) from_till++, steps2++;
        if (from_till==from.end()) {
            //'from_last' is not part of 'from'
            _ASSERT(0);
            return {};
        }
        first = in.insert(last, from.begin(), std::next(from_last));
        last = first = in.insert(last, from_first, from.end());
        std::advance(last, steps+steps2);
    }
    //'first' is the first inserted, 'last' is the last inserted
    if (connected_tolerance>=0) {
        first->SetStart(start, from_first_pos);
        last->SetEnd(end, from_last_pos);
    } else {
        first->SetStart(from_first_pos); //actual endpoints may be different from start/end
        last->SetEnd(from_last_pos);
    }
    return std::pair<typename Container1::iterator, typename Container1::iterator>{first, std::next(last)};
}



/** Return a vector of points ignoring all the curvature of the edges.
 * Assumes connected path.*/
template <typename iter>
std::vector<XY> GetPoints(iter first, iter last)
{
    _ASSERT(IsConnected(first, last));
    std::vector<XY> points;
    if (first!=last) {
        points.push_back(first->GetStart());
        for (; first!=last; ++first)
            points.push_back(first->GetEnd());
    }
    return points;
}


/** Smoothen a path, by replacing straight edges with gentle curves.
 * All the edge endpoints remain the same as the number of edges.
 * We assume a connected series of edges and ignore if it is circular.
 * @param [in] first The first edge.
 * @param [in] last Iterator pointing beyond the last edge.
 * @param [in] threshold Straight edges shorter than this are kept straight.
 * @param [in] circular If first and prev(last) connect, we smooth them, as well.
 * @param [in] limit_length If true, we do not allow the resulting control points to cross, thus the
 *                          crosspoint of the infinite lines start->C1 and end->C2 must be outside both
 *                          sections. If this would not be the case, we limit the lengths.
 * @param [in] Length A function double Length(const Edgeish &A, double orig_len, double other_len, bool start),
 *                   where Edgeish is the type pointed by 'iter'.
 *                   The function shall return a length: how far the control point shall be
 *                   from the Edge endpoint. A value of zero returned
 *                   will prevent smoothing at the end specified by 'start'.
 *                   'orig_len' is the original length of 'A', when it was straight (one of its endpoints
 *                   may have been switched to curvy by the time of calling Length()), while
 *                   'other_len' is the length of the neighbouring edge (preceding if start==true,
 *                   following, if start==false). other_len==0 if the neighbouring edge is curvy.
 *                   It defaults to returning 20% of the length.*/
template <typename iter, typename lenfunc>
void SmoothenVariableRatio(iter first, iter last, double threshold = 3, bool circular = true, bool limit_length = true,
                           lenfunc Length = [](typename iter::value_type a, double l, double, bool)->double {return 0.2*l; })
{
    if (first==last) return;
    //Find the last eligible & connected element. If prev==last, it means no such element
    iter prev = last;
    //Length of the original (non-curvified) prev, if that was straight. Zero if it was curvy to begin with
    double prev_len = 0;
    if (circular && first->GetStart()==std::prev(last)->GetEnd()) {
        //check for ==last, as last may ==end() and this catches when first & last is not from the same container.
        while (prev!=first && prev!=last && prev->GetLength()<threshold)
            if (std::prev(prev)->GetEnd()==prev->GetStart())
                prev--;
            else {
                prev_len = -1; //signal disconnectedness
                break;
            }
        if (prev==first || prev==last) return; //Only a single eligible edge or error
        if (prev_len==-1) {
            //no eligible, connected previous edge
            prev = last;
            prev_len = 0;
        } else {
            prev_len = prev->IsStraight() ? prev->GetLength() : 0;
        }
    }
    //past_len is zero if an edge is curvy
    for (auto i = first; i!=last; i++) {
        //If we are disconnected, kill the value in 'prev'
        if (i!=first && std::prev(i)->GetEnd()!=i->GetStart())
            prev = last;
        //if we are too short, just continue
        if (i->GetLength()<threshold)
            continue;
        const double len = i->IsStraight() ? i->GetLength() : 0;
        if (i->IsStraight()) {
            const double my_dist = Length(*i, len, prev_len, true);
            if (my_dist) {
                if (prev != last) {
                    //If there is a previous, connected edge, smoothen our start and perhaps
                    //the end of that prev edge (if that was straight to begin with)
                    //Let us start by checking the control point distances first
                    const double prev_dist = Length(*prev, prev_len, len, false);
                    if (prev_len && prev_dist) {
                        //prev was originally straight (only we may have made it curvy)
                        const XY delta = ((i->GetStart()-i->GetEnd()).Normalize() + (prev->GetStart() - prev->GetEnd()).Normalize()).Normalize();
                        if (prev->IsStraight())
                            prev->SetC1C2(prev->GetStart(), prev->GetEnd() + delta*prev_dist);
                        else
                            prev->SetC2(prev->GetEnd() + delta*prev_dist);
                        if (limit_length)
                            prev->Still();
                        i->SetC1C2(i->GetStart()-delta*my_dist, i->GetEnd());
                    } else if (std::next(prev)==i) {
                        //prev was curvy to begin with or its Length is zero: align us to its tangent
                        //(but only if we immediately follow each other, else we may get all sorts of
                        //quirks)
                        const XY delta = (prev->NextTangentPoint(1)-prev->GetEnd()).Normalize();
                        i->SetC1C2(i->GetStart() + delta*my_dist, i->GetEnd());
                    }
                }
                prev_len = len;
                prev = i;
                continue;
            }
        }
        //We get here if 'i' is either curvy or i->Length() has returned zero for the starting end
        if (prev!=last && prev_len && std::next(prev)==i) {
            //(but only if we immediately follow each other, else we may get all sorts of
            //quirks)
            const double prev_dist = Length(*prev, prev_len, len, false);
            //OK, we are curvy or cannot smoothen at all, but may need to set the end of the prev curve
            if (prev_dist) {
                prev->SetC1C2(prev->IsStraight() ? prev->GetStart() : prev->GetC1(),
                              prev->GetEnd() + (i->PrevTangentPoint(0)-i->GetStart()).Normalize()*prev_dist);
                if (limit_length)
                    prev->Still();
            }
        }
        prev_len = len; //if we are straight, this must be nonzero
        prev = i;
    }
}


/** Helper structure to determine if a type has a member function called
 * Eligible().*/
template <typename T>
struct CallEligible
{
    /* SFINAE Eligible-has-correct-sig :) */
    template<typename A>
    static std::true_type test(bool (A::*)() const)
    {
        return std::true_type();
    }

    /* SFINAE Eligible-exists :) */
    template <typename A>
    static decltype(test(&A::Eligible))
        test(decltype(&A::Eligible), void *)
    {
        /* Eligible exists. What about sig? */
        typedef decltype(test(&A::Eligible)) return_type;
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...)
    {
        return std::false_type();
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0, 0)) type;

    static const bool value = type::value; /* Which is it? */

    /*  `eval(T const &,std::true_type)`
    delegates to `T::foo()` when `type` == `std::true_type`*/
    static bool eval(T const & t, std::true_type)
    {
        return t.Eligible();
    }
    /* `eval(...)` is a no-op for otherwise unmatched arguments */
    static bool eval(...)
    {
        return true;
    }

    /* `eval(T const & t)` delegates to :-
    - `eval(t,type()` when `type` == `std::true_type`
    - `eval(...)` otherwise
    */
    static bool eval(T const & t)
    {
        return eval(t, type());
    }
};




/** Smoothen a path, by replacing straight edges with gentle curves.
 * All the edge endpoints remain the same as the number of edges.
 * We assume a connected series of edges and ignore if it is circular.
 * If the value type of 'iter' has a bool Eligible() const member,
 * it is called. If it returns false, we do not replace the straight edge
 * to a curve.
 * @param [in] first The first edge.
 * @param [in] last Iterator pointing beyond the last edge.
 * @param [in] fix_ratio A number between (0, 1) determining what fraction of the
 *             straight edge length will be the control point's distance from the
 *             end of the curve inserted. Larger numbers result in wider curves.
 * @param [in] threshold Straight edges shorter than this are kept straight.
 * @param [in] circular If true and the first and last edge connect, we
 *                      smoothen their connection, too.
 * @param [in] limit_length If true, we do not allow the resulting control points to cross, thus the
 *                          crosspoint of the infinite lines start->C1 and end->C2 must be outside both
 *                          sections. If this would not be the case, we limit the lengths.*/
template <typename iter>
void Smoothen(iter first, iter last, double fix_ratio=0.2, double threshold=3,
              bool circular = true, bool limit_length = true)
{
    SmoothenVariableRatio(first, last, threshold, circular, limit_length,
                          [fix_ratio](const typename iter::value_type &e, double len, double other_len, bool)
                          { return CallEligible<typename iter::value_type>::eval(e) ?
                          fix_ratio*(other_len ? std::min(len, other_len) : len) : 0; });
}

/** Determines the angle of the path at the given position.
 * What we return is a fake radian, but it also includes a curvature (see RayAngle).
 * This is solely useful to see how two edges depart from a cross or touchpoint.
 * If we query the angle in a vertex (where the tangent may abruptly change), it will
 * always take the following edge if forward==true and the preceding edge otherwise.
 * @param [in] first The first edge in the path.
 * @param [in] last Iterator pointing beyond the last edge of the path.
 * @param [in] edge The edge where the point in question lies.
 * @param [in] pos The position on the edge.
 * @param [in] forward if true we tell you the angle of the curve in the forward dir.
 * @param [in] circular If true and the first and last edge connect, we
 *                      return Angle(first, 0, forward) when asked of (last, 1, forward).*/
template <typename iter>
RayAngle Angle(iter first, iter last, iter edge, double pos, bool forward, bool circular = true)
{
    _ASSERT(first!=last);
    if (pos==0 && !forward)
    if (pos==0) {
        if (!forward) {
            if (edge==first) {
                if (circular)
                    return std::prev(last)->Angle(true, 1);
                else
                    return RayAngle(edge->Angle(false, 0).Opposite().angle, 0); //A linear extension
            }
            return std::prev(edge)->Angle(true, 1);
        } else if (edge!=last)  //Normal case
            return edge->Angle(!forward, pos);
        else {//Special case of pos==0 on the beyond last edge
            edge--;
            pos = 1;
            //fallthrough to next if
        }
    }
    if (pos==1 && forward){
        if (edge==std::prev(last)) {
            if (circular)
                return first->Angle(false, 0);
            else
                return RayAngle(edge->Angle(true, 1).Opposite().angle, 0); //A linear extension
        }
        return std::next(edge)->Angle(false, 0);
    }
    return edge->Angle(!forward, pos);
}

/** Creates a comma separated list of dumped version of the objects.
 * Calls iter->Dump(precise).
 * @param [in] precise
 * @param [in] first The first edge or point in the list.
 * @param [in] last Iterator pointing beyond the last edge or point of the list.
 * @param [in] pre String to prepend to each dumped guy
 * @param [in] post String to append to each dumped guy (after a comma)
 * @returns the full string.*/
template <typename iter>
std::string Dump(bool precise, iter first, iter last, const std::string &pre = {}, const std::string &post = "\n")
{
    std::string ret;
    while (first!=last)
        ret += pre + (first++)->Dump(precise) + "," + post;
    return ret;
}

template <typename Edgeish>
class EdgeVector;

template <typename iter>
EdgeVector<Edge> CreateRounded(iter first, iter last,
                               double radius, bool bevel, bool circular = true, double min_radius = -1);

/** A generic list holding descendants of class Edge.*/
template <typename Edgeish>
class EdgeList : public std::list<Edgeish>
{
public:
    using std::list<Edgeish>::list;
    using std::list<Edgeish>::splice;
    using std::list<Edgeish>::begin;
    using std::list<Edgeish>::end;
    using std::list<Edgeish>::cbegin;
    using std::list<Edgeish>::cend;
    using std::list<Edgeish>::push_back;
    using std::list<Edgeish>::pop_back;
    using std::list<Edgeish>::insert;
    using std::list<Edgeish>::emplace;
    using std::list<Edgeish>::emplace_back;
    using std::list<Edgeish>::erase;
    using std::list<Edgeish>::front;
    using std::list<Edgeish>::back;
    using std::list<Edgeish>::size;
    using std::list<Edgeish>::swap;
    using std::list<Edgeish>::clear;
    using std::list<Edgeish>::reverse;
    using typename std::list<Edgeish>::iterator;
    using typename std::list<Edgeish>::const_iterator;

    iterator next(iterator i) { return std::next(i) == end() ? begin() : std::next(i); }
    iterator prev(iterator i) { return i == begin() ? --end() : std::prev(i); }
    const_iterator next(const_iterator i) const { return std::next(i) == end() ? begin() : std::next(i); }
    const_iterator prev(const_iterator i) const { return i == begin() ? --end() : std::prev(i); }

    EdgeList &append(std::list<Edgeish> &&o) { splice(end(), o); return *this; }
    EdgeList &append(const std::vector<Edgeish> &o) { for (auto &e : o) push_back(e); return *this; }
    EdgeList &append(std::span<const XY> o, bool ensure_connect = false);
    EdgeList<Edgeish> &Invert() { for (auto &e:*this) e.Invert(); reverse(); return *this; } ///<Invert the direction of the path
    /** Removes short edges, keeps connected what was connected.
     * If circular is true, we keep a connected first and last edge connected.
     * @returns true if we have made a change.*/
    bool RemoveDots(double tolerance = SMALL_NUM, bool circular = true);
    /** Removes short edges, keeps connected what was connected and also combines edges if possible
    * If circular is true, we keep a connected first and last edge connected.
    * @returns true if we have made a change.*/
    bool Simplify(double tolerance = SMALL_NUM, bool circular = true);
    EdgeList<Edgeish> &LinearExtend(double length, bool forward, bool visible);
    /** Makes 'i' the first edge by appending anything before it to the end.
     * Closed paths remain closed, with their clockwiseness intact.*/
    EdgeList<Edgeish> &Cycle(iterator i) { EdgeList<Edgeish> tmp; tmp.splice(tmp.end(), *this, begin(), i); splice(end(), tmp); return *this; }
    void Apply(Edge::Update u) { if (u.DoesSomething()) for (Edgeish &e : *this) e.Apply(u); }
    /** Creates a comma separated list of dumped version of the edges.
     * Calls Edgeish->Dump().
     * @param [in] type The name of our type.
     * @param [in] pre String to prepend to each dumped guy
     * @param [in] post String to append to each dumped guy (after a comma)
     * @returns the full string.*/
    std::string Dump(std::string_view type, const std::string &pre = {}, const std::string &post = "\n") const
        { return StrCat(type,'{',post, contour::Dump(begin(), end(), pre, post), '}'); }
};

template <typename Edgeish>
EdgeList<Edgeish> &EdgeList<Edgeish>::append(std::span<const XY> o, bool ensure_connect)
{
    if (o.size()) {
        if (ensure_connect && size() && XY(o[0])!=back().GetEnd()) {
            const XY tmp = back().GetEnd();
            emplace_back(tmp, XY(o[0]));
        }
        for (auto i = o.begin(), j = std::next(i); j!=o.end(); i++, j++)
            emplace_back(*i, *j);
    }
    return *this;
}


/** Remove edges shorter than 'tolerance', while keeping connected
 * segments connected. If 'circular' is true, we keep a connected
 * start and endpoint connected even if the first/last edge was
 * removed.
 * @returns true if a change was made.*/
template <typename Edgeish>
bool EdgeList<Edgeish>::RemoveDots(double tolerance, bool circular)
{
    bool ret = false;
    for (auto i = begin(); i!=end(); /*nope*/)
        if (i->IsDot(tolerance)) {
            //connect if it was connected before deleting
            if ((circular || (i!=begin() && i!=--end())) &&
                prev(i)->GetEnd() == i->GetStart() && i->GetEnd() == next(i)->GetStart())
                next(i)->SetStartOnly(prev(i)->GetEnd());
            erase(i++);
            ret = true;
        } else
            i++;
    return ret;
}

/** Remove edges shorter than 'tolerance', while keeping connected
 * segments connected. If 'circular' is true, we keep a connected
 * start and endpoint connected even if the first/last edge was
 * removed. We also combine subsequent edges that can be expressed
 * as a single edge.
 * @returns true if a change was made.*/
template <typename Edgeish>
bool EdgeList<Edgeish>::Simplify(double tolerance, bool circular)
{
    bool ret = false;
    bool had_dots;
    do {
        had_dots = false;
        ret |= RemoveDots(tolerance, circular);
        if (size()<2) return ret;
        for (auto i = ++begin(); i!=end(); /*nope*/)
            if (std::prev(i)->CheckAndCombine(*i, tolerance)) {
                had_dots |= std::prev(i)->IsDot();
                i = erase(i);
                ret = true;
            } else
                i++;
        if (size()>=2 && circular
            && back().CheckAndCombine(front(), tolerance)) {
            had_dots |= back().IsDot();
            erase(begin());
            ret = true;
        }
    } while (had_dots);
    return ret;
}

/** Extend the path with a linear segment of 'length' length.
 * Note that if the path starts/end with a straight edge, we
 * simple make it longer - so number of edges don't necessarily grow.
 * For the empty path, we do nothing, but that is considered an error.
 * @param [in] length The length of the extension. Should be positive.
 * @param [in] forward If true we extend at the end, else in the start.
 * @param [in] visible The visibility of the extension. */
template <typename Edgeish>
EdgeList<Edgeish> &EdgeList<Edgeish>::LinearExtend(double length, bool forward, bool visible)
{
    _ASSERT(length>0);
    _ASSERT(size());
    if (length<=0) return *this;
    auto res = (forward ? back() : front()).LinearExtend(length, forward, visible);
    if (res.first) {
        if (forward)
            push_back(res.second);
        else
            push_front(res.second);
    }
    return *this;
}

/** A generic vector of descendants of class Edge with
 * many useful utilities.*/
template <typename Edgeish>
class EdgeVector : public std::vector<Edgeish>
{
public:
    using std::vector<Edgeish>::vector;
    using std::vector<Edgeish>::begin;
    using std::vector<Edgeish>::end;
    using std::vector<Edgeish>::cbegin;
    using std::vector<Edgeish>::cend;
    using std::vector<Edgeish>::push_back;
    using std::vector<Edgeish>::pop_back;
    using std::vector<Edgeish>::insert;
    using std::vector<Edgeish>::emplace;
    using std::vector<Edgeish>::emplace_back;
    using std::vector<Edgeish>::erase;
    using std::vector<Edgeish>::at;
    using std::vector<Edgeish>::front;
    using std::vector<Edgeish>::back;
    using std::vector<Edgeish>::size;
    using std::vector<Edgeish>::swap;
    using std::vector<Edgeish>::clear;
    using std::vector<Edgeish>::resize;
    using std::vector<Edgeish>::reserve;
    using std::vector<Edgeish>::empty;
    using typename std::vector<Edgeish>::iterator;
    using typename std::vector<Edgeish>::const_iterator;

    EdgeVector() = default;
    EdgeVector(const EdgeVector<Edgeish> &) = default;
    EdgeVector(EdgeVector<Edgeish> &&) = default;
    EdgeVector(const SimpleContour &o);
    EdgeVector(SimpleContour &&o);
    EdgeVector(const ContourList &o);
    EdgeVector(const HoledSimpleContour &o);
    EdgeVector(HoledSimpleContour &&o);
    EdgeVector(const Contour &o);
    EdgeVector(Contour &&o);
    EdgeVector(std::span<const Edge> o) { append(o); }
    EdgeVector(std::span<const XY> o) { append(o, false); }
    EdgeVector(std::initializer_list<const XY> o) { append(o, false); }
    template<size_t SIZE> EdgeVector(const XY(&v)[SIZE]) { append(std::span<const XY>(v, SIZE)); }
    EdgeVector(std::initializer_list<Edge> l) { reserve(l.size());  for (auto &e : l) push_back(e); }
    EdgeVector(const XY &c, double radius_x, double radius_y, double tilt_deg, double s_deg, double d_deg, bool clockwise, bool add_closing_line)
    { AppendEllipse(c, radius_x, radius_y, tilt_deg, s_deg, d_deg, clockwise, false, add_closing_line); }
    EdgeVector<Edgeish> &operator=(const EdgeVector<Edgeish> &) = default;
    EdgeVector<Edgeish> &operator=(EdgeVector<Edgeish> &&) = default;
    EdgeVector<Edgeish> &operator=(std::initializer_list<Edge> l) { clear(); reserve(l.size());  for (auto &e : l) push_back(e); return *this; }
    EdgeVector<Edgeish> &operator=(std::initializer_list<const XY> l) { clear(); append(l); return *this; }
    EdgeVector<Edgeish> &append(const XY &p) { if (size() && back().GetEnd()!=p) { XY tmp = back().GetEnd(); push_back({tmp, p}); } return *this; }
    EdgeVector<Edgeish> &append(const Edgeish &o, bool ensure_connect=false) { if (ensure_connect) append(o.GetStart()); push_back(o); return *this; }
    EdgeVector<Edgeish> &append(Edgeish &&o, bool ensure_connect = false) { if (ensure_connect) append(o.GetStart()); push_back(std::move(o)); return *this; }
    EdgeVector<Edgeish> &append(const std::list<Edgeish> &o, bool ensure_connect = false) { if (ensure_connect && o.size()) append(o.front().GetStart()); for (auto &e : o) push_back(e); return *this; }
    EdgeVector<Edgeish> &append(std::vector<Edgeish> &&o, bool ensure_connect = false) { if (ensure_connect && o.size()) append(o.front().GetStart()); std::move(o.begin(), o.end(), std::insert_iterator<EdgeVector<Edgeish>>(*this, end())); return *this; }
    EdgeVector<Edgeish> &append(const std::vector<Edgeish> &o, bool ensure_connect = false) { if (ensure_connect && o.size()) append(o.front().GetStart()); std::copy(o.begin(), o.end(), std::insert_iterator<EdgeVector<Edgeish>>(*this, end())); return *this; }
    EdgeVector<Edgeish> &append(std::span<const Edge> o, bool ensure_connect = false) { if (ensure_connect && o.size()) append(o.begin()->GetStart()); reserve(size()+o.size()); for (auto &e : o) push_back(e); return *this; }
    EdgeVector<Edgeish> &append(const std::vector<XY> &o, bool ensure_connect = false) { return append(std::span<const XY>(o), ensure_connect); }
    EdgeVector<Edgeish> &append(std::span<const XY> o, bool ensure_connect = false);
    EdgeVector<Edgeish> &append(std::initializer_list<const XY> o, bool ensure_connect = false);
    template <typename Edgeish_iter> EdgeVector<Edgeish> &append(Edgeish_iter first, Edgeish_iter last, bool ensure_connect = false) { if (ensure_connect && first!=last) append(first->GetStart()); std::copy(first, last, std::insert_iterator<EdgeVector<Edgeish>>(*this, end())); return *this; }
    template <typename XYish_iter> EdgeVector<Edgeish> &appendpoints(XYish_iter first, XYish_iter last, bool ensure_connect = false);
    EdgeVector<Edgeish> &prepend(const XY &p) { if (size() && front().GetStart()!=p) { XY tmp = front().GetStart(); insert(begin(), {p, tmp}); } return *this; }
    EdgeVector<Edgeish> &prepend(const Edgeish &o, bool ensure_connect = false) { if (ensure_connect) prepend(o.GetStart()); insert(begin(), o); return *this; }
    EdgeVector<Edgeish> &prepend(Edgeish &&o, bool ensure_connect = false) { if (ensure_connect) prepend(o.GetStart()); insert(begin(), std::move(o)); return *this; }
    EdgeVector<Edgeish> &prepend(std::list<Edgeish> &&o, bool ensure_connect = false) { if (ensure_connect && o.size()) prepend(o.back().GetEnd()); insert(begin(), o.begin(), o.end()); return *this; }
    EdgeVector<Edgeish> &prepend(std::vector<Edgeish> &&o, bool ensure_connect = false) { if (ensure_connect && o.size()) prepend(o.back().GetEnd()); std::move(o.begin(), o.end(), std::insert_iterator<EdgeVector<Edgeish>>(*this, begin())); return *this; }
    EdgeVector<Edgeish> &prepend(const std::vector<Edgeish>& o, bool ensure_connect = false) { if (ensure_connect && o.size()) prepend(o.back().GetEnd()); std::copy(o.begin(), o.end(), std::insert_iterator<EdgeVector<Edgeish>>(*this, begin())); return *this; }
    EdgeVector<Edgeish> &prepend(std::span<const Edge> o, bool ensure_connect = false) { if (ensure_connect && o.size()) prepend(std::prev(o.end())->GetEnd()); insert(begin(), o.begin(), o.end()); return *this; }

    size_t next(size_t vertex) const noexcept { return vertex+1<size() ? vertex+1 : 0; }         ///<Returns index of subsequent edge, warps around to 0 at last one. Always returns in range [0..size-1]
    size_t prev(size_t vertex) const noexcept { return vertex-1<size() ? vertex-1 : size()-1; }  ///<Returns index of preceeding edge, warps around at 0 to last edge.  Always returns in range [0..size-1]
    Edge &at_next(size_t i) noexcept { return this->operator[](next(i)); }  ///<Returns reference to edge after index `i`. Wraps around at last edge.
    Edge &at_prev(size_t i) noexcept { return this->operator[](prev(i)); }  ///<Returns reference to edge before index `i`. Wraps around at first edge.
    const Edge &at_next(size_t i) const noexcept { return this->operator[](next(i)); }  ///<Returns reference to edge after index `i`. Wraps around at last edge.
    const Edge &at_prev(size_t i) const noexcept { return this->operator[](prev(i)); }  ///<Returns reference to edge before index `i`. Wraps around at first edge.

    /** Returns true if the previous edge, connects to 'edge'. If circular is true and edge is zero, we test size()-1.*/
    bool prev_connected(size_t edge, bool circular) const noexcept { return edge>0 ? at_prev(edge).GetEnd()==at(edge).GetStart() : circular && back().GetEnd()==front().GetStart(); }
    /** Returns true if the next edge, connects to 'edge'. If circular is true and edge is the last edge, we test the first edge.*/
    bool next_connected(size_t edge, bool circular) const noexcept { return edge<size()-1 ? at_next(edge).GetEnd()==at(edge).GetStart() : circular && back().GetEnd()==front().GetStart(); }

    Block CalculateBoundingBox() const { return contour::CalculateBoundingBox(begin(), end()); } ///<Calculates the bounding box of us. Moderately expensive.
    bool IsEmpty() const noexcept { return size()==0; }            ///<Returns if the shape is empty (no edges).
    double GetLength(bool include_hidden [[maybe_unused]] = false) const noexcept { return contour::GetLength(begin(), end()); }

    std::vector<EdgeVector<Edgeish>> ConvertToClosed(ECloseType close = ECloseType::IGNORE_OPEN_PATH) const
        { return contour::ConvertToClosed<EdgeVector<Edgeish>>(begin(), end(), close); }

    EdgeVector<Edgeish> &Invert() noexcept { for (auto &e:*this) e.Invert(); std::reverse(begin(), end()); return *this; } ///<Invert the direction of the path
    EdgeVector<Edgeish> &Shift(const XY &xy) noexcept { for (auto &e : *this) e.Shift(xy); return *this; } ///<Translates the path.
    EdgeVector<Edgeish> &Scale(double sc) noexcept { for (auto &e : *this) e.Scale(sc); return *this; } ///<Scale the path.
    EdgeVector<Edgeish> &Scale(const XY &sc) noexcept { for (auto &e : *this) e.Scale(sc); return *this; } ///<Scale the path.
    EdgeVector<Edgeish> &SwapXY() noexcept { for (auto &e : *this) e.SwapXY(); return *this; } ///<Transpose the path: swap XY coordinates
    EdgeVector<Edgeish> &Rotate(double degree) noexcept { return Rotate(cos(degree/180*M_PI), sin(degree/180*M_PI)); } ///<Rotate path around origin
    EdgeVector<Edgeish> &Rotate(double cos, double sin) noexcept { for (auto &e : *this) e.Rotate(cos, sin); return *this; } ///<Rotate the path by `radian`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> &RotateAround(const XY&c, double degree) noexcept { return RotateAround(c, cos(degree/180*M_PI), sin(degree/180*M_PI)); } ///<Rotat path around 'c'
    EdgeVector<Edgeish> &RotateAround(const XY&c, double cos, double sin) noexcept { for (auto &e : *this) e.RotateAround(c, cos, sin); return *this; } ///<Rotate the path by `radian` around `c`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> &Transform(const TRMatrix & M) noexcept { for (auto &e: *this) e.Apply(M); return *this; } ///<Applies a transform. If it is an inverting transform, we do not invert the edge series.

    EdgeVector<Edgeish> CreateInverted() const { EdgeVector<Edgeish> tmp(*this); return tmp.Invert(); } ///<Invert the direction of the path
    EdgeVector<Edgeish> CreateShifted(const XY &xy) const { EdgeVector<Edgeish> tmp(*this); return tmp.Shift(xy); } ///<Translates the path.
    EdgeVector<Edgeish> CreateScaled(double sc) const { EdgeVector<Edgeish> tmp(*this); return tmp.Scale(sc); } ///<Scale the path.
    EdgeVector<Edgeish> CreateScaled(const XY &sc) const { EdgeVector<Edgeish> tmp(*this); return tmp.Scale(sc); } ///<Scale the path.
    EdgeVector<Edgeish> CreateSwapXYd() const { EdgeVector<Edgeish> tmp(*this); return tmp.SwapXY(); } ///<Transpose the path: swap XY coordinates
    EdgeVector<Edgeish> CreateRotated(double degree) const { EdgeVector<Edgeish> tmp(*this); return tmp.Rotate(degree); } ///<Rotate the path by `radian`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> CreateRotated(double cos, double sin) const { EdgeVector<Edgeish> tmp(*this); return tmp.Rotate(cos, sin); } ///<Rotate the path by `radian`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> CreateRotatedAround(const XY&c, double degree) const { EdgeVector<Edgeish> tmp(*this); return tmp.RotateAround(c, degree); } ///<Rotate the path by `radian` around `c`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> CreateRotatedAround(const XY&c, double cos, double sin) const { EdgeVector<Edgeish> tmp(*this); return tmp.RotateAround(c, cos, sin); } ///<Rotate the path by `radian` around `c`. `sin` and `cos` are pre-computed values.
    EdgeVector<Edgeish> CreateTransformed(const TRMatrix & M) { EdgeVector<Edgeish> tmp(*this); return tmp.Transform(M); } ///<Applies a transform. If it is an inverting transform, we do not invert the edge series.

    EdgeVector<Edgeish> CreateRounded(double radius, bool bevel,
                                      bool circular = true, double min_radius = -1)
    { return contour::CreateRounded(begin(), end(), radius, bevel, circular, min_radius); }

    /** Smoothen a path, by replacing straight edges with gentle curves.
     * All the edge endpoints remain the same as the number of edges.
     * We assume a connected series of edges and ignore if it is circular.
     * If the value type of 'iter' has a bool Eligible() const member,
     * it is called. If it returns false, we do not replace the straight edge
     * to a curve.
     * @param [in] fix_ratio A number between (0, 1) determining what fraction of the
     *             straight edge length will be the control point's distance from the
     *             end of the curve inserted. Larger numbers result in wider curves.
     * @param [in] threshold Straight edges shorter than this are kept straight.
     * @param[in] circular If first and prev(last) connect, we smooth them, as well.
     * @param[in] limit_length If true, we do not allow the resulting control points to cross, thus the
     *                          crosspoint of the infinite lines start->C1 and end->C2 must be outside both
     *                          sections.If this would not be the case, we limit the lengths.*/
    void Smoothen(double fix_ratio = 0.2, double threshold = 3,
                  bool circular=true, bool limit_length = true)
    { contour::Smoothen(begin(), end(), fix_ratio, threshold, circular, limit_length); }

    /** Replace a path section with another path section.
     * If 'connected_tolerance' is nonnegative, the two sections are assumed to start/end on the same coordinate
     * (or similar) and are snapped to equal values (Checked in connected_tolerance Manhattan distance and if failed,
     * we _ASSERT and return in.end() and don't change 'in'.) If negative, then we just cut and paste.
     * Containers are supposed to hold Edge descendants, one convertible to the other.
     * We handle the case when any of the pairs <first,last> or <from_first,from_last> warp around
     * the end of the vector.
     * @returns the positions of the inserted path in the new return. If the inserted path is of nonzero
     *          length we always return PathPos(x,0)->PathPos(y,1). We don't return value on parameter problem.*/
    template <typename Edgeish2>
    std::optional<std::pair<PathPos, PathPos>>
        Replace(const PathPos&first, const PathPos &last, const EdgeVector<Edgeish2> &from,
                const PathPos&from_first, const PathPos &from_last, double connected_tolerance = 0.01) {
        auto ii = contour::Replace(*this, begin()+first.edge, first.pos, begin()+last.edge, last.pos,
                                   from, from.begin()+from_first.edge,
                                   from_first.pos, from.begin()+from_first.edge, from_first.pos,
                                   from.begin()+from_last.edge, from_last.pos, connected_tolerance);
        std::optional<std::pair<PathPos, PathPos>> ret;
        if (ii) {
            if (ii.first!=ii.last)
                ret.emplace(PathPos(ii.first-begin(), 0), PathPos(ii.second-1-begin(), 1));
            else if (ii.first==end())
                ret.emplace(PathPos(0, 0), PathPos(0, 0));
            else
                ret.emplace(PathPos(ii.first-begin(), 0), PathPos(ii.first-begin(), 0));
        }
        return ret;
    }

    /** Replace a path section with another path.
     * If 'connected_tolerance' is nonnegative, the two sections are assumed to start/end on the same coordinate
     * (or similar) and are snapped to equal values (Checked in connected_tolerance Manhattan distance and if failed,
     * we _ASSERT and return in.end() and don't change 'in'.) If negative, then we just cut and paste.
     * Containers are supposed to hold Edge descendants, one convertible to the other.
     * We handle the case when any of the pairs <first,last> warps around the end of the vector.
     * @returns the positions of the inserted path in the new return. If the inserted path is of nonzero
     *          length we always return PathPos(x,0)->PathPos(y,1). We don't return value on parameter problem.*/
    template <typename Edgeish2>
    std::optional<std::pair<PathPos, PathPos>>
        Replace(const PathPos&first, const PathPos &last, const EdgeVector<Edgeish2> &from, double connected_tolerance = 0.01) {
        auto ii = contour::Replace(*this, begin()+first.edge, first.pos, begin()+last.edge, last.pos,
                                   from, from.begin(), 0, --from.end(), 1, connected_tolerance);
        std::optional<std::pair<PathPos, PathPos>> ret;
        if (ii) {
            if (ii.first!=ii.last)
                ret.emplace(PathPos(ii.first-begin(), 0), PathPos(ii.second-1-begin(), 1));
            else if (ii.first==end())
                ret.emplace(PathPos(0, 0), PathPos(0, 0));
            else
                ret.emplace(PathPos(ii.first-begin(), 0), PathPos(ii.first-begin(), 0));
        }
        return ret;
    }

    /** Remove edges shorter than 'tolerance', while keeping connected
     * segments connected. If 'circular' is true, we keep a connected
     * start and endpoint connected even if the first/last edge was
     * removed.
     * @returns true if a change was made.*/
    bool RemoveDots(double tolerance = SMALL_NUM, bool circular = true);
    /** Remove edges shorter than 'tolerance', while keeping connected
     * segments connected. If 'circular' is true, we keep a connected
     * start and endpoint connected even if the first/last edge was
     * removed. We also combine subsequent edges that can be expressed
     * as a single edge.
     * @returns true if a change was made.*/
    bool Simplify(double tolerance = SMALL_NUM, bool circular = true);
    /** If the path crosses itself, remove the loops from its middle.
     * It makes sense only if the path is connected from first edge to last.
     * It may be circular (start==end), but we ignore that. After this call
     * the path will not cross itself (except from start==end perhaps).
     * In case of pretzel-like crosses, it is undefined which section do
     * we leave or take out - but the path will not cross itself after.
     *
     * @verbatim
     *           *
     *   *   ****+****
     *    * *    *    *
     *     +     *    *
     *    *  ****+****
     *    *      *
     *    *******
     * @endverbatim
     * @param [in] self If true, we also remove loops caused by edges crossing themselves.
     * @returns true if a change was made.*/
    bool RemoveLoops(bool self = true) { return Edge::RemoveLoops(*this, self); }
    /** Return true if all edges connect to the next. The last may not connect to the first. An empty Vector is connected.*/
    bool IsConnected() const { return contour::IsConnected(begin(), end()); }
    /** Return if all edges connect to the next and the last to the first. Any empty Vector is closed.*/
    bool IsClosed() const { return contour::IsClosed(begin(), end()); }
    /** Returns true if this position is valid for this path.*/
    bool IsValidPos(const PathPos &p) const { return p.edge<size() && 0<=p.pos && p.pos<=1; }
    /** Returns the coordinates at this position.*/
    XY GetPoint(const PathPos &p) const { return at(p.edge).Pos2Point(p.pos); }
    /** Take tangent of `edge` at `pos` and return a point on the line of a tangent towards the start of curve/edge. */
    XY PrevTangentPoint(PathPos pos, bool circular) const
    { return (test_smaller(0, pos.pos) || !prev_connected(pos.edge, circular) ? at(pos.edge) : at_prev(pos.edge)).PrevTangentPoint(test_smaller(0, pos.pos) ? pos.pos : 0); }
    /** Take tangent of `edge` at `pos` and return a point on the line of a tangent towards the end of curve/edge. */
    XY NextTangentPoint(PathPos pos, bool circular) const
    { return (test_smaller(pos.pos, 1) || !next_connected(pos.edge, circular) ? at(pos.edge) : at_next(pos.edge)).NextTangentPoint(test_smaller(pos.pos, 1) ? pos.pos : 1); }
    /** Returns a tangent point at this position */
    XY GetTangent(const PathPos &p, bool forward, bool circular) const { return forward ? NextTangentPoint(p, circular) : PrevTangentPoint(p, circular); }
    /** Returns the angle of the path at the pos. This includes a fake radian and a curvature.*/
    RayAngle Angle(const PathPos &p, bool forward, bool circular) const { return contour::Angle(begin(), end(), begin()+p.edge, p.pos, forward, circular); }
    /** Get the position at the beginning of the path (0,0)*/
    PathPos GetStartPos() const { return PathPos(0, 0); }
    /** Get the position at the end of the path*/
    PathPos GetEndPos() const { _ASSERT(!IsEmpty());  return PathPos(size()-1, 1); }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, bool me_closed, const Block *bb_me,
                     const EdgeVector<Edgeish> &o, bool o_closed = true, const Block *bb_o = nullptr,
                     CPTasks to_do = {}, bool swap=false) const
    { return CrossPoints(ret, me_closed, bb_me, o.begin(), o.end(), o_closed, bb_o, to_do, swap); }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    std::vector<CPData> CrossPoints(bool me_closed, const Block *bb_me,
                                    const EdgeVector<Edgeish> &o, bool o_closed = true, const Block *bb_o = nullptr,
                                    CPTasks to_do = {}, bool swap=false) const
    { return CrossPoints(me_closed, bb_me, o.begin(), o.end(), o_closed, bb_o, to_do, swap); }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    bool CrossPoints(std::vector<CPData> &ret, bool me_closed, const Block *bb_me,
                     iter first, iter last, bool o_closed = true, const Block *bb_o = nullptr,
                     CPTasks to_do = {}, bool swap = false) const
    { return swap ? contour::CrossPoints(ret, first, last, o_closed, bb_o, begin(), end(), me_closed, bb_me, to_do)
                  : contour::CrossPoints(ret, begin(), end(), me_closed, bb_me, first, last, o_closed, bb_o, to_do);  }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    std::vector<CPData> CrossPoints(bool me_closed, const Block *bb_me,
                                    iter first, iter last, bool o_closed = true, const Block *bb_o = nullptr,
                                    CPTasks to_do = {}, bool swap = false) const
    { std::vector<CPData> ret; CrossPoints(ret, me_closed, bb_me, first, last, o_closed, bb_o, to_do, swap); return ret; }

    /** Moves a position along the path by 'len'.
    * If the path is unconnected, no problem, we only consider
    * distances on actual edges. We assume the path non-closed, that is, we do not wrap around.
    * If 'ignore_invisible' is true, we jump across invisible edges.
    * For bezier edges this is an expensive operation.
     * If we walk beyond the endpoint, we make pos equal to the end or start
     * and return a nonzero len - the remaining distance to cover. Unless 'warp'
     * is set in which case we walk from the end/start the remaining length. In
     * this case we always return len=0, except if 'pos' is not valid, when we do nothing.*/
    PathPos& MovePos(double& len, PathPos& pos, bool ignore_invisible = false, bool warp = false) const
    { len = MovePos(pos, len, ignore_invisible, warp); return pos; }
    /** Moves a position along the path by 'len'.
    * If the path is unconnected, no problem, we only consider
    * distances on actual edges. We assume the path non-closed, that is, we do not wrap around.
    * If 'ignore_invisible' is true, we jump across invisible edges.
    * For bezier edges this is an expensive operation.
     * If we walk beyond the endpoint, we make pos equal to the end or start
     * and return a nonzero len - the remaining distance to cover. Unless 'warp'
     * is set in which case we walk from the end/start the remaining length. In
     * this case we always return len=0, except if 'pos' is not valid, when we do nothing.*/
    PathPos MovePos(double& len, const PathPos& pos, bool ignore_invisible = false, bool warp = false) const
    { PathPos ret = pos; len = MovePos(ret, len, ignore_invisible, warp); return ret; }
    /** Moves a position along the path by 'len'.
     * If the path is unconnected, no problem, we only consider
     * distances on actual edges. We assume the path non-closed, that is, we do not wrap around.
     * If 'ignore_invisible' is true, we jump across invisible edges.
     * For bezier edges this is an expensive operation.
     * If we walk beyond the endpoint, we make pos equal to the end or start
     * and return a nonzero len - the remaining distance to cover. Unless 'warp'
     * is set in which case we walk from the end/start the remaining length. In
     * this case we always return 0, except if 'pos' is not valid, when we return 'len'.*/
    double MovePos(PathPos& pos, double len, bool ignore_invisible = false, bool warp = false) const;
    /** Cut away the end of the path (after the given position). Returns false is params are bad (no change then).*/
    bool SetEnd(const PathPos &p) { if (!IsValidPos(p)) return false; resize(p.edge+1); back().SetEnd(p.pos); return true; }
    /** Cut away the beginning of the path (before the given position). Returns false is params are bad (no change then).*/
    bool SetStart(const PathPos &p) { if (!IsValidPos(p)) return false; erase(begin(), begin()+p.edge); front().SetStart(p.pos); return true; }
    EdgeVector<Edgeish> CopyPart(const PathPos &p1, const PathPos &p2) const;
    EdgeVector<Edgeish> SplitAt(const PathPos &p, bool keep_front = true);
    double TruncateStart(double length, bool ignore_invis=false);
    double TruncateEnd(double length, bool ignore_invis=false);
    std::vector<std::span<const Edgeish>> Split(bool remove_hidden = false) const
    { return contour::Split(static_cast<const std::vector<Edgeish>&>(*this).cbegin(), static_cast<const std::vector<Edgeish>&>(*this).cend(), remove_hidden); }
    std::vector<std::span<Edgeish>> Split(bool remove_hidden = false)
    { return contour::Split(begin(), end(), remove_hidden); }
    EdgeVector<Edgeish> &LinearExtend(double length, bool forward, bool visible = true);
    bool LinearExtend(PathPos &pos, double length, bool forward, bool visible = true);
    iterator AppendEllipse(const XY &c, double radius_x, double radius_y=0,
                           double tilt_deg=0, double s_deg=0, double d_deg=360,
                           bool clockwise=true, bool ensure_connect=false, bool add_closing_line=false)
    { return contour::AppendEllipse(*this, c, radius_x, radius_y, tilt_deg, s_deg, d_deg, clockwise, ensure_connect, add_closing_line); }

    /** Makes 'i' the first edge by appending anything before it to the end.
     * Closed paths remain closed, with their clockwiseness intact.*/
    EdgeVector<Edgeish> &Cycle(iterator i) { if (i!=end()) std::rotate(begin(), i, end()); return *this; }
    /** Makes the uth edge the first edge by appending anything before it to the end.
     * Closed paths remain closed, with their clockwiseness intact.*/
    EdgeVector<Edgeish>& Cycle(unsigned u) { if (u < size()) return Cycle(begin() + u); else return *this; }
    /** Makes the vertex at 'start' be the start of the first edge, by appending anything before it to the end.
     * If 'start' is not a the start of any edge, it selects the edge start closest to it.
     * If there are multiple vertices at 'start' it selects the first one.
     * Closed paths remain closed, with their clockwiseness intact.*/
    EdgeVector<Edgeish> &Cycle(const XY &start);
    /** Find for an egde, which matches 'good' and follows an edge not matching it.
     * If all or none of the edges match, we make no change. */
    EdgeVector<Edgeish> &Cycle(Edge::Update good);
    /** Find he first edge matching (starting from an edge).
     * @returns end() if none do. */
    iterator Find(Edge::Update u, std::optional<iterator> from={}) noexcept { return std::find_if(from.value_or(begin()), end(), [u](const Edgeish &e) noexcept { return e.Match(u); }); }
    /** Find he first edge matching (starting from an edge).
     * @returns end() if none do. */
    const_iterator Find(Edge::Update u, std::optional<iterator> from={}) const noexcept { return std::find_if(from.value_or(begin()), end(), [u](const Edgeish &e) noexcept { return e.Match(u); }); }
    /** Find he first edge NOT matching (starting from an edge).
     * @returns end() if none do. */
    iterator FindNot(Edge::Update u, std::optional<iterator> from={}) noexcept { return std::find_if(from.value_or(begin()), end(), [u](const Edgeish &e) noexcept { return !e.Match(u); }); }
    /** Find he first edge NOT matching (starting from an edge).
     * @returns end() if none do. */
    const_iterator FindNot(Edge::Update u, std::optional<iterator> from={}) const noexcept { return std::find_if(from.value_or(begin()), end(), [u](const Edgeish &e) noexcept { return !e.Match(u); }); }


    EdgeVector<Edgeish> CreateSimpleExpand(double gap, bool circular = true, EExpandType type = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY) const
    { return contour::CreateSimpleExpand<const_iterator, EdgeVector<Edgeish>>(begin(), end(), gap, circular, type, miter_limit); }
    EdgeVector<Edgeish> &SimpleExpand(double gap, bool circular = true, EExpandType type = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY)
    { contour::CreateSimpleExpand(begin(), end(), gap, circular, type, miter_limit).swap(*this); return *this; }

    void CairoPath(cairo_t *cr, bool show_hidden, bool circular = true) const;
    void CairoPathDashed(cairo_t *cr, std::span<const double>, bool show_hidden, bool circular = true) const;

    void Distance(const EdgeVector<Edgeish> &o, DistanceType &ret) const noexcept;
    DistanceType Distance(const EdgeVector<Edgeish> &o) const noexcept;
    DistanceType Distance(const XY &o) const noexcept;

    /** Calculates the crosspoints with a contour. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, bool me_closed, const SimpleContour &o,
                     CPTasks to_do = {}, bool swap = false) const;
    /** Calculates the crosspoints with a contour. See @ref CrossPoints for details.*/
    std::vector<CPData> CrossPoints(bool me_closed, const Block *bb_o, const Contour &o,
                                    CPTasks to_do = {}, bool swap = false) const;
    /** Creates a contour that is covered by the path, when drawn as a continuous line of 'width'. */
    Contour SimpleWiden(double width, EExpandType line_cap = EXPAND_MITER, EExpandType line_join = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY) const;
    Contour SimpleWidenAsymmetric(double off1, double off2, EExpandType line_cap = EXPAND_MITER, EExpandType line_join = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY) const;
    EdgeVector<Edge> DoubleWiden(double offset, EExpandType line_cap=EXPAND_MITER, EExpandType line_join = EXPAND_MITER_ROUND, double miter_limit = CONTOUR_INFINITY) const;
    Contour LinearWiden(const PathPos &pos, double gap1, double gap2, double length, bool trim = false) const;
    Contour LinearWiden(const PathPos &pos, unsigned num, const double *gap, const double *length, bool trim = false) const;
    Contour LinearWidenAsymetric(const PathPos &pos, double gap1_left, double gap1_right,
                                 double gap2_left, double gap2_right, double length,
                                 Edge::Update update_left = Edge::Update(), Edge::Update update_right = Edge::Update(),
                                 bool trim = false) const;
    Contour LinearWidenAsymetric(const PathPos &pos,
                                 unsigned num, const double *gap_left, const double *gap_right, const double *length,
                                 Edge::Update update_left = Edge::Update(), Edge::Update update_right = Edge::Update(),
                                 bool trim = false) const;
    void Apply(Edge::Update u) { if (u.DoesSomething()) for (Edgeish &e : *this) e.Apply(u);}
    void Clip(const Contour& by, Edge::Update outside = Edge::Update::clear_visible(), Edge::Update inside = {}, Edge::Update on_edge = {}, const Block* bb_me = nullptr);
    std::vector<Edgeish> CreateClipped(const Contour& by, Edge::Update outside = Edge::Update::clear_visible(), Edge::Update inside = {}, Edge::Update on_edge = {}, const Block* bb_me = nullptr) const
    { std::vector<Edgeish> ret = *this; ret.Clip(by, outside, inside, on_edge, bb_me); return ret; }

    void ClipRemove(const Contour& by, bool invert = false, bool remove_on_edge = true, const Block* bb_me = nullptr, Edge::Update use = Edge::Update::set_internal_mark());
    std::vector<Edgeish> CreateClipRemoved(const Contour& by, bool invert = false, bool remove_on_edge = true, const Block* bb_me = nullptr, Edge::Update use = Edge::Update::set_internal_mark()) const
    { std::vector<Edgeish> ret; ret.ClipRemove(by, invert, remove_on_edge, bb_me, use); return ret; }

    /** Creates a comma separated list of dumped version of the edges.
     * Calls Edgeish->Dump().
     * @param [in] precise When set, we emit the hex representation of doubles, else a human readable one.
     * @param [in] type The name of our type. Defaults to "Path"
     * @param [in] pre String to prepend to each dumped guy
     * @param [in] post String to append to each dumped guy (after a comma)
     * @returns the full string.*/
    std::string Dump(bool precise, const std::string &type="Path", const std::string &pre = {}, const std::string &post = "\n") const
        { return type+"{"+post+contour::Dump(precise, begin(), end(), pre, post)+"}"; }
};

using Path = EdgeVector<Edge>;

template <typename Edgeish>
EdgeVector<Edgeish>  &EdgeVector<Edgeish>::append(std::span<const XY> v, bool ensure_connect)
{
    if (v.size()) {
        if (ensure_connect && size() && XY(v[0])!=back().GetEnd()) {
            reserve(size()+v.size());
            const XY tmp = back().GetEnd();
            emplace_back(tmp, XY(v[0]));
        } else
            reserve(size()+v.size()-1);
        for (auto i = v.begin(), j = std::next(i); j!=v.end(); i++, j++)
            emplace_back(*i, *j);
    }
    return *this;
}

template <typename Edgeish>
EdgeVector<Edgeish> &EdgeVector<Edgeish>::append(std::initializer_list<const XY> o, bool ensure_connect)
{
    if (o.size()) {
        if (ensure_connect && size() && *o.begin()!=back().GetEnd()) {
            reserve(size()+o.size());
            const XY tmp = back().GetEnd();
            emplace_back(tmp, *o.begin());
        } else
            reserve(size()+o.size()-1);
        for (auto i = o.begin(), j = std::next(i); j!=o.end(); i++, j++)
            emplace_back(*i, *j);
    }
    return *this;
}


template <typename Edgeish>
template<typename XYish_iter>
EdgeVector<Edgeish> &EdgeVector<Edgeish>::appendpoints(XYish_iter first, XYish_iter last, bool ensure_connect)
{
    if (first!=last) {
        if (ensure_connect && size() && XY(*first)!=back().GetEnd()) {
            const XY tmp = back().GetEnd();
            emplace_back(tmp, XY(*first));
        }
        for (auto next = std::next(first); next!=last; ++first, ++next)
            emplace_back(XY(*first), XY(*next));
    }
    return *this;
}


template <typename Edgeish>
bool EdgeVector<Edgeish>::RemoveDots(double tolerance, bool circular)
{
    bool ret = false;
    for (unsigned u = 0; u<size(); /*nope*/)
        if (at(u).IsDot(tolerance)) {
            //connect if it was connected before deleting
            if ((circular || (u!=0 && u!=size()-1))
                    && at_prev(u).GetEnd() == at(u).GetStart() && at(u).GetEnd() == at_next(u).GetStart())
                at_next(u).SetStartOnly(at_prev(u).GetEnd());
            erase(begin()+u);
            ret = true;
        } else
                u++;
    return ret;
}

template <typename Edgeish>
bool EdgeVector<Edgeish>::Simplify(double tolerance, bool circular)
{
    bool ret = false;
    bool had_dots;
    do {
        had_dots = false;
        ret |= RemoveDots(tolerance, circular);
        if (size()<2) return ret;
        for (size_t i = 1; i<size(); /*nope*/)
            if (at(i-1).CheckAndCombine(at(i), tolerance)) {
                had_dots |= at(i-1).IsDot();
                erase(begin()+i);
                ret = true;
            } else
                i++;
        if (size()>=2 && circular
            && back().CheckAndCombine(front(), tolerance)) {
            had_dots |= back().IsDot();
            erase(begin());
            ret = true;
        }
    } while(had_dots);
    return ret;
}

template <typename Edgeish>
double EdgeVector<Edgeish>::MovePos(PathPos & pos, double len, bool ignore_invisible, bool warp) const
{
    if (!IsValidPos(pos)) return len;
    const size_t last_edge = len>0 ? size()-1 : 0;
    const double last_pos = len>0 ? 1 : 0;
    while (len) {
        //if we are an edge to process..
        if (pos.pos != last_pos && (!ignore_invisible || at(pos.edge).IsVisible()))
            //..and the endgame is within us...
            if (at(pos.edge).MovePos(pos.pos, len))
                //..then done
                break;
        //..else move to next edge, if not at total end
        if (pos.edge == last_edge) {
            pos.pos = last_pos;
            if (!warp) break;
            pos = len>0 ? PathPos(0, 0) : PathPos(size()-1, 1);
        } else if (len>0) {
            pos.edge++;
            pos.pos = 0;
        } else {
            pos.edge--;
            pos.pos = 1;
        }
    }
    return len;
}

/** Copies a part of the path (between the given positions).
 * Positions may come in any order, but if p1 > p2, then
 * the result will be in opposite direction (inverted)
 * Returns empty if parameters are bad.*/
template <typename Edgeish>
EdgeVector<Edgeish> EdgeVector<Edgeish>::CopyPart(const PathPos & p1, const PathPos & p2) const
{
    EdgeVector<Edgeish> ret;
    if (IsValidPos(p1) && IsValidPos(p2)) {
        if (p1<p2) {
            ret.reserve(p2.edge-p1.edge+1);
            ret.push_back(at(p1.edge));
            if (p1.edge==p2.edge) {
                ret.back().SetStartEndIgn(p1.pos, p2.pos);
                if (ret.back().IsDot()) ret.pop_back();
            } else {
                ret.back().SetStart(p1.pos);
                if (ret.back().IsDot()) ret.pop_back();
                for (size_t u = p1.edge+1; u<=p2.edge; u++)
                    ret.push_back(at(u));
                ret.back().SetEnd(p2.pos);
                if (ret.back().IsDot()) ret.pop_back();
            }
        } else {
            ret.reserve(p1.edge-p2.edge+1);
            ret.push_back(at(p1.edge));
            ret.back().Invert();
            if (p1.edge==p2.edge) {
                ret.back().SetStartEndIgn(1-p1.pos, 1-p2.pos);
                if (ret.back().IsDot()) ret.pop_back();
            } else {
                ret.back().SetStart(1-p1.pos);
                if (ret.back().IsDot()) ret.pop_back();
                for (size_t u = p1.edge; u>p2.edge; u--) {
                    ret.push_back(at(u-1));
                    ret.back().Invert();
                }
                ret.back().SetEnd(1-p2.pos);
                if (ret.back().IsDot()) ret.pop_back();
            }
        }
    }
    return ret;
}

/** Split the path at the given position.
 * If 'keep_front' is true, we keep the part *before* the position in
 * the object itself and return the part after. Else vice versa.*/
template <typename Edgeish>
EdgeVector<Edgeish> EdgeVector<Edgeish>::SplitAt(const PathPos & p, bool keep_front)
{
    EdgeVector<Edgeish> ret(begin()+p.edge, end());
    ret.front().SetStart(p.pos);
    SetEnd(p);
    if (!keep_front) swap(ret);
    return ret;
}

/** Cuts a section of length 'length' from the start of the path.
 * If ignore_invis is true, we ignore invisible parts, when identifying
 * the part to cut (so we will cut more). If the path is shorter than
 * 'length' we return 'length' reduced by the path length and an empty path.
 * Else return zero and the truncated path.*/
template <typename Edgeish>
double EdgeVector<Edgeish>::TruncateStart(double length, bool ignore_invis)
{
    const PathPos pos = MovePos(length, GetStartPos(), ignore_invis);
    if (length) clear();
    else SetStart(pos);
    return length;
}

/** Cuts a section of length 'length' (positive value expected) from the end of the path.
* If ignore_invis is true, we ignore invisible parts, when identifying
* the part to cut (so we will cut more). If the path is shorter than
* 'length' we return 'length' reduced by the path length and an empty path.
* Else return zero and the truncated path.*/
template <typename Edgeish>
double EdgeVector<Edgeish>::TruncateEnd(double length, bool ignore_invis)
{
    length *= -1;
    const PathPos pos = MovePos(length, GetEndPos(), ignore_invis);
    if (length) clear();
    else SetEnd(pos);
    return -length;
}


/** Extend the path with a linear segment of 'length' length.
 * Note that if the path starts/end with a straight edge, we
 * simple make it longer - so number of edges don't necessarily grow.
 * For the empty path, we do nothing, but that is considered an error.
 * @param [in] length The length of the extension. Should be positive.
 * @param [in] forward If true we extend at the end, else in the start.
 * @param [in] visible The visibility of the extension. */
template <typename Edgeish>
EdgeVector<Edgeish> &EdgeVector<Edgeish>::LinearExtend(double length, bool forward, bool visible)
{
    _ASSERT(length>0);
    _ASSERT(size());
    if (length<=0 || empty()) return *this;
    const auto res = (forward ? back() : front()).LinearExtend(length, forward, visible);
    if (res.first) {
        if (forward)
            push_back(res.second);
        else
            insert(begin(), res.second);
    }
    return *this;
}

/** Checks if the path has 'length' length from pos to its start/end. If not
 * the path is linearly extended to be that long.
 * For the empty path, we do nothing, but that is considered an error.
 * @param pos The position from which we measure the length.
 *            At return this variable contains the same position as at input,
 *            but in the new path.
 *            Note that since the path may have changed, the original 'pos'
 *            value may become invalid, so we return it updated.
 * @param [in] length The length of the extension. Can be negative - in that
 *                    case we move in the opposite direction signalled by
 *                    'forward'.
 * @param [in] forward If true we extend at the end, else in the start.
 *                     If 'length' is negative - the exact opposite.
 * @param [in] visible The visibility of the extension.
 * @returns true if we have added something to the path.*/
template <typename Edgeish>
bool EdgeVector<Edgeish>::LinearExtend(PathPos & pos, double length, bool forward, bool visible)
{
    _ASSERT(size());
    _ASSERT(IsValidPos(pos));
    if (length==0 || size()==0 || !IsValidPos(pos)) return false;
    if (!forward) {
        length *= -1;
        forward = true;
    }
    MovePos(length, pos); //pos remains unchanged, length is the remaining length to add (signed)
    if (length==0) return false;
    if (length<0) forward = !forward;
    length = fabs(length);
    Edge &edge = forward ? back() : front();
    const double lin_len = edge.IsStraight() ? GetLength() : 0; //store the length if linear for later. If bezier, we will never need it.
    const auto res = edge.LinearExtend(length, forward, visible);
    if (forward) {
        if (res.first)
            push_back(res.second); //no need to update pos, we insert to the end
        else if (pos.edge==size()-1) //if the original pos is on the last edge and we have enlengthtened it, recalc.
            pos.pos *= lin_len/back().GetLength();
    } else {
        if (res.first)
            insert(begin(), res.second);
        else if (pos.edge==0) //if the original pos is on the first edge and we have enlengthtened it, recalc.
            pos.pos = 1 - (1-pos.pos)*lin_len/front().GetLength();
    }
    return true;
}

template<typename Edgeish>
inline EdgeVector<Edgeish>& EdgeVector<Edgeish>::Cycle(const XY & start)
{
    if (size()==0) return *this;
    double d = front().GetStart().Distance(start);
    unsigned e = 0;
    for (unsigned u = 1; u<size() && d; u++)
        if (d < at(u).GetStart().Distance(start))
            d = at(e = u).GetStart().Distance(start);
    return Cycle(e);
}

template<typename Edgeish>
inline EdgeVector<Edgeish> &EdgeVector<Edgeish>::Cycle(Edge::Update good) {
    auto i = FindNot(good);
    if (i==end()) return *this; //all of them matches 'good'
    if (std::next(i)==end()) return *this; //only the last (maybe only) edge does not match
    i = Find(good, std::next(i));
    if (i==end()) return *this;   //Edges all the way up to the end don't match - either none of them do of all the matches are at the front
    return Cycle(i);
}

/** Draw the path as a path to a cairo context.
 *
 * @param [in] cr The cairo context.
 * @param [in] show_hidden If false, we skip edges marked not visible.
 * @param [in] circular If true, we draw connected start and end as a single joined line.
 */
template <typename Edgeish>
void EdgeVector<Edgeish>::CairoPath(cairo_t *cr, bool show_hidden, bool circular) const
{
    if (size()==0 || cr==nullptr) return;
    size_t start = 0;
    if (circular) {
        //we may have the case where we have invisible or non-connecting
        //edges, but the last and first are both visible and connected.
        //We need to ensure they are actually added to cairo as part of
        //the same subpath.
        //First test if we have an invisible edge - start just after
        for (start=size(); start>0; start--)
            if ((!at_prev(start-1).IsVisible() && !show_hidden) ||
                at_prev(start-1).GetEnd() != at(start-1).GetStart())
                break;
        if (start) //if we found an edge, decrement to have the index of that
            start--;
    }
    XY started_at(0, 0); //gcc complains of not initializing this
    bool drawing = false;
    for (size_t u = start, count = 0; count<size(); u = next(u), count++) {
        if (show_hidden || at(u).IsVisible()) {
            if (!drawing || at_prev(u).GetEnd()!=at(u).GetStart()) {
                cairo_new_sub_path(cr);
                started_at = at(u).GetStart();
                cairo_move_to(cr, started_at.x, started_at.y);
                drawing = true;
            }
            at(u).PathTo(cr);
            if (at(u).GetEnd()==started_at)
                cairo_close_path(cr);
        } else {
            drawing = false;
        }
    }
    if (circular && drawing && back().GetEnd()==started_at)
        cairo_close_path(cr);
}

/** Draw the path in dashed lines to the path of a cairo context. Needed for backends not supporting dashed lines.
 *
 * We also assume cairo dash is set to continuous: we fake dash here.
 * @param [in] cr The cairo context.
 * @param [in] pattern Contains lengths of alternating on/off segments.
 * @param [in] show_hidden If false, we skip edges marked not visible.
 * @param [in] circular If true, we draw connected start and end as a single joined line.
 */
template <typename Edgeish>
void EdgeVector<Edgeish>::CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool circular [[maybe_unused]]) const
{
    if (size()==0 || cr==nullptr) return;
    if (pattern.size()<2) {
        CairoPath(cr, show_hidden);
        return;
    }
    double offset = 0;
    int pos = 0;
    for (size_t i = 0; i<size(); i++)
        if (at(i).IsVisible())
            at(i).PathDashed(cr, pattern, pos, offset);
}


/** 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 always nonnegative, zero if partial overlap only (two points equal)
 *            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.
 */
template <typename Edgeish>
void EdgeVector<Edgeish>::Distance(const EdgeVector<Edgeish> &o, DistanceType &ret) const noexcept
{
    return contour::Distance(ret, begin(), end(), o.begin(), o.end());
}

/** Calculates the distance between two shapes by finding their two closest points.
 *
 * @param [in] o The other shape to take the distance from.
 * @returns We return the distance of the two closest points and the two points themselves.
 *          Distance is always nonnegative, zero if partial overlap only (two points equal)
 *          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.
*/
template <typename Edgeish>
DistanceType EdgeVector<Edgeish>::Distance(const EdgeVector<Edgeish> &o) const noexcept
{
    DistanceType ret;
    ret.MakeAllOutside();
    contour::Distance(ret, begin(), end(), o.begin(), o.end());
    return ret;
}

/** Calculates the distance between a point and us by finding our closest point.
 * @param [in] o The point to take the distance from.
 * @return The distance, `CONTOUR_INFINITY` if we are empty. */
template <typename Edgeish>
DistanceType EdgeVector<Edgeish>::Distance(const XY &o) const noexcept
{
    DistanceType ret;
    ret.MakeAllOutside();
    contour::Distance(ret, begin(), end(), o);
    return ret;
}


/** Create a version of the path where sharp vertices are replaced with
 * arcs/bevels of a certain radius. Only vertices between two straight edges
 * are considered now. The original edges remain unchanged.
 * @param first From this edge
 * @param last to this edge.
 * @param radius The width of the circle/bevel to apply. If the edges are
 *               shorter than required, we decrease the radius appropriately.
 * @param bevel If true we add a bevel and not a round join.
 * @param circular If true, we also add a join if start and endpoints are
 *                 the same.
 * @param min_radius If the edges are short and the radius goes below 'min_radius'
 *                   we skip the vertex and have it sharp.
 * @returns The rounded path.*/
template <typename iter>
Path CreateRounded(iter first, iter last,
                   double radius, bool bevel, bool circular, double min_radius)
{

    struct VertexResult
    {
        iter edge;               //the edge after the vertex we found
        bool wrap = false;       //true if we have wrapped beyond size()-1 back to front(). If !circular, this means we found no breaking vertices and path is unset
        bool disconnect = false; //true if we have found a non-connected edge pair (prev(edge)->edge). path is unset
        bool clockwise;          //true if the edge pair (prev(edge)->edge) turns clockwise
        Path path;               //the segment. If set this, comes before 'edge'
        double length;           //length of the segment
        bool single_straight;    //true if this is a segment of a single straight edge

        VertexResult(iter e, iter first, iter last, bool circular) :
            edge(e)
        {
            //Walk ahead till we find edges that don't smoothly connect
            contour::ETriangleDirType dir = contour::ALL_EQUAL;
            const iter stop = circular ? first : edge;
            do {
                auto next = std::next(edge);
                if (next == last) {
                    next = first;
                    wrap = true;
                }
                path.push_back(*edge);
                if (edge->GetEnd() != next->GetStart() || (next==first && !circular)) {
                    edge = next;
                    disconnect = true;
                    goto finish;
                }
                const XY bw_tangent1 = edge->PrevTangentPoint(1);
                const XY fw_tangent2 = next->NextTangentPoint(0);
                dir = contour::triangle_dir(bw_tangent1, next->GetStart(), fw_tangent2);
                edge = next;
            } while (edge != stop && dir!=contour::COUNTERCLOCKWISE && dir!=contour::CLOCKWISE);
            disconnect = false;
        finish:
            clockwise = dir==contour::CLOCKWISE;
            length = path.GetLength();
            single_straight = path.size()==1 && path.front().IsStraight();
        }
    };

    Path ret;
    if (first==last) return ret;

    iter start_edge = first;
    //if circular, find the first breaking point
    if (circular && first->GetStart()==std::prev(last)->GetEnd()) {
        VertexResult initial(first, first, last, circular);
        if (initial.wrap) //no breaking or disconnected edges
            return initial.path;
        start_edge = initial.edge;
    }
    std::vector<VertexResult> sections;
    iter e = start_edge;
    do {
        e = sections.emplace_back(e, first, last, circular).edge;
    } while (e!=start_edge);

    for (unsigned u = 0; u<sections.size(); u++) {
        if ((sections[u].wrap && !circular) || sections[u].disconnect)
            continue;
        unsigned v = sections.size()==1 ? u : (u+1)%sections.size(); //next section - may be us if only one (and connected)
        double r = sections[u].clockwise ? -radius : radius;
        XY c, start, end; //the centerpoint of the radius and its two ends.
        if (sections[u].single_straight && sections[v].single_straight) {
            //fast path: two single straight edges
            typename iter::value_type &i = sections[u].path.back();
            typename iter::value_type &j = sections[v].path.front();
            XY d1 = (i.GetEnd() - i.GetStart()).Normalize()*r;
            XY d2 = (j.GetEnd() - j.GetStart()).Normalize()*r;
            d1 = d1.Rotate90CCW();
            d2 = d2.Rotate90CCW();
            auto res = contour::crossing_line_line(i.GetStart()+d1, i.GetEnd()+d1,
                                                   j.GetStart()+d2, j.GetEnd()+d2, c);
            if (res==contour::ELineCrossingType::PARALLEL)
                continue;
            //now we know 'c' is valid
            const double chop_base1 = i.GetEnd().Distance(c-d1);
            const double chop_base2 = i.GetEnd().Distance(c-d2);

            const double chop_ratio = std::min({1., sections[u].length/2/chop_base1, sections[v].length/2/chop_base2});
            c = (c - i.GetEnd())*chop_ratio + i.GetEnd();
            r *= chop_ratio;

            if (fabs(r)<min_radius)
                continue;

            start = c.ProjectOntoLine(i.GetStart(), i.GetEnd());
            end = c.ProjectOntoLine(j.GetStart(), j.GetEnd());
            //OK, we have all of start, end and c.
            //Trim the edges
            i.SetEndOnly(start);
            j.SetStartOnly(end);
        } else {
            for (; fabs(r)>std::max(min_radius, 1.); r /= 2) {
                const Path exp1 = sections[u].path.CreateSimpleExpand(r, false, EXPAND_MITER);
                const Path exp2 = sections[v].path.CreateSimpleExpand(r, false, EXPAND_MITER);
                auto cp = exp1.CrossPoints(false, nullptr, exp2, false, nullptr);
                if (cp.empty())
                    continue;
                std::sort(cp.begin(), cp.end(), [](auto &a, auto &b) {return a.me<b.me; });
                c = cp.front().xy;
                XY dir1 = exp1.GetTangent(cp.front().me, sections[u].clockwise, false) - c;
                XY dir2 = exp2.GetTangent(cp.front().other, sections[u].clockwise, false) - c;
                //rotate ccw
                dir1 = dir1.Rotate90CCW().Normalize()*fabs(r);
                dir2 = dir2.Rotate90CCW().Normalize()*fabs(r);
                //now find the original point on v1.path and v2.path
                PathPos p1 = sections[u].path.Distance(c + dir1).pos_on_me;
                PathPos p2 = sections[v].path.Distance(c + dir2).pos_on_me;
                //determine if they are not further than the half of those paths
                if (test_smaller(sections[u].length/2, sections[u].path.CopyPart(p1, sections[u].path.GetEndPos()).GetLength()) ||
                    test_smaller(sections[v].length/2, sections[v].path.CopyPart(sections[v].path.GetStartPos(), p2).GetLength()))
                    continue; //if so, halven radius& repeat
                              //if not, here we have all three points to be able to chop away and add the round join.
                sections[u].path.SetEnd(p1);
                sections[v].path.SetStart(p2);
                start = sections[u].path.back().GetEnd();
                end = sections[v].path.front().GetStart();
                sections[u].path.Simplify(); //remove dots
                sections[v].path.Simplify();
                goto do_join;
            }
            //r has fallen below min_radius or 1.
            //We were not successful in adding a join.
            //Leave it as is and go to the next section.
            continue;
        }
    do_join:
        //now we have start, end and c. Add the join.
        double s_deg = acos(std::max(-1., std::min(1., (start.x-c.x)/fabs(r))))*180/M_PI;
        if (start.y<c.y) s_deg = 360-s_deg;
        double d_deg = acos(std::max(-1., std::min(1., (end.x-c.x)/fabs(r))))*180/M_PI;
        if (end.y<c.y) d_deg = 360-d_deg;
        if (bevel) {
            sections[u].path.emplace_back(start, end);
        } else {
            auto first_inserted =
                contour::AppendEllipse<Path>(sections[u].path, c, fabs(r), 0, 0, s_deg, d_deg,
                                             sections[u].clockwise, false, false);
            if (first_inserted!=sections[u].path.end()) {
                _ASSERT(first_inserted->GetStart().DistanceSqr(start)<1);
                first_inserted->SetStartOnly(start);
            }
            _ASSERT(sections[u].path.back().GetEnd().DistanceSqr(end)<1);
            sections[u].path.back().SetEndOnly(end);
        }
    }
    //assemble the path
    for (auto &s:sections)
        ret.append(std::move(s.path));
    return ret;
}



}//namespace
#endif //CONTOUR_PATH_H

