/*
    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.h Declares class ContourList, HoledSimpleContour and Contour.
 * @ingroup contour_files
 */

#if !defined(CONTOUR_CONTOURS_H)
#define CONTOUR_CONTOURS_H

#include <list>
#include <iostream>
#include "contour_simple.h"

#ifndef NDEBUG
#define _ASSERT_PRINT(cond, c1, c2, txt) do { if (!::contour::suppress_assert_print && !(cond)) std::cerr<<std::string(__FILE__)+": "+(txt)+"\nContour A = "+(c1).Dump(true)+";\nContour B = "+(c2).Dump(true)<<";"<<std::endl; } while(false)
//Call this in any scope to disable printouts from _ASSERT_PRINT macros in the remainder of that scope.
#define _SUPPRESS_ASSERT_PRINT ::contour::suppress_assert_print = true; auto contour_suppress_assert_print__ = std::unique_ptr<void, void(*)(void*)>((void*)1, [](void*){ ::contour::suppress_assert_print = false;})
#else
#define _ASSERT_PRINT(cond, c1, c2, txt) do {} while(false)
#define _SUPPRESS_ASSERT_PRINT do {} while(false)
#endif



namespace contour {

#ifndef NDEBUG
extern thread_local bool suppress_assert_print;
#endif

class HoledSimpleContour;

/** A list of non-overlapping ContourWithHole objects.
 * @ingroup contour_internal
 *
 * A building block toward the overall Contour class.
 * Essentially functions working on a list of HoledSimpleContour objects.
 * Should only contain contours with the same clockwiseness. It can be clockwise,
 * in which case we are a collection of shapes (possibly with holes inside them),
 * or it can be counterclockwise, in which case we are a list of holes inside a
 * shape (with possibly each hole containing some positive shapes in them).
 *
 * When we say "non-overlapping" we mean that no two shapes have intersections
 * and they do not even touch by edge. However, it is possible that they touch
 * by vertex. That is, a vertex of one shape may lie on a vertex or edge of
 * another. For speed it maintains an overall bounding box of its content.*/
class ContourList : protected std::vector<HoledSimpleContour>
{
    template <typename Edgeish> friend class EdgeVector;
    template <typename Edgeish> friend class EdgeList;
    friend class HoledSimpleContour;
    friend class Contour;
    friend class ContoursHelper;
    template <bool is_constant> friend class contour_iterator_base;

public:
    using std::vector<HoledSimpleContour>::size;
    using std::vector<HoledSimpleContour>::at;
    using std::vector<HoledSimpleContour>::cbegin;
    using std::vector<HoledSimpleContour>::cend;

    ContourList() noexcept { boundingBox.MakeInvalid();}

    const_iterator begin() const noexcept { return std::vector<HoledSimpleContour>::begin(); }
    const_iterator end() const noexcept { return std::vector<HoledSimpleContour>::end(); }

    ContourList CreateNonInvertingTransformed(const TRMatrix & M) const;
    ContourList CreateInvertingTransformed(const TRMatrix & M) const;

    EContourRelationType RelationTo(const HoledSimpleContour &c, bool ignore_holes) const;
    EContourRelationType RelationTo(const ContourList &c, bool ignore_holes) const;
    void Distance(const HoledSimpleContour &c, DistanceType &dist_so_far) const noexcept;
    void Distance(const ContourList &cl, DistanceType &dist_so_far) const noexcept;

    bool FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert, std::list<const SimpleContour*> &list) const;

    void swap(ContourList &a) noexcept {std::vector<HoledSimpleContour>::swap(a); std::swap(boundingBox, a.boundingBox);}  ///<Swap content with another ContourList object
    void clear() noexcept {std::vector<HoledSimpleContour>::clear(); boundingBox.MakeInvalid();} ///<Clear content, making the list empty
    bool operator < (const ContourList &p) const noexcept;
    bool operator ==(const ContourList &p) const noexcept;
    const HoledSimpleContour &operator[](size_type i) const noexcept { return std::vector<HoledSimpleContour>::operator[](i); } ///<Returns the i_th element in the list. UB if i is beyond our size.
    operator Path() const { Path p; AppendToPath(p); return p; } ///<Converts the list to a single Path object.
    void AppendToPath(Path &p) const; ///<Appends the list of contours to a Path object.
    Path CreatePathFromSelected(bool keep(const Edge&)) const;
    void SetVisible(bool visible) const noexcept;
    void SetInternalMark(bool imark) const noexcept;
    void SetMark(unsigned mark) const noexcept;
    void Apply(Edge::Update u) const noexcept;
    void Remove(unsigned u);
    bool Simplify(double tolerance = SMALL_NUM);

    bool IsEmpty() const noexcept { return size()==0; } ///<True if the list is empty
    const Block &GetBoundingBox(void) const noexcept {return boundingBox;} ///<Return the bounding box of the list. Invalid if the list is empty.
    bool GetClockWise() const noexcept; ///<Returns true if the contours are positive in this list.
    double GetArea(bool consider_holes=true) const noexcept; ///<Returns the combined area of the contours. Negative if they are counterclockwise.
    double GetCircumference(bool consider_holes=true, bool include_hidden=false) const noexcept; ///<Returns the combined length of the circumference of the contours in the list.
    XY CentroidUpscaled() const noexcept; ///<Returns the combined centroid of the shapes, scaled by their (potentially negative) area.

    EPointRelationType IsWithin(const XY &p) const; ///<Returns the relation of a poiont to the list of contours.
    EPointRelationType Tangents(const XY &p, XY &t1, XY &t2) const;
    void VerticalCrossSection(double x, DoubleMap<bool> &section) const;
    double OffsetBelow(const SimpleContour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;
    double OffsetBelow(const Contour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;
    double OffsetBelow(const ContourList &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;

    void CairoPath(cairo_t *cr, bool show_hidden) const; ///< Draw the shapes as a path to a cairo context. If `show_hidden` is false, we skip edges marked not visible.
    void CairoPath(cairo_t *cr, bool show_hidden, bool clockwiseonly) const; ///< Draw the shape as a path to a cairo context. If `show_hidden` is false, we skip edges marked not visible. If `clockwiseonly` is false, we skip counterclockwise contours.
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden) const;
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool clockwiseonly) const;

    double Distance(const XY &o, XY &ret) const noexcept;
    double DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const;
    Range Cut(const Edge &e) const;
    bool CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do = {}, bool swap = false) const;
    bool CrossPoints(std::vector<CPData> &ret, const Path &p, bool p_closed = true, const Block *bb_p = nullptr, CPTasks to_do = {}, bool swap = false) const;
    template <typename iter>
    bool CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const;
    Range CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const;
    void Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const;
    bool TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const;

    std::string Dump(bool precise) const;
    static ContourList UnsafeMake(std::vector<HoledSimpleContour>&& v);

protected:
    iterator begin() { return std::vector<HoledSimpleContour>::begin(); }
    iterator end() { return std::vector<HoledSimpleContour>::end(); }
    HoledSimpleContour &operator[](size_type i) { return at(i); } ///<Returns the i_th element in the list. Crashes if i is beyond our size.
    void append(const HoledSimpleContour &p);  ///<Append a shape & update bounding box. Assumes the shape not overlapping with us.
    void append(HoledSimpleContour &&p);       ///<Append (move) a shape & update bounding box. Assumes the shape not overlapping with us.
    void append(const ContourList &); ///<Append a list of shapes & update bounding box. Assumes none of the shapes is overlapping with us.

    void Invert() noexcept;   ///<Inverts all shapes in the list.

    void Shift(const XY &xy) noexcept; ///<Shifts all shapes in the list.
    void Scale(double sc) noexcept;    ///<Scales all shapes in the list.
    void Scale(const XY &sc) noexcept; ///<Scales all shapes in the list.
    void SwapXY() noexcept;            ///<Swaps the x and y coordinates of all the shapes in the list.
    void Rotate(double cos, double sin) noexcept; ///<Rotates all shapes in the list around the origin
    void RotateAround(const XY&c, double cos, double sin) noexcept; ///<Scales all shapes in the list around 'c'
    void NonInvertingTransform(const TRMatrix & M) noexcept;
    void InvertingTransform(const TRMatrix & M) noexcept;
    bool FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert, std::list<SimpleContour*> &list);

private:
    Block boundingBox; ///< Bounding box containing all of the ContourWithHole shapes.
};


/** A single contigous shape with potentially a list of non-overlapping holes.
 * @ingroup contour_intermal
 *
 * Or the opposite: a single contigous hole with potentially a list of non-overlapping
 * shapes in it. This is decided by the clockwiseness of the `outline` member.
 * The `holes` member shall have opposite clockwisedness than `outline`.
 *
 * For simplicity of description we will assume the outline is clockwise.
 *
 * The holes must be fully inside the outline. By we mean that no part of a hole
 * lies outside the outline and they do not even touch by edge.
 * However, it is possible that they touch
 * by vertex. That is, a vertex of a hole or the outline may lie on a vertex or edge of
 * the shape or a hole, respectively.
 */
class HoledSimpleContour
{
    template <typename Edgeish> friend class EdgeVector;
    template <typename Edgeish> friend class EdgeList;
    friend class SimpleContour;
    friend class Contour;
    friend class ContourList;
    friend class ContoursHelper;

protected:
    SimpleContour outline; ///<The outer shape
    ContourList   holes;   ///<The list of holes in it - inside `outline`

    HoledSimpleContour() noexcept = default; ///<Create an empty shape.
    HoledSimpleContour(double sx, double dx, double sy, double dy) : outline(sx, dx, sy, dy) {} ///<Create a rectangular shape
    HoledSimpleContour(const Block &b) : outline(b) {} ///<Create a rectangular shape
    HoledSimpleContour(XY a, XY b, XY c) : outline(a,b,c) {} ///<Create a triangle
    HoledSimpleContour(double ax, double ay, double bx, double by, double cx, double cy) : outline(ax, ay, bx, by, cx, cy) {} ///<Create a triangle
    /** Create an ellipse (or ellipse slice) shape.
     *
     * @param [in] c Centerpoint
     * @param [in] radius_x Radius in the x direction.
     * @param [in] radius_y Radius in the y direction (same as `radius_x` if omitted = circle)
     * @param [in] tilt_deg The tilt of the ellipse in degrees. 0 if omitted.
     * @param [in] s_deg The startpoint of the arc.
     * @param [in] d_deg The endpoint of the arc. If unequal to `s_deg` a straight line is added to close the arc.
    */
    HoledSimpleContour(const XY &c, double radius_x, double radius_y=0, double tilt_deg=0, double s_deg=0, double d_deg=360) :
        outline(c, radius_x, radius_y, tilt_deg, s_deg, d_deg) {}

    void assign(const SimpleContour &a) {outline = a; holes.clear();} ///<Assign a SimpleContour to us (no holes).
    void assign(SimpleContour &&a) noexcept {outline.swap(a); holes.clear(); } ///<Move a SimpleContour to us (no holes). `a` becomes undefined
    void assign(const HoledSimpleContour &a) {operator=(a);}          ///<Copy a HoledSimpleContour to us.
    void assign(HoledSimpleContour &&a) noexcept {if (this!=&a) swap(a);}      ///<Move a HoledSimpleContour to us. `a` becomes undefined.

    void clear() noexcept {outline.clear(); holes.clear();} ///<Delete all content.

    void Invert() noexcept {outline.Invert(); holes.Invert();} ///<Reverse the direction (clockwise/counterclockwise) of both `outline` and `holes`.
    /** Remove edges shorter than 'tolerance'. 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 ret = outline.Simplify(tolerance); return holes.Simplify() || ret; } //make sure both run.
    void Shift(const XY &xy) noexcept { outline.Shift(xy); holes.Shift(xy); } ///<Translate the shape.
    void Scale(double sc) noexcept { outline.Scale(sc); holes.Scale(sc); } ///<Scale the shape.
    void Scale(const XY& sc) noexcept { outline.Scale(sc); holes.Scale(sc); } ///<Scale the shape.
    void SwapXY() noexcept { outline.SwapXY(); holes.SwapXY();  } ///<Transpose the shape: swap X and Y coordinates (but keep clockwisedness).
    void Rotate(double cos, double sin) noexcept {outline.Rotate(cos, sin); holes.Rotate(cos, sin);} ///<Rotate the shape by `radian`. `sin` and `cos` are pre-computed values.
    void RotateAround(const XY&c, double cos, double sin) noexcept {outline.RotateAround(c, cos, sin); holes.RotateAround(c, cos, sin);} ///<Rotate the shape around 'c' by `radian`. `sin` and `cos` are pre-computed values.
    void Expand(EExpandType type4positive, EExpandType type4negative, double gap, Contour &res,
                double miter_limit_positive, double miter_limit_negative) const;
    Contour CreateExpand2D(const XY &gap) const;
    HoledSimpleContour &NonInvertingTransform(const TRMatrix & M) noexcept { outline.NonInvertingTransform(M); holes.NonInvertingTransform(M); return *this; }
    HoledSimpleContour CreateNonInvertingTransformed(const TRMatrix & M) const { HoledSimpleContour ret(*this); return ret.NonInvertingTransform(M); }
    HoledSimpleContour &InvertingTransform(const TRMatrix & M) noexcept { outline.InvertingTransform(M); holes.InvertingTransform(M); return *this;}
    HoledSimpleContour CreateInvertingTransformed(const TRMatrix & M) const { HoledSimpleContour ret(*this); return ret.InvertingTransform(M); }
    EContourRelationType RelationTo(const HoledSimpleContour &c, bool ignore_holes) const;
    EContourRelationType RelationTo(const ContourList &c, bool ignore_holes) const { return switch_side(c.RelationTo(*this, ignore_holes)); } ///<Retermine a relation to a list of shapes.

    void Distance(const HoledSimpleContour &c, DistanceType &ret) const noexcept;
    bool FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert, std::list<const SimpleContour*> &list) const;
    bool FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert, std::list<SimpleContour*> &list);
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Path &p, bool p_closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const
    {
        bool r = outline.CrossPoints(ret, p, p_closed, bb_o, to_do, swap) && holes.empty();
        holes.CrossPoints(ret, p, p_closed, bb_o, to_do, swap);
        return r;
    }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    bool CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const {
        bool r = outline.CrossPoints(ret, from, last, closed, bb_o, to_do, swap) && holes.empty();
        holes.CrossPoints(ret, from, last, closed, bb_o, to_do, swap);
        return r;
    }

public:
    HoledSimpleContour(const SimpleContour &p) : outline(p) {} ///<Create a shape with no holes.
    HoledSimpleContour(const HoledSimpleContour& p) = default;
    HoledSimpleContour(HoledSimpleContour&& p) noexcept = default;
    void swap(HoledSimpleContour &b) {holes.swap(b.holes); outline.swap(b.outline);} ///<Swap content with another HoledSimpleContour.
    bool operator < (const HoledSimpleContour& p) const noexcept { return std::tie(outline, holes)< std::tie(p.outline, p.holes); } ///<Comparision operator for container ordering.
    bool operator ==(const HoledSimpleContour& p) const noexcept { return std::tie(outline, holes)==std::tie(p.outline, p.holes); } ///<Tests equality. Returns true only if even the order of vertices and holes is identical.
    HoledSimpleContour &operator = (const Block &a) {outline = a; holes.clear(); return *this;} ///<Assigns a rectangular shape with no holes.
    HoledSimpleContour &operator = (const SimpleContour &a) {outline=a; holes.clear(); return *this;} ///<Assigns a shape with no holes.
    HoledSimpleContour &operator = (SimpleContour &&a) noexcept {outline.swap(a); holes.clear(); return *this;} ///<Moves a shape with no holes. `a` becomes undefined.
    HoledSimpleContour& operator = (const HoledSimpleContour& a) = default; ///<Copy a shape.
    HoledSimpleContour& operator = (HoledSimpleContour&& a) noexcept = default; ///<Move a shape, `a` becomes undefined.
    operator Path() const { Path p; AppendToPath(p); return p; } ///<Converts the shape (with its holes) to a Path object
    void AppendToPath(Path &p) const { outline.AppendToPath(p); holes.AppendToPath(p); } ///<Appends the shape (with its holes) to a Path object
    void MoveToPath(Path &p) { outline.MoveToPath(p); holes.AppendToPath(p); } ///<Moves the shape (with its holes) to a Path object (destroying us)
    Path CreatePathFromSelected(bool keep(const Edge&)) const { return outline.CreatePathFromSelected(keep).append(holes.CreatePathFromSelected(keep)); } ///<Copy the edges matching a criteria to a path
    void SetVisible(bool visible) const noexcept { outline.SetVisible(visible);  holes.SetVisible(visible); }
    void SetInternalMark(bool imark) const noexcept { outline.SetInternalMark(imark);  holes.SetInternalMark(imark); }
    void SetMark(unsigned mark) const noexcept { outline.SetMark(mark);  holes.SetMark(mark); }
    void Apply(Edge::Update u) const noexcept { outline.Apply(u); holes.Apply(u); }

    bool IsSane(bool shouldbehole=false) const;
    bool IsEmpty() const noexcept {return outline.IsEmpty();} ///<True if shape has no outline (and holes).

    EPointRelationType IsWithin(const XY &p) const noexcept;
    EPointRelationType Tangents(const XY &p, XY &t1, XY &t2) const;
    bool HasHoles() const noexcept {return !holes.IsEmpty();} ///<True if shape has holes.
    void ClearHoles() noexcept { holes.clear(); } ///<Removes all the holes - this does not violate any of our invariants or that of our Contour holder.
    const ContourList &Holes() const noexcept {return holes;} ///<Return the holes
    const SimpleContour &Outline() const & noexcept { return outline; } ///<Return just the outline of the contour - no holes
    SimpleContour && Outline() && noexcept { return std::move(outline); } ///<Return just the outline of the contour - no holes
    const Block &GetBoundingBox(void) const noexcept {return outline.GetBoundingBox();} ///<Return the bounding box.
    bool GetClockWise() const noexcept {return outline.GetClockWise();} ///<Ture if the `outline` is clockwise.
    double GetArea(bool consider_holes=true) const noexcept {return consider_holes && !holes.IsEmpty() ? outline.GetArea() + holes.GetArea(true) : outline.GetArea();} ///<Return surface area. Holes are excluded. Negative if `outline` is counterclockwise.
    double GetCircumference(bool consider_holes=true, bool include_hidden=false) const noexcept {return consider_holes ? outline.GetCircumference(include_hidden) + holes.GetCircumference(true, include_hidden) : outline.GetCircumference(include_hidden);} ///<Return the length of the circumference. Holes are added in.
    XY CentroidUpscaled() const noexcept {return holes.IsEmpty() ? outline.CentroidUpscaled() : outline.CentroidUpscaled() + holes.CentroidUpscaled();} ///<Returns the centroid of the shape, scaled by its (potentially negative) area.

    /** Creates a cross-section of the HoledSimpleContour along a vertical line.
     *
     * @param [in] x The x coordinate of the vertical line
     * @param section A DoubleMap containing true for points inside the shape, false for outside. May contain entries already.
     */
    void VerticalCrossSection(double x, DoubleMap<bool> &section) const {outline.VerticalCrossSection(x, section); if (holes.size()) holes.VerticalCrossSection(x, section);}
    /** Determine the relative vertical distance between two shapes.
     *
     * For detailed explanation and parameters, see
     * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
     */
    double OffsetBelow(const SimpleContour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const {return outline.OffsetBelow(below, touchpoint, offset);}

    void CairoPath(cairo_t *cr, bool show_hidden) const { outline.CairoPath(cr, show_hidden); if (holes.size()) holes.CairoPath(cr, show_hidden); } ///< Draw the shape as a path to a cairo context. If `show_hidden` is false, we skip edges marked not visible.
    void CairoPath(cairo_t *cr, bool show_hidden, bool clockwiseonly) const { outline.CairoPath(cr, show_hidden, clockwiseonly); if (holes.size()) holes.CairoPath(cr, show_hidden, clockwiseonly); } ///<Draw the shape to the path of a cairo context, but only if `clockwiseonly` equals to the clockwisedness of this shape. If `show_hidden` is false, we skip edges marked not visible.
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden) const { outline.CairoPathDashed(cr, pattern, show_hidden); if (holes.size()) holes.CairoPathDashed(cr, pattern, show_hidden); } ///<Draw the shape in dashed lines to the path of a cairo context. Needed for backends not supporting dashed lines.
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool clockwiseonly) const { outline.CairoPathDashed(cr, pattern, show_hidden, clockwiseonly); if (holes.size()) holes.CairoPathDashed(cr, pattern, show_hidden, clockwiseonly); } ///<Draw the shape in dashed lines to the path of a cairo context. Needed for backends not supporting dashed lines.

    double Distance(const XY &o, XY &ret) const;
    double DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const;
    Range Cut(const Edge &e) const {return outline.Cut(e);} ///< Returns a cut of the shape along an edge
    std::array<CPData, 2> CutEx(const Edge &e) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do = {}, bool swap = false) const;
    Range CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const {return outline.CutWithTangent(e, from, till);} ///<Returns a cut of the shape along an edge (with tangents).
    void Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const {outline.Cut(A, B, map); holes.Cut(A, B, map);} ///< Returns a cut of the shape along an infinite line

    std::string Dump(bool precise) const;
    static HoledSimpleContour UnsafeMake(SimpleContour&& o, ContourList&& h);
};

inline void ContourList::append(const ContourList &p) { _ASSERT(p.size()==0 || size()==0 || GetClockWise() == p.GetClockWise()); boundingBox += p.GetBoundingBox(); insert(end(), p.begin(), p.end()); }

/** A type used when initializing a contour from a path */
enum class EForceClockwise
{
    DONT,                   ///<Do not force a clockwise construction - result may be counterclockwise
    INVERT_IF_NEEDED,       ///<Invert negative contours if needed
    REMOVE_COUNTERCLOCKWISE ///<Simply drop counterclockwise parts
};

    /** An iterator to Contour. Needed because the first element is in a separate field 'first'.*/
template <bool is_constant>
class contour_iterator_base {
    using Cont = std::conditional_t<is_constant, const Contour, Contour>;
    constexpr static int end = std::numeric_limits<int>::max();
    friend class Contour;
    Cont *contour = nullptr;
    ptrdiff_t index = end; //-1=>first, [0..further.size()-1]=>further, smaller or larger=>end()()
    contour_iterator_base(Cont *c, int i) noexcept : contour(c), index(i) {}
    ptrdiff_t norm() const noexcept { return index<-1 ? contour->further.size() : std::min(index, ptrdiff_t(contour->further.size())); } ///<normalized in the [-1, further.size()] range
public:
    typedef ptrdiff_t difference_type;
    typedef std::conditional_t<is_constant, const HoledSimpleContour,  HoledSimpleContour> value_type;
    typedef value_type &reference;
    typedef value_type *pointer;
    typedef std::random_access_iterator_tag iterator_category;
    contour_iterator_base() noexcept = default;
    contour_iterator_base(const contour_iterator_base<is_constant> &) noexcept = default;
    contour_iterator_base &operator=(const contour_iterator_base<is_constant> &) noexcept = default;
    value_type &operator *() const noexcept { return index==-1 ? contour->first : contour->further[index]; }
    value_type *operator ->() const noexcept { return index==-1 ? &contour->first : &(contour->further[index]); }
    value_type &operator [](difference_type n) const noexcept { return norm()+n==-1 ? contour->first : contour->further[norm()+n]; }
    contour_iterator_base<is_constant> &operator++() noexcept { return operator+=(1); }
    contour_iterator_base<is_constant> operator++(int) const noexcept { return operator+(1); }
    contour_iterator_base<is_constant> &operator--() noexcept { return operator-=(1); }
    contour_iterator_base<is_constant> operator--(int) const noexcept { return operator-(1); }
    contour_iterator_base<is_constant> &operator+=(difference_type n) noexcept { index = norm() + n; if (index<-1 || index>=int(contour->further.size())) index = end; return *this; }
    contour_iterator_base<is_constant> &operator-=(difference_type n) noexcept { index = norm() - n; if (index<-1 || index>=int(contour->further.size())) index = end; return *this; }
    contour_iterator_base<is_constant> operator+(difference_type n) const noexcept { return contour_iterator_base<is_constant>(contour, index) += n; }
    contour_iterator_base<is_constant> operator-(difference_type n) const noexcept { return contour_iterator_base<is_constant>(contour, index) -= n; }
    difference_type operator-(const contour_iterator_base<is_constant> &o) const noexcept { if (contour==o.contour && !contour) return norm()-o.norm(); else { _ASSERT(0); return end; } }
    bool operator <(const contour_iterator_base<is_constant> &o) const noexcept { return operator-(o)<0; }
    bool operator <=(const contour_iterator_base<is_constant> &o) const noexcept { return operator-(o)<=0; }
    bool operator ==(const contour_iterator_base<is_constant> &o) const noexcept { return contour==o.contour && index==o.index; }
    bool operator !=(const contour_iterator_base<is_constant> &o) const noexcept { return contour!=o.contour || index!=o.index; }
    bool operator >=(const contour_iterator_base<is_constant> &o) const noexcept { return operator-(o)>=0; }
    bool operator >(const contour_iterator_base<is_constant> &o) const noexcept { return operator-(o)>0; }
};



} //namespace contour

namespace std
{

template <>
struct iterator_traits<contour::contour_iterator_base<false>> {
    typedef int difference_type;
    typedef contour::contour_iterator_base<false>::value_type value_type;
    typedef contour::contour_iterator_base<false>::reference reference;
    typedef contour::contour_iterator_base<false>::pointer pointer;
    typedef contour::contour_iterator_base<false>::iterator_category iterator_category;
};

template <>
struct iterator_traits<contour::contour_iterator_base<true>> {
    typedef int difference_type;
    typedef contour::contour_iterator_base<true>::value_type value_type;
    typedef contour::contour_iterator_base<true>::reference reference;
    typedef contour::contour_iterator_base<true>::pointer pointer;
    typedef contour::contour_iterator_base<true>::iterator_category iterator_category;
};

} //namespace std

namespace contour
{

/** The main class in the library - an arbitrary shape (may be non-contiguous, may have holes).
 * @ingroup contour
 *
 * It contains a set of disjoint positive shapes with potentially holes in them. A wide variety of operations
 * are defined, see the public members.
 */
class Contour
{
    template <typename Edgeish> friend class EdgeVector;
    template <typename Edgeish> friend class EdgeList;
    friend class SimpleContour;
    friend class HoledSimpleContour;
    friend class ContourList;
    friend class ContoursHelper;
    template <bool is_constant> friend class contour_iterator_base;
    template <typename LT> friend void Distance(const LT &list, const Contour &c, DistanceType &dist_so_far);

    /** A list of possible operations on contours.
     *
     * Used internally. There are operations for union, intersection and xor
     * (subtraction is implemented via union). There are two sets of these operations
     * depending on whether we expect to get a clockwise or counterclockwise shape.
     * If we take the union of two clockwise surface, we should use POSITIVE_UNION,
     * if we take the union of two holes (counterclockwise), we should use NEGATIVE_UNION.
     * If the input shapes differ in clockwiseness, man, think about what you really want....
     * (The only legitimate situation I can think of is subtraction, like A-B, when
     * we take the union of A and the inverse of B. But in this case we use POSITIVE or
     * NEGATIVE depending on the clockwiseness of 'A'.)
     * This is important, for in case of a positive expectation we discard standalone parts
     * of the result if they are counterclockwise (a hole that is not inside something);
     * and vice versa for negative expectations.
     *
     * Then there are two rules for untangling, one for each winding rule.
     *
     * Finally there are two rules for untangling after expansion, depending again on
     * whether we want a clockwise or counterclockwise surface.
     */

    enum EOperationType {
        POSITIVE_UNION=0,     ///<Take the union of two (or more) positive shapes. Include places covered by any of the shapes.
        POSITIVE_INTERSECT,   ///<Take the intersection of two (or more) positive shapes. Include places covered by all of the shapes.
        POSITIVE_XOR,         ///<Take the xor of two (or more) positive shapes. Include places covered by odd number of the shapes.
        WINDING_RULE_NONZERO, ///<Untangle a set of edges using the nonzero winding rule.
        WINDING_RULE_EVENODD, ///<Untangle a set of edges using the evenodd winding rule.
        EXPAND_POSITIVE,      ///<Untangle a contour resulting from expanding a positive SimpleContour.
        NEGATIVE_UNION,       ///<Take the union of two (or more) negative (counterclockwise) shapes (holes). The resulting hole will include places included in any of the holes.
        NEGATIVE_INTERSECT,   ///<Take the intersection of two (or more) negative (counterclockwise) shapes (holes). The resulting hole will include places included in all of the holes.
        NEGATIVE_XOR,         ///<Take the xor of two (or more) negative (counterclockwise) shapes (holes). The resulting hole will include places included in an odd number of the holes.
        EXPAND_NEGATIVE       ///<Untangle a contour resulting from expanding a counterclockwise SimpleContour.
    };
    static bool is_positive(EOperationType t) {return t<=EXPAND_POSITIVE;} ///<True if the operation works on and hence produces clockwise shapes.
    friend std::string to_string(EOperationType t) {
        switch (t) {
        case POSITIVE_UNION:        return "positive union";
        case POSITIVE_INTERSECT:    return "positive intersect";
        case POSITIVE_XOR:          return "positive xor";
        case WINDING_RULE_NONZERO:  return "untangle winding rule nonzero";
        case WINDING_RULE_EVENODD:  return "untangle winding rule evenodd";
        case EXPAND_POSITIVE:       return "untangle expand positive";
        case NEGATIVE_UNION:        return "negative union";
        case NEGATIVE_INTERSECT:    return "negative intersect";
        case NEGATIVE_XOR:          return "negative xor";
        case EXPAND_NEGATIVE:       return "untangle expand negative";
        default:                    return "invalid";
        }
    }

    /** The first contiguous shape in the list of disjoint shapes.
     *
     * The first such shape is a separate member. This is to reduce heap operations,
     * when the Contour consists only of a single contiguous member (most common case).
     * In this case the HoledSimpleContour associated with that single member will not
     * be allocated on the heap as the sole member of a list.
     *
     * In the most common case of the shape containing no holes, the only heap
     * allocation will be for the vector of edges.
     */
    HoledSimpleContour first;
    ContourList further; ///< If the contour has multiple disjoint shapes, this contains all of them but the first.

    friend Contour operator + (const Contour &a, const Contour &b);
    friend Contour operator * (const Contour &a, const Contour &b);
    friend Contour operator - (const Contour &a, const Contour &b);
    friend Contour operator ^ (const Contour &a, const Contour &b);
    friend Contour operator + (const Contour &a, Contour &&b);
    friend Contour operator * (const Contour &a, Contour &&b);
    friend Contour operator - (const Contour &a, Contour &&b);
    friend Contour operator ^ (const Contour &a, Contour &&b);
    friend Contour operator + (Contour &&a, const Contour &b);
    friend Contour operator * (Contour &&a, const Contour &b);
    friend Contour operator - (Contour &&a, const Contour &b);
    friend Contour operator ^ (Contour &&a, const Contour &b);
    friend Contour operator + (Contour &&a, Contour &&b);
    friend Contour operator * (Contour &&a, Contour &&b);
    friend Contour operator - (Contour &&a, Contour &&b);
    friend Contour operator ^ (Contour &&a, Contour &&b);

    /** @name Private constructors
     * @{ */
    /** Create a contour by executing an operation on two contours */
    Contour(EOperationType type, const Contour &c1, const Contour &c2) {Operation(type, c1, c2);}
    Contour(EOperationType type, const Contour &c1, Contour &&c2) { Operation(type, c1, std::move(c2)); }
    Contour(EOperationType type, Contour &&c1, const Contour &c2) { Operation(type, std::move(c1), c2); }
    Contour(EOperationType type, Contour &&c1, Contour &&c2) { Operation(type, std::move(c1), std::move(c2)); }
    //@}
protected:
    HoledSimpleContour &at(unsigned u) { return u==0 ? first : further[u-1]; }
    void append(const HoledSimpleContour &p) {if (p.IsEmpty()) return; if (IsEmpty()) first.assign(p); else further.append(p); } ///<Append a shape assuming it is disjoint.
    void append(HoledSimpleContour &&p)  {if (p.IsEmpty()) return; if (IsEmpty()) first.assign(std::move(p)); else further.append(std::move(p)); } ///<Append (move) a shape assuming it is disjoint.

    void Invert(); ///<Reverse the contour. Used internally for subtraction. Untangles the resulting shape.
    Contour& invert_dont_check() {first.Invert(); if (further.size()) further.Invert(); return *this;} ///<Reverse the contour. Used internally for substraction. Does not untangle the resulting shape.
    Contour CreateInverse() const {Contour tmp(*this); tmp.Invert(); return tmp;} ///<Creates a Contour that is the invese of us.
    Contour create_inverse_dont_check() const { Contour tmp(*this); tmp.invert_dont_check(); return tmp; } ///<Creates a Contour that is the invese of us. Used internally for substraction. Does not untangle the resulting shape.

    /** @name Operation() variants.
     * Performs an operation on one or two contours and stores the result in `this`. Used internally.*/
    /** @{ */
    void Operation(EOperationType type, const Contour &c1);
    void Operation(EOperationType type, Contour &&c1);
    void Operation(EOperationType type, const Contour &c1, const Contour &c2);
    void Operation(EOperationType type, const Contour &c1, Contour &&c2);
    void Operation(EOperationType type, Contour &&c1, Contour &&c2);
    /** @} */

    /** Helper: Expands the shape and stores the result in res.
     *
     * @param [in] type4positive Specifies how to handle joins for clockwise contours.
     * @param [in] type4negative Specifies how to handle joins for counterclockwise contours (that is, holes).
     * @param [in] gap The amount to expand (shrink if <0).
     * @param [out] res The result is stored here.
     * @param [in] miter_limit_positive The miter limit for the clockwise contours.
     * @param [in] miter_limit_negative The miter limit for the counterclockwise contours.
     */
    void Expand(EExpandType type4positive, EExpandType type4negative, double gap, Contour &res,
                double miter_limit_positive, double miter_limit_negative) const;
    void Distance(const Contour &c, DistanceType &dist_so_far) const; ///<Helper: calculates the distance
    Contour &Untangle(Contour &&tmp, EForceClockwise force_clockwise, bool winding); ///<Untangle after assigning random edges
public:
    using iterator = contour_iterator_base<false>;
    using const_iterator = contour_iterator_base<true>;

    /** @name Constructors */
    /** @{ */
    Contour() noexcept = default; ///<Creates an empty Contour.
    /** Creates a rectangle.
     *
     * @param [in] sx X coordinate of the left side.
     * @param [in] dx X coordinate of the right side.
     * @param [in] sy Y coordinate of the upper side.
     * @param [in] dy Y coordinate of the lower side.
     *
     * Coordinates are reversed if they are in the wrong order, e.g., dx<sx or dy<sy.
     * An empty contour is created if sx==dx or sy==dy. */
    Contour(double sx, double dx, double sy, double dy) : first(sx, dx, sy, dy) {}
    Contour(const Block &b) : first(b) {} ///<Creates a rectangle specified by Block.
    /** Creates a triangle.
     *
     * Irrespective of the order and arrangement of the points a clockwise triangle is created.
     * An empty contour is created if two of the points equal or they fall on a line. */
    Contour(XY a, XY b, XY c) : first(a,b,c) {}
    /** Creates a triangle.
     *
     * Irrespective of the order and arrangement of the points a clockwise triangle is created.
     * An empty contour is created if two of the points equal or they fall on a line. */
    Contour(double ax, double ay, double bx, double by, double cx, double cy) : first(ax, ay, bx, by, cx, cy) {} ///<Creates a triangle
    /** Create an ellipse (or ellipse slice) shape.
     *
     * @param [in] c Center point
     * @param [in] radius_x Radius in the x direction. Its absolute value is used.
     * @param [in] radius_y Radius in the y direction (same as `radius_x` if omitted = circle) Its absolute value is used.
     * @param [in] tilt_deg The tilt of the ellipse in degrees. 0 if omitted.
     * @param [in] s_deg The start point of the arc.
     * @param [in] d_deg The endpoint of the arc. If equal to `s_deg` a full ellipse results, else a straight line is added to close the arc.
     *
     * If `radius_x` is zero, we return an empty shape. If `radius_y` is zero, we assume it to be the same as `radius_x` (circle). */
    Contour(const XY &c, double radius_x, double radius_y=0, double tilt_deg=0, double s_deg=0, double d_deg=360) :
        first (c, radius_x, radius_y, tilt_deg, s_deg, d_deg) {}
    /** Creates a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    explicit Contour(std::span<const XY> v, EForceClockwise force_clockwise=EForceClockwise::INVERT_IF_NEEDED, bool winding=true) {assign(v, force_clockwise, winding);}
    /** Creates a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    explicit Contour(std::initializer_list<XY> v, EForceClockwise force_clockwise = EForceClockwise::INVERT_IF_NEEDED, bool winding = true) { assign(v, force_clockwise, winding); }

    explicit Contour(const HoledSimpleContour &p) : first(p) {}       ///<Create a contour of a single shape by copying a HoledSimpleContour object.
    explicit Contour(const SimpleContour &p) : first(p) {}            ///<Create a contour of a single shape with no holes by copying a SimpleContour object.
    explicit Contour(HoledSimpleContour &&p) : first(std::move(p)) {} ///<Create a contour of a single shape by moving a HoledSimpleContour object. `p` will become undefined.
    explicit Contour(SimpleContour &&p) : first(std::move(p)) {}      ///<Create a contour of a single shape with no holes by moving a SimpleContour object. `p` will become undefined.
    Contour(Contour&& a) noexcept = default;
    Contour(const Contour& a) = default;                              
    /** Creates a closed contour from a path.
     * @param [in] p The path
     * @param [in] close What to do with non-closed segments of path
     * @param [in] force_clockwise What to do with counterclockwise sub-contours.
     * @param [in] winding If we shall use the winding rule when untangling or the evenodd rule*/
    explicit Contour(const Path &p, ECloseType close = ECloseType::IGNORE_OPEN_PATH, EForceClockwise force_clockwise=EForceClockwise::INVERT_IF_NEEDED, bool winding = true) { assign(p, close, force_clockwise, winding); }
    /** Creates a closed contour from a path.
     * @param [in] p The path
     * @param [in] close What to do with non-closed segments of path
     * @param [in] force_clockwise What to do with counterclockwise sub-contours.
     * @param [in] winding If we shall use the winding rule when untangling or the evenodd rule*/
    explicit Contour(Path &&p, ECloseType close = ECloseType::IGNORE_OPEN_PATH, EForceClockwise force_clockwise=EForceClockwise::INVERT_IF_NEEDED, bool winding = true) { assign(std::move(p), close, force_clockwise, winding); }
    /** Creates a contour from a path - used after expanding something.*/
    Contour(const Path &p, bool positive, ECloseType close) { assign(p, positive, close); }
    /** Creates a contour from a path - used after expanding something.*/
    Contour(Path &&p, bool positive, ECloseType close) { assign(std::move(p), positive, close); }
    /** Creates a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    template<size_t SIZE> explicit Contour(const XY(&v)[SIZE], EForceClockwise force_clockwise = EForceClockwise::INVERT_IF_NEEDED, bool winding = true) { assign(std::span<const XY>(v, SIZE), force_clockwise, winding); }
    /** @} */ //Constructors

    /** @name Assignment */
    /** @{ */
    /** Set the Contour to a rectangle */
    Contour &operator = (const Block &a) {first = a; further.clear(); return *this;}
    /** Set the Contour to a simple shape with no holes */
    Contour &operator = (const SimpleContour &a) {first.operator=(a); further.clear(); return *this;}
    /** Set the Contour to a simple shape with no holes by moving it. `a` becomes undefined.*/
    Contour &operator = (SimpleContour &&a) noexcept {first.operator=(std::move(a)); return *this;}
    /** Set the Contour to a contiguous shape.*/
    Contour &operator = (const HoledSimpleContour &a) {first.operator=(a); further.clear(); return *this;}
    /** Set the Contour to a contiguous shape by moving it. `a` becomes undefined.*/
    Contour &operator = (HoledSimpleContour &&a) {first.operator=(std::move(a)); further.clear(); return *this;}
    /** Standard assignment move operation. `a` becomes undefined. */
    Contour& operator = (Contour&& a) noexcept = default;
    /** Standard assignment operation. */
    Contour& operator = (const Contour& a) = default;
    /** Sets the Contour to the enclosed area(s) of a path. Untangles them.*/
    Contour &operator = (const Path &p) { assign(p); return *this; }
    /** Sets the Contour to the enclosed area(s) of a path. Untangles them.*/
    Contour &operator = (Path &&p) { assign(std::move(p)); return *this; }

    /** Sets the Contour to a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    Contour &assign(std::span<const XY> v, EForceClockwise force_clockwise=EForceClockwise::INVERT_IF_NEEDED, bool winding=true)
    { Contour tmp; tmp.assign_dont_check(v); Untangle(std::move(tmp), force_clockwise, winding); return *this; }
    /** Sets the Contour to a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise.  */
    template<size_t SIZE> Contour &assign(const XY(&v)[SIZE])
    { return assign(std::span<const XY>(v, SIZE)); }
    /** Sets the Contour to a polygon from an ordered list of points. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    Contour &assign(std::initializer_list<XY> v, EForceClockwise force_clockwise = EForceClockwise::INVERT_IF_NEEDED, bool winding = true)
    { Contour tmp; tmp.assign_dont_check(v); Untangle(std::move(tmp), force_clockwise, winding);  return *this; }
    /** Sets the Contour to a shape with no holes from an ordered list of edges. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    template <typename Edgeish>
    Contour &assign(std::span<const Edgeish> v, ECloseType close = ECloseType::IGNORE_OPEN_PATH, EForceClockwise force_clockwise = EForceClockwise::INVERT_IF_NEEDED, bool winding = true);
    /** Sets the Contour to the enclosed area(s) of a path. Untangles them using the winding rule if `winding` is true, using evenodd rule otherwise. */
    Contour &assign(const Path &p, ECloseType close = ECloseType::IGNORE_OPEN_PATH, EForceClockwise force_clockwise=EForceClockwise::INVERT_IF_NEEDED, bool winding = true)
    { return assign(std::span<const Edge>(p), close, force_clockwise, winding); }
    /** Sets the Contour to the enclosed area(s) of a path. Used after the path has been expanded: untangles them and keeps areas of only positive/negative coverage. */
    Contour &assign(const Path &p, bool positive, ECloseType close);
    /** Sets the Contour to the enclosed area(s) of a path. Used after the path has been expanded: untangles them and keeps areas of only positive/negative coverage. */
    Contour &assign(Path &&p, bool positive, ECloseType close);

    /** Sets the Contour to a polygon from an ordered list of points. Assumes the points specify an untangled polygon. */
    Contour &assign_dont_check(std::span<const XY> v) {clear(); first.outline.assign_dont_check(v); return *this;}
    /** Sets the Contour to a polygon from an ordered list of points. Assumes the points specify an untangled polygon. */
    Contour &assign_dont_check(std::initializer_list<XY> v) { clear(); first.outline.assign_dont_check(v); return *this; }
    /** Sets the Contour to a polygon from an ordered list of points. Assumes the points specify an untangled polygon. */
    template<size_t SIZE> Contour &assign_dont_check(const XY(&v)[SIZE]) { return assign_dont_check(std::span<const XY>(v, SIZE)); }
    /** Sets the Contour to a shape with no holes from an ordered list of edges. Assumes the points specify an untangled polygon. */
    template <typename Edgeish>
    Contour &assign_dont_check(std::span<const Edgeish> v) {clear();  first.outline.assign_dont_check(v); return *this;}
    /** Sets the Contour to a shape with no holes from an ordered list of edges. Assumes the points specify an untangled polygon. */
    Contour &assign_dont_check(const Path &p, ECloseType close = ECloseType::IGNORE_OPEN_PATH) { clear(); for (auto &e : p.ConvertToClosed(close)) { SimpleContour s; s.assign_dont_check(std::move(static_cast<Path&>(e))); append_dont_check(std::move(s)); } return *this; }
    Contour &assign_dont_check(Path &&p, ECloseType close = ECloseType::IGNORE_OPEN_PATH);

    /** Appends shape. Assumes that it does not overlap with the existing Contour. */
    Contour &append_dont_check(HoledSimpleContour &&v) {if (!v.IsEmpty()) append(std::move(v)); return *this;}
    /** Appends shape. Assumes that it does not overlap with the existing Contour. */
    Contour &append_dont_check(const HoledSimpleContour &v) { if (!v.IsEmpty()) append(v); return *this; }
    /** Appends shape. Assumes that it does not overlap with the existing Contour. */
    Contour &append_dont_check(SimpleContour &&v) { if (!v.IsEmpty()) append(HoledSimpleContour(std::move(v))); return *this; }
    /** Appends shape. Assumes that it does not overlap with the existing Contour. */
    Contour &append_dont_check(const SimpleContour &v) { if (!v.IsEmpty()) append(HoledSimpleContour(v)); return *this; }
    /** Appends the Contour with  a polygon from an ordered list of points. Assumes the points specify an untangled polygon. */
    template<size_t SIZE> Contour &append_dont_check(const XY(&v)[SIZE])
    { SimpleContour tmp; tmp.assign_dont_check(std::span<const XY>(v, SIZE)); return append_dont_check(std::move(tmp));  }

    /** @name Basic information
     * @{ */
    size_t size() const noexcept {if (first.outline.size()) return further.size()+1; return 0;} ///< Returns how many outlines we have
    iterator begin() noexcept { return {this, first.IsEmpty() ? iterator::end : -1}; }
    const_iterator begin() const noexcept { return {this, first.IsEmpty() ? iterator::end : -1}; }
    const_iterator cbegin() const noexcept { return {this, first.IsEmpty() ? iterator::end : -1}; }
    iterator end() noexcept { return {this, iterator::end}; }
    const_iterator end() const noexcept { return {this, iterator::end}; }
    const_iterator cend() const noexcept { return {this, iterator::end}; }
    const HoledSimpleContour &at(unsigned u) const { return u==0 ? first : further.at(u-1); }
    const HoledSimpleContour &front() const & noexcept { return first; }
    HoledSimpleContour && front() && noexcept { return std::move(first); }

    bool IsEmpty() const noexcept {return first.IsEmpty();} ///<Returns if we have any content at all or not
    bool IsRectangle() const noexcept { return further.empty() && first.holes.empty() && first.outline.IsRectangle(); }
    const Block GetBoundingBox(void) const noexcept {return further.empty() ? first.GetBoundingBox() : first.GetBoundingBox()+further.GetBoundingBox();} ///<returns our bounding box
    bool GetClockWise() const noexcept {return first.GetClockWise();} ///<Returns if we are clockwise or not (all our outlines must be the same)
    double GetArea(bool consider_holes=true) const noexcept {return further.size() ? further.GetArea(consider_holes) + first.GetArea(consider_holes) : first.GetArea(consider_holes);} ///<Get the total surface area of our shapes. Substract holes if 'consider_holes' is ture. If we are counterclocwise result will be negative.
    /// Return the total combined length of the circumference of the shapes
    /// If 'consider_holes' is true, we also add the circumference of holes (plus any positive shapes inside them and that of their holes and so on).
    /// If 'include_hidden' is true, we also add the length of invisible edges.
    double GetCircumference(bool consider_holes=true, bool include_hidden=false) const noexcept {return further.size() ? further.GetCircumference(consider_holes, include_hidden) + first.GetCircumference(consider_holes, include_hidden) : first.GetCircumference(consider_holes, include_hidden);}
    XY Centroid() const noexcept {return (further.IsEmpty() ? first.CentroidUpscaled() : first.CentroidUpscaled()+further.CentroidUpscaled())/GetArea();} ///<Returns the centroid (centerpoint) of us.
    const HoledSimpleContour &operator[](size_t n) const noexcept {return n==0 ? first : further[n-1];} ///<Returns a reference to the n_th outline. Crashes if 'n' is out-of-bounds.
    bool IsSane() const;
    bool operator < (const Contour &p) const noexcept {return first==p.first ? further < p.further: first < p.first;}
    bool operator ==(const Contour &p) const noexcept {return first==p.first && further == p.further;}
    /** @} */ //Basic information

    /** @name Basic Operations
     * @{ */
    /** Swaps us with another Contour.*/
    void swap(Contour &a) noexcept {first.swap(a.first); further.swap(a.further); }
    /** Clears all content from us.*/
    void clear() noexcept {first.clear(); further.clear(); }
    /** Removes holes from all our shapes. Returns a reference to us.*/
    Contour &ClearHoles() noexcept {first.holes.clear(); for (auto i = further.begin(); i!=further.end(); i++) i->holes.clear(); return *this;}
    Contour &ClearHolesIn(unsigned u) { at(u).ClearHoles(); return *this; }
    Contour &Remove(unsigned u);
    Contour &RemoveAllBut(unsigned u);
    std::list<const SimpleContour *> FindSimpleContour(const XY &point, bool has_to_be_inside) const;
    std::list<SimpleContour *> FindSimpleContour(const XY &point, bool has_to_be_inside);
    /** Appends our edges to a Path object*/
    void AppendToPath(Path &p) const { first.AppendToPath(p); further.AppendToPath(p); }
    /** Appends our edges to a Path object (by moving) */
    void MoveToPath(Path &p) { first.MoveToPath(p); further.AppendToPath(p); }
    Path CreatePathFromSelected(bool keep(const Edge&)) const { return first.CreatePathFromSelected(keep).append(further.CreatePathFromSelected(keep)); } ///<Copy the edges matching a criteria to a path
    Path CreatePathFromVisible() const { return CreatePathFromSelected([](const Edge&e) {return e.IsVisible(); }); }
    const Contour &SetVisible(bool visible) const noexcept { first.SetVisible(visible);  further.SetVisible(visible); return *this; }
    const Contour& SetInternalMark(bool imark) const noexcept { first.SetInternalMark(imark);  further.SetInternalMark(imark); return *this; }
    const Contour &SetMark(unsigned mark) const noexcept { first.SetMark(mark);  further.SetMark(mark); return *this; }
    const Contour &Apply(Edge::Update u) const noexcept { first.Apply(u); further.Apply(u); return *this; }
    Contour &SetVisible(bool visible) noexcept { first.SetVisible(visible);  further.SetVisible(visible); return *this; }
    Contour& SetInternalMark(bool imark) noexcept { first.SetInternalMark(imark);  further.SetInternalMark(imark); return *this; }
    Contour &SetMark(unsigned mark) noexcept { first.SetMark(mark);  further.SetMark(mark); return *this; }
    Contour &Apply(Edge::Update u) noexcept { first.Apply(u); further.Apply(u); return *this; }
    /** Remove edges shorter than 'tolerance'. 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 ret = first.Simplify(tolerance); return further.Simplify() || ret; } //make sure both run.
    /** @} */ //Basic Operations

    /** @name Transformations and expansion
     * A set of linear transformations and two expansion operation.
     * Members starting with 'Create...' create a new copy of the contour. The others modify the contour itself.
     * @{ */
    /** Translate the contour */
    Contour &Shift(const XY &xy) noexcept {first.Shift(xy); if (further.size()) further.Shift(xy); return *this;}
    /** Scale the contour */
    Contour &Scale(double sc) noexcept { first.Scale(sc); if (further.size()) further.Scale(sc); return *this; }
    /** Scale the contour */
    Contour &Scale(const XY &sc) noexcept { first.Scale(sc); if (further.size()) further.Scale(sc); return *this; }
    /** Transpose the contour by swapping x and y coordinate. The contour, however, remains clockwise. */
    Contour &SwapXY() noexcept {first.SwapXY(); if (further.size()) further.SwapXY(); return *this;}
    /** Rotate the contour around the origin by `degrees` degrees. */
    Contour &Rotate(double degrees) noexcept {if (degrees) {const double r=deg2rad(degrees); Rotate(cos(r), sin(r));} return *this;}
    /** Rotate the shape: `sin` and `cos` are pre-computed values.*/
    Contour& Rotate(double cos, double sin) noexcept { first.Rotate(cos, sin); further.Rotate(cos, sin); return *this; }
    /** Rotate the contour around the `c` by `degrees` degrees. */
    Contour &RotateAround(const XY&c, double degrees) noexcept {if (degrees) {const double r=deg2rad(degrees); RotateAround(c, cos(r), sin(r));} return *this;}
    /** Rotate the shape around `c` : `sin` and `cos` are pre-computed values.*/
    Contour& RotateAround(const XY&c, double cos, double sin) noexcept { first.RotateAround(c, cos, sin); further.RotateAround(c, cos, sin); return *this; }
    /** Apply a generic 2D transformation. */
    Contour &Transform(const TRMatrix & M) noexcept;

    /** Create a translated version of the contour. */
    Contour CreateShifted(const XY & xy) const {Contour a(*this); a.Shift(xy); return a;}
    /** Create a scaled version of the contour */
    Contour CreateScaled(double sc) const { Contour a(*this); a.Scale(sc); return a; }
    /** Create a scaled version of the contour */
    Contour CreateScaled(const XY &sc) const { Contour a(*this); a.Scale(sc); return a; }
    /** Create a clockwise, transposed version of the contour by swapping x and y coordinates. */
    Contour CreateSwapXYd() const {Contour a(*this); a.SwapXY(); return a;}
    /** Create a rotated version of the contour */
    Contour CreateRotated(double degrees) const {Contour a(*this); a.Rotate(degrees); return a;}
    /** Create a rotated version of the contour */
    Contour CreateRotated(double cos, double sin) const { Contour a(*this); a.Rotate(cos, sin); return a; }
    /** Create a version of the contour rotated around `c`. */
    Contour CreateRotatedAround(const XY&c, double degrees) const {Contour a(*this); a.RotateAround(c, degrees); return a;}
    /** Create a transformed version of us.*/
    Contour CreateTransformed(const TRMatrix & M) const;

    /** Expands a Contour.
     *
     * Expansion aims to create a shape whose edges run parallel along the edges of the
     * original, but at `gap` pixels away. Positive `gap` values result in actual expansion
     * negative values in shrinkage (the new shape is inside the original).
     *
     * After the expansion, we check if there are edges crossing each other and
     * remove them by untangling the result. In this step we also may end up in multiple separate
     * disjoint shapes. (E.g., shrinking a dog-bone shape may lead us to two circles,
     * like `O=O` becomes `o o`.)
     *
     * There are multiple typical ways to handle edge joins, see EExpandType. With this function
     * we can specify the join type separately for positive contour outlines and hole outlines.
     * It is also possible to specify respective miter limits. These tells us that with
     * EXPAND_MITER_* join types how long the miter edges can be in terms of the `gap`.
     * E.g., miter_limit=2 says they can be as long as `2*gap`. The rest is cut away with a bevel-like edge.
     *
     * @param [in] gap The amount of expansion/shrinkage. No-op if zero.
     * @param [in] et4pos The edge join type for shape outlines.
     * @param [in] et4neg The edge join type for hole outlines.
     * @param [in] miter_limit_positive The miter limit for edge joins of shape outlines.
     * @param [in] miter_limit_negative The miter limit for edge joins of hole outlines.
     * @returns A reference to `*this`. */
    Contour& Expand(double gap, EExpandType et4pos=EXPAND_MITER_ROUND, EExpandType et4neg=EXPAND_MITER_ROUND,
                    double miter_limit_positive=CONTOUR_INFINITY, double miter_limit_negative=CONTOUR_INFINITY);
    /** Creates an expanded version of the contour; for parameters, see Expand. */
    Contour CreateExpand(double gap, EExpandType et4pos=EXPAND_MITER_ROUND, EExpandType et4neg=EXPAND_MITER_ROUND,
                         double miter_limit_positive=CONTOUR_INFINITY, double miter_limit_negative=CONTOUR_INFINITY) const;
    /** Perform 2D expansion.
     *
     * For positive x and y `gap` values this function expands the shape such that placing a rectangle
     * 2*x wide and 2*y tall centered anywhere on or outside the expanded shape, the rectangle will not
     * overlap with the original shape.
     * This operation can be visualized as follows. Take an x*y rectangle touch the shape with it. Use
     * the rectangle as a brush and shift it along the contours of the shape while painting with it.
     * Outline of the the resulting, painted shape will be the result of the Expand2D operation.
     * @param [in] gap The x and y size of the rectangle used.
     * @returns A reference to `*this`.*/
    Contour& Expand2D(const XY &gap);
    /** Creates a 2D expanded version of the contour; for more details, see Expand2D. */
    Contour CreateExpand2D(const XY &gap) const;
    /** @} */ //Transformations

    /** @name Combination operations
     * A set of operations combining two contours.
     * Both copy and move versions available. Using the move version may result in less memory allocation.
     * For example, at taking the union of two disjoint shapes the move version need not create a separate copy,
     * but can simply append one of the shapes to the other.
     * The result of the operation is stored in `this` as is usually the case with `+=`, `*=`, etc operators.
     * Also, the returned reference is always to `*this`.
     * Note that separate `+`, `*`, `-` and `^` operators are defined outside class contour.
     * @{ */
    /** Take the union with `a`.*/
    Contour &operator += (const Contour &a) {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_UNION : NEGATIVE_UNION, std::move(*this), a); return *this;}
    /** Take the union with `a`.*/
    Contour &operator += (Contour &&a)      {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_UNION : NEGATIVE_UNION, std::move(*this), std::move(a)); return *this;}
    /** Take the intersection with `a`.*/
    Contour &operator *= (const Contour &a) {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_INTERSECT : NEGATIVE_INTERSECT, std::move(*this), a); return *this;}
    /** Take the intersection with `a`.*/
    Contour &operator *= (Contour &&a)      {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_INTERSECT : NEGATIVE_INTERSECT, std::move(*this), std::move(a)); return *this;}
    /** Substract `a` from us, that is keep only areas of us not covered by `a`.*/
    Contour &operator -= (const Contour &a) { Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_UNION : NEGATIVE_UNION, std::move(*this), a.create_inverse_dont_check()); return *this; }
    /** Substract `a` from us, that is keep only areas of us not covered by `a`.*/
    Contour &operator -= (Contour &&a)      {a.invert_dont_check(); Operation(GetClockWise() || !a.GetClockWise() ? POSITIVE_UNION : NEGATIVE_UNION, std::move(*this), std::move(a)); return *this;}
    /** Take `a` xor us, that is keep areas covered by strictly one of `a` or us.*/
    Contour &operator ^= (const Contour &a) {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_XOR : NEGATIVE_XOR, std::move(*this), a); return *this;}
    /** Take `a` xor us, that is keep areas covered by strictly one of `a` or us.*/
    Contour &operator ^= (Contour &&a)      {Operation(GetClockWise() || a.GetClockWise() ? POSITIVE_XOR : NEGATIVE_XOR, std::move(*this), std::move(a)); return *this;}
    /** @}*/ // Combination operations

    /** @name Graphics related operations
     * @{ */
    /** Establish the shape as a path(s) to a cairo context.
     *
     * This operation adds a number of closed paths to the cairo context. Holes are added counterclockwise
     * resulting in holes if you use cairo_fill() on the path.
     * If any of the closed contours has non-visible edges and `show_hidden` is false, those edges are
     * omitted abd the path will contain open sub-paths.
     * @param [in] cr The cairo context.
     * @param [in] show_hidden If false, we skip edges marked not visible.*/
    void CairoPath(cairo_t *cr, bool show_hidden) const { first.CairoPath(cr, show_hidden); if (further.size()) further.CairoPath(cr, show_hidden); }
    /** Establish parts of the shape as path(s) to a cairo context.
     *
     * This operation adds a number of closed paths to the cairo context, either the clockwise
     * outlines or the counterclockwise hole outlines (but not both).
     * If any of the closed contours has non-visible edges and `show_hidden` is false, those edges are
     * omitted abd the path will contain open sub-paths.
     * @param [in] cr The cairo context.
     * @param [in] show_hidden If false, we skip edges marked not visible.
     * @param [in] clockwiseonly If true only the clockwise outlines are added, if false, only the hole outlines (if any).*/
    void CairoPath(cairo_t *cr, bool show_hidden, bool clockwiseonly) const { first.CairoPath(cr, show_hidden, clockwiseonly); if (further.size()) further.CairoPath(cr, show_hidden, clockwiseonly); }
    /** Establish the shape as a path(s) to a cairo context using a series of sub-paths forming a dashed line.
     *
     * This operation is useful to emulate dashed lines for backends having no such support (Windows Metafiles).
     * This operation adds a number of open sub-paths, one for each dash segment.
     * If any of the closed contours has non-visible edges and `show_hidden` is false, those edges are
     * omitted.
     * @param [in] cr The cairo context.
     * @param [in] pattern Contains lengths of alternating on/off segments. Even indices contain the length of
                           on segments, odd indices the length of off segments.
     * @param [in] show_hidden If false, we skip edges marked not visible. */
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden) const { first.CairoPathDashed(cr, pattern, show_hidden); if (further.size()) further.CairoPathDashed(cr, pattern, show_hidden); }
    /** Establish the shape as a path(s) to a cairo context using a series of sub-paths forming a dashed line.
     *
     * This operation is useful to emulate dashed lines for backends having no such support (Windows Metafiles).
     * This operation adds a number of open sub-paths, one for each dash segment.
     * Either the clockwise outlines or the counterclockwise hole outlines are added (but not both).
     * If any of the closed contours has non-visible edges and `show_hidden` is false, those edges are
     * omitted.
     * @param [in] cr The cairo context.
     * @param [in] pattern Contains lengths of alternating on/off segments. Even indices contain the length of
                           on segments, odd indices the length of off segments.
     * @param [in] show_hidden If false, we skip edges marked not visible.
     * @param [in] clockwiseonly If true only the clockwise oultines are added, if false, only the hole outlines (if any).*/
    void CairoPathDashed(cairo_t *cr, std::span<const double> pattern, bool show_hidden, bool clockwiseonly) const { first.CairoPathDashed(cr, pattern, show_hidden, clockwiseonly); if (further.size()) further.CairoPathDashed(cr, pattern, show_hidden, clockwiseonly); }
    /** Strokes the shape on a cairo context. Line with, color, dash, operation, transformations and mask are taken from `cr`.*/
    void Line(cairo_t *cr, bool show_hidden=false) const { Contour::CairoPath(cr, show_hidden); cairo_stroke(cr); }
    /** Strokes the shape on a cairo context, stroking holes with a dashed line.*/
    void Line2(cairo_t *cr) const {cairo_save(cr); const double dash[]={2,2}; cairo_set_dash(cr, dash, 2, 0); CairoPath(cr, true); cairo_stroke(cr); cairo_set_dash(cr, nullptr, 0, 0); CairoPath(cr, false); cairo_stroke(cr); cairo_restore(cr);}
    /** Fills the shape on a cairo context with the source set in the context.*/
    void Fill(cairo_t *cr) const { Contour::CairoPath(cr, true); cairo_fill(cr); }
    /** @} */ //name Graphics related operations

    /** @name Advanced information functions
     * These functions keep the contour intact, merely provide information about it.
     * @{ */
    /** Returns the relation of a point and the shape. */
    EPointRelationType IsWithin(const XY &p) const {EPointRelationType ret = first.IsWithin(p); if (ret==WI_OUTSIDE) ret = further.IsWithin(p); return ret;}
    /** Returns the touchpoint of tangents drawn from a point to the shape.
     *
     * @param [in] p The point to draw the tangents from.
     * @param [out] t1 The point where the first tangent touches the shape.
     * @param [out] t2 The point where the second tangent touches the shape.
     * @returns The relation of the point to the shape. Meaningful tangents can only be drawn if this is WI_OUTSIDE.*/
    EPointRelationType Tangents(const XY &p, XY &t1, XY &t2) const {EPointRelationType ret = first.Tangents(p, t1, t2); if (ret==WI_OUTSIDE) ret = further.Tangents(p, t1, t2); return ret;}
    /** Returns the relation of two shapes.
     *
     * If `ignore holes` is true we consider only the outline of the shapes. In this case only
     * A_IS_EMPTY, B_IS_EMPTY, BOTH_EMPTY, A_INSIDE_B, B_INSIDE_A, SAME, APART, OVERLAP can be returned.
     * If false, these additional values may come:
     * A_IN_HOLE_OF_B, B_IN_HOLE_OF_A, IN_HOLE_APART.
     * (Latter meaning no overlap, but some parts of A is in holes and outside of B.) */
    EContourRelationType RelationTo(const Contour &c, bool ignore_holes) const;
    /** Returns if a relation means overlap */
    bool Overlaps(EContourRelationType t) const {return result_overlap(t);}
    /** Returns true if the two contours overlap (surface area of intersection is nonzero) */
    bool Overlaps(const Contour &c) const {return Overlaps(RelationTo(c, true));}
    /** Not implemented on purpose. Declaration here to prevent unintended use (similar function of Block exist). */
    bool Overlaps(const Contour &c, double gap) const = delete;
    double OffsetBelow(const SimpleContour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;
    double OffsetBelow(const Contour &below, double &touchpoint, double offset=CONTOUR_INFINITY) const;
    /** Returns distance of and two points on the two contours.
     *
     * May return invalid distance (infinity) if one or the other contour is empty.
     * Negative distance is returned if one is inside another (but positive if in a hole).
     * Zero returned if they cross. If the two points are equal that is a crosspoint, if not they are to be ignored.
     * (Latter may happen if one of the contours have two pieces and one is inside and one is outside the other.) */
    DistanceType Distance(const Contour &c) const {DistanceType r; Distance(c, r); return r;}
    /** Calculates the distance between a point and us by finding our closest point.
     *
     * @param [in] o The point to take the distance from.
     * @param [out] ret We return the point on our contour closes to `o`.
     * @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty. */
    double Distance(const XY &o, XY &ret) const { XY tmp; double d = first.Distance(o, ret); const double dd = further.Distance(o, tmp); if (fabs(dd)<fabs(d)) { ret = tmp; d = dd; } return d; }
    /** Calculates the distance between a point and us by finding our closest point and returns two tangent points.
     *
     * Same as Distance(const XY &o, XY &ret), but in addition we return two tangent points
     * from the tangent of the shape at `ret`. See @ref contour for a description of tangents.
     *
     * @param [in] o The point to take the distance from.
     * @param [out] ret We return the point on our contour closes to `o`.
     * @param [out] t1 The forward tangent point.
     * @param [out] t2 The backward tangent point.
     * @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty. */
    double DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const { XY tmp, _1, _2; double d = first.DistanceWithTangents(o, ret, t1, t2); const double dd = further.DistanceWithTangents(o, tmp, _1, _2); if (fabs(dd)<fabs(d)) { ret = tmp; t1 = _1; t2 = _2; d = dd; } return d; }
    /** Returns a cut of the shape along an (infinite) line.
     *
     * Takes all crosspoints of the infinite line defined by `A` and `B` and the shape
     * and returns the two outermost. We return `pos` values corresponding to the
     * finite section `A`->`B`,
     * thus both values are between 0 and 1. It is possible that a single value is
     * returned if the line touches the shape.
     * If the line does not cross or touch the shape, an invalid
     * range is returned.
     * @param [in] A One point on the line.
     * @param [in] B Another point on the line.
     * @return The `pos` value corresponding to the two outmost crosspoints. */
    Range Cut(const XY &A, const XY &B) const;
    /** Returns a cut of the shape along an edge.
     *
     * Takes all crosspoints of a (finite length, possibly curvy) edge and the shape
     * and returns the two outermost. We return `pos` values corresponding to the edge,
     * thus both values are between 0 and 1. It is possible that a single value is
     * returned if the edge touches the shape or if its start is inside the shape, but
     * its end is outside. If the edge does not cross or touch the shape, an invalid
     * range is returned. */
    Range Cut(const Edge &e) const {Range ret = first.Cut(e); if (!further.IsEmpty()) ret += further.Cut(e); return ret;}
    /** Creates a cross-section (or cut) of the Contour along a straight line.
     *
     * We return all crosspoints of an infinite line specified by `A` and `B`.
     * We return them in `pos` terms, that is, at `A` we return 0, at `B` we return 1 and
     * linearly interpolate and extrapolate between them and outside, respectively.
     *
     * @param [in] A One point of the infinite line.
     * @param [in] B Another point of the infinite line.
     * @param [out] map A DoubleMap containing true for points inside the shape, false for outside. */
    void Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const {first.Cut(A, B, map); if (!further.IsEmpty()) further.Cut(A, B, map); map.Prune();}
    /** Returns the crosspoints with an infinite line.
     * 'pos_other' values may be outside the [0..1] range.*/
    std::vector<CPData> CutExAll(const XY &A, const XY &B) const;
    /** Returns the max 2 outer crosspoints with an infinite line.
     * 'pos_other' values may be outside the [0..1] range.
     * If the line just touches, we return 1 element.
     * If no cp, we return 0 elements.
     * When we return 2, [0]->[1] is in the same direction as A->B.*/
    std::vector<CPData> CutExAll2(const XY &A, const XY &B) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do = {}, bool swap = false) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    std::vector<CPData> CrossPoints(const Edge &e, CPTasks to_do = {}, bool swap = false) const
    { std::vector<CPData> ret; CrossPoints(ret, e, to_do, swap); return ret; }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    bool CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const;
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    template <typename iter>
    std::vector<CPData> CrossPoints(iter from, iter last, bool closed = true, const Block *bb_o = nullptr, CPTasks to_do = {}, bool swap = false) const
    { std::vector<CPData> ret; CrossPoints(ret, from, last, closed, bb_o, to_do, swap); return ret; }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    bool CrossPoints(std::vector<CPData> &ret, const Path &p, bool p_closed = true, const Block *bb_p = nullptr, CPTasks to_do = {}, bool swap = false) const
    { return CrossPoints(ret, p.begin(), p.end(), p_closed, bb_p, to_do, swap); }
    /** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
    std::vector<CPData> CrossPoints(const Path &p, bool p_closed = true, const Block *bb_p = nullptr, CPTasks to_do = {}, bool swap = false) const
    { std::vector<CPData> ret; CrossPoints(ret, p, p_closed, bb_p, to_do, swap); return ret; }
    /** Calculates the crosspoints with a simplecontour. See @ref CrossPoints for details.*/
    std::vector<CPData> CrossPoints(const SimpleContour &s, CPTasks to_do = {}, bool swap = false) const
    { return CrossPoints(s.GetEdges(), true, &s.GetBoundingBox(), to_do, swap); }
    /** Returns a cut of the shape along an (infinite) line.
     *
     * Same as Cut(const XY &A, const XY &B), but also returns the coordinates of the positions
     * returned and a forward tangent point for them. In case the line cuts the shape
     * at a non-smooth vertex, then we take the tangent line of both edges of the vertex
     * average them and return a point from this middle line in the forward direction.
     *
     * @param [in] A One point on the line.
     * @param [in] B Another point on the line.
     * @param [out] from Values correspond to the position returned in `from`. `first` is the coordinate of the point, `second` is the forward tangent point.
     * @param [out] till Values correspond to the position returned in `till`. `first` is the coordinate of the point, `second` is the forward tangent point.
     * @return The `pos` value corresponding to the two outmost crosspoints. */
    Range CutWithTangent(const XY &A, const XY &B, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const;
    /** Returns a cut of the shape along an edge.
     *
     * Same as Cut(const Edge &s), but also returns the coordinates of the positions
     * returned and a forward tangent point for them. In case the edge cuts the shape
     * at a non-smooth vertex, then we take the tangent line of both edges of the vertex
     * average them and return a point from this middle line in the forward direction.
     *
     * @param [in] e The edge to cut with.
     * @param [out] from Values correspond to the position returned in `from`. `first` is the coordinate of the point, `second` is the forward tangent point.
     * @param [out] till Values correspond to the position returned in `till`. `first` is the coordinate of the point, `second` is the forward tangent point.
     * @return The `pos` value corresponding to the two outmost crosspoints. */
    Range CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const {Range ret = first.CutWithTangent(e, from, till); if (!further.IsEmpty()) ret += further.CutWithTangent(e,from, till); return ret;}
    /** Calculates the touchpoint of tangents drawn from a given point.
     *
     * Given the point `from` draw tangents to the shape (two can be drawn)
     * and calculate where these tangents touch the shape.
     * Tangent is anything touching the shape (at a vertex, at the middle of a curvy edge
     * or going along, in parallel with a full straight edge).
     * In this context the *clockwise tangent* is the one which is traversed from
     * `from` towards the shape touches the shape in the clockwise direction.
     * @param [in] from The point from which the tangents are drawn.
     * @param [out] clockwise The point where the clockwise tangent touches the shape.
     * @param [out] cclockwise The point where the counterclockwise tangent touches the shape.
     * @returns True if success, false if `from` is inside or on the shape. */
    bool TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const;
    /** Calculates the touchpoint of tangents drawn to touch two shapes.
     *
     * Given the two shapes, four such tangents can be drawn, here we focus on the two
     * outer ones, the ones that touch either both shapes clockwise or both of them
     * counterclockwise, but not mixed.
     * Tangent is anything touching the shape (at a vertex, at the middle of a curvy edge
     * or going along, in parallel with a full straight edge).
     * @param [in] from The other shape to the tangents are drawn.
     * @param [out] clockwise The points where the clockwise tangent touches our shape
     *                        (clockwise[0]) and `from` (clockwise[1]).
     * @param [out] cclockwise The points where the counterclockwise tangent touches our shape
     *                         (cclockwise[0]) and `from` (cclockwise[1]).
     * @returns True if success, false if `from` is inside us. */
    bool TangentFrom(const Contour &from, XY clockwise[2], XY cclockwise[2]) const;
    /** Creates a cross-section of the Contour along a vertical line.
     *
     * @param [in] x The x coordinate of the vertical line
     * @param section A DoubleMap containing true for points inside the shape, false for outside. May contain entries already, when calling this function. */
    void VerticalCrossSection(double x, DoubleMap<bool> &section) const {first.VerticalCrossSection(x, section); if (further.size()) further.VerticalCrossSection(x, section);}
    /** @} */ //Advanced information functions
    std::string Dump(bool precise) const;
    static Contour UnsafeMake(HoledSimpleContour&& fi, ContourList&& fu);
};

inline Contour operator + (const Contour &a, const Contour &b) {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         a, b);}
inline Contour operator * (const Contour &a, const Contour &b) {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_INTERSECT : Contour::NEGATIVE_INTERSECT, a, b);}
inline Contour operator - (const Contour &a, const Contour &b) {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         a, b.create_inverse_dont_check());}
inline Contour operator ^ (const Contour &a, const Contour &b) {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_XOR : Contour::NEGATIVE_XOR,             a, b);}
inline Contour operator + (const Contour &a, Contour &&b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         a, std::move(b));}
inline Contour operator * (const Contour &a, Contour &&b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_INTERSECT : Contour::NEGATIVE_INTERSECT, a, std::move(b));}
inline Contour operator - (const Contour &a, Contour &&b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         a, std::move(b.invert_dont_check()));}
inline Contour operator ^ (const Contour &a, Contour &&b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_XOR : Contour::NEGATIVE_XOR,             a, std::move(b));}
inline Contour operator + (Contour &&a, const Contour &b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         std::move(a), b);}
inline Contour operator * (Contour &&a, const Contour &b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_INTERSECT : Contour::NEGATIVE_INTERSECT, std::move(a), b);}
inline Contour operator - (Contour &&a, const Contour &b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         std::move(a), Contour(b).invert_dont_check());}
inline Contour operator ^ (Contour &&a, const Contour &b)      {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_XOR : Contour::NEGATIVE_XOR,             std::move(a), b);}
inline Contour operator + (Contour &&a, Contour &&b)           {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         std::move(a), std::move(b));}
inline Contour operator * (Contour &&a, Contour &&b)           {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_INTERSECT : Contour::NEGATIVE_INTERSECT, std::move(a), std::move(b));}
inline Contour operator - (Contour &&a, Contour &&b)           {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_UNION : Contour::NEGATIVE_UNION,         std::move(a), std::move(b.invert_dont_check()));}
inline Contour operator ^ (Contour &&a, Contour &&b)           {return Contour(a.GetClockWise() || b.GetClockWise() ? Contour::POSITIVE_XOR : Contour::NEGATIVE_XOR,             std::move(a), std::move(b));}



/** Calculate the distance of a list of shape pointers from a contour 'c' and merges it into 'dist_so_far'*/
template <typename iter> void Distance(iter first, iter last, const Contour &c, DistanceType &dist_so_far) noexcept
{
    if (first==last || c.IsEmpty()) return;
    for (unsigned u = 0; first!=last; ++first, ++u) {
        const double bbdist = first->GetBoundingBox().Distance(c.GetBoundingBox());
        if (dist_so_far.ConsiderBB(bbdist))
            first->Distance(c, dist_so_far);
        else
            dist_so_far.MergeInOut(bbdist);
        if (dist_so_far.IsZero())
            break;
    }
}


inline void ContourList::AppendToPath(Path &p) const
{
    for (auto &cwh : *this)
        cwh.AppendToPath(p);
}

inline Path ContourList::CreatePathFromSelected(bool keep(const Edge&)) const
{
    Path ret;
    for (auto &c : *this)
        ret.append(c.CreatePathFromSelected(keep));
    return ret;
}

inline void ContourList::SetVisible(bool visible) const noexcept
{
    for (auto &c : *this)
        c.SetVisible(visible);
}

inline void ContourList::SetInternalMark(bool imark) const noexcept
{
    for (auto &c : *this)
        c.SetInternalMark(imark);
}


inline void ContourList::SetMark(unsigned mark) const noexcept
{
    for (auto &c : *this)
        c.SetMark(mark);
}

inline void ContourList::Apply(Edge::Update u) const noexcept
{
    for (auto &c : *this)
        c.Apply(u);
}

inline bool ContourList::GetClockWise() const noexcept
{
    return size() ? begin()->GetClockWise() : true;
}

inline double ContourList::GetArea(bool consider_holes) const noexcept
{
    double ret = 0;
    for (auto i = begin(); i!=end(); i++)
        ret += i->GetArea(consider_holes);
    return ret;
}

inline double ContourList::GetCircumference(bool consider_holes, bool include_hidden) const noexcept
{
    double ret = 0;
    for (auto i = begin(); i!=end(); i++)
        ret += i->GetCircumference(consider_holes, include_hidden);
    return ret;
}

inline XY ContourList::CentroidUpscaled() const noexcept
{
    XY ret(0,0);
    for (auto i = begin(); i!=end(); i++)
        ret += i->CentroidUpscaled();
    return ret;
}


inline void ContourList::append(const HoledSimpleContour &p)
{
    if (p.IsEmpty()) return;
    //_ASSERT(size()==0 || GetClockWise() == p.GetClockWise()); These trigger when a Path is converted
    boundingBox += p.GetBoundingBox();
    push_back(p);
}

inline void ContourList::append(HoledSimpleContour &&p)
{
    if (p.IsEmpty()) return;
    //_ASSERT(size()==0 || GetClockWise() == p.GetClockWise()); These trigger when a Path is converted
    boundingBox += p.GetBoundingBox();
    push_back(std::move(p));
}

/** Remove the uth component, while keeping the bb updated. */
inline void ContourList::Remove(unsigned u)
{
    erase(begin()+u);
    boundingBox.MakeInvalid();
    for (const auto &c : *this)
        boundingBox += c.GetBoundingBox();
}

/** Remove edges shorter than 'tolerance'. We also combine subsequent edges
* that can be expressed as a single edge.
* @returns true if a change was made.*/
inline bool ContourList::Simplify(double tolerance)
{
    bool ret = false;
    for (auto &c : *this)
        ret |= c.Simplify(tolerance);
    return ret;
}


inline EPointRelationType ContourList::IsWithin(const XY &p) const
{
    EPointRelationType ret = boundingBox.IsWithin(p);
    if (ret==WI_OUTSIDE) return WI_OUTSIDE;
    for (auto i = begin(); i!=end(); i++)
        if (WI_OUTSIDE != (ret=i->IsWithin(p))) return ret; //we also return IN_HOLE
    return WI_OUTSIDE;
}

/** Returns the touchpoint of tangents drawn from a point to the list of shape.
*
* @param [in] p The point to draw the tangents from.
* @param [out] t1 The point where the first tangent touches any of the shapes.
* @param [out] t2 The point where the second tangent touches any of the the shapes.
* @returns The relation of the point to the shape. Meaningful tangents can only be drawn if this is WI_OUTSIDE.*/
inline EPointRelationType ContourList::Tangents(const XY &p, XY &t1, XY &t2) const
{
    EPointRelationType ret = boundingBox.IsWithin(p);
    if (ret==WI_OUTSIDE) return WI_OUTSIDE;
    for (auto i = begin(); i!=end(); i++)
        if (WI_OUTSIDE != (ret=i->Tangents(p, t1, t2))) return ret; //we also return IN_HOLE
    return WI_OUTSIDE;
}

/** Creates a cross-section of the list of shapes along a vertical line.
* @param [in] x The x coordinate of the vertical line
* @param section A DoubleMap containing true for points inside the shape, false for outside. May contain entries already.*/
inline void ContourList::VerticalCrossSection(double x, DoubleMap<bool> &section) const
{
    for (auto i=begin(); i!=end(); i++)
        i->VerticalCrossSection(x, section);
}


inline void ContourList::Shift(const XY &xy) noexcept
{
    for (auto &c : *this)
        c.Shift(xy);
    boundingBox.Shift(xy);
}

inline void ContourList::Scale(double sc) noexcept
{
    for (auto &c : *this)
        c.Scale(sc);
    boundingBox.Scale(sc);
}

inline void ContourList::Scale(const XY &sc) noexcept
{
    for (auto &c : *this)
        c.Scale(sc);
    boundingBox.Scale(sc);
}


inline void ContourList::Rotate(double cos, double sin) noexcept
{
    boundingBox.MakeInvalid();
    for (auto &c : *this) {
        c.Rotate(cos, sin);
        boundingBox += c.GetBoundingBox();
    }
}

inline void ContourList::RotateAround(const XY&center, double cos, double sin) noexcept
{
    boundingBox.MakeInvalid();
    for (auto &c : *this) {
        c.RotateAround(center, cos, sin);
        boundingBox += c.GetBoundingBox();
    }
}

/** Apply a non-inverting transformation */
inline void ContourList::NonInvertingTransform(const TRMatrix & M) noexcept
{
    _ASSERT(!M.IsFlipping());
    boundingBox.MakeInvalid();
    for (auto &c : *this) {
        c.NonInvertingTransform(M);
        boundingBox += c.GetBoundingBox();
    }
}

/** Create a transformed copy (using a non-inverting transformation) */
inline ContourList ContourList::CreateNonInvertingTransformed(const TRMatrix & M) const
{
    _ASSERT(!M.IsFlipping());
    ContourList ret;
    for (auto &c : *this)
        ret.append(c.CreateNonInvertingTransformed(M));
    return ret;
}

/** Apply an inverting transformation */
inline void ContourList::InvertingTransform(const TRMatrix & M) noexcept
{
    _ASSERT(M.IsFlipping());
    boundingBox.MakeInvalid();
    for (auto &c : *this) {
        c.InvertingTransform(M);
        boundingBox += c.GetBoundingBox();
    }
}

/** Create a transformed copy (using an inverting transformation) */
inline ContourList ContourList::CreateInvertingTransformed(const TRMatrix & M) const
{
    _ASSERT(M.IsFlipping());
    ContourList ret;
    for (auto &c : *this)
        ret.append(c.CreateInvertingTransformed(M));
    return ret;
}

/** Return the contiguous surfaces that have 'point' inside or on their edges.
 * If 'has_to_be_inside' is true, there can only be one such contiguous surface.
 * Returns true if we have added to the list.
 * if 'invert' is true, we are actually a hole, so we need to search in our holes.
 * The pointer returned is still owner by us.*/
inline bool ContourList::FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert,
                                           std::list<const SimpleContour *> &list) const
{
    if (!boundingBox.IsWithinBool(point)) return false;
    bool ret = false;
    for (auto &c : *this) {
        ret |= c.FindSimpleContour(point, has_to_be_inside, invert, list);
    }
    return ret;
}

/** Return the contiguous surfaces that have 'point' inside or on their edges.
 * If 'has_to_be_inside' is true, there can only be one such contiguous surface.
 * Returns true if we have added to the list.
 * if 'invert' is true, we are actually a hole, so we need to search in our holes.
 * The pointer returned is still owner by us - but it can be emptied via std::move() or swap().
 * However, in that case we become invalid. (especially bounding box)*/
inline bool ContourList::FindSimpleContour(const XY &point, bool has_to_be_inside, bool invert,
                                           std::list<SimpleContour *> &list)
{
    if (!boundingBox.IsWithinBool(point)) return false;
    bool ret = false;
    for (auto &c : *this) {
        ret |= c.FindSimpleContour(point, has_to_be_inside, invert, list);
    }
    return ret;
}

/** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
inline bool ContourList::CrossPoints(std::vector<CPData>& ret, const Path& p, bool p_closed, const Block *bb_p,
                                     CPTasks to_do, bool swap) const
{
    bool r = size()==1;
    for (auto &c : *this)
        r &= c.CrossPoints(ret, p, p_closed, bb_p, to_do, swap);
    return r;
}

/** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
template <typename iter>
inline bool ContourList::CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed, const Block *bb_o,
                                     CPTasks to_do, bool swap) const {
    bool r = size()==1;
    for (auto &c : *this)
        r &= c.CrossPoints(ret, from, last, closed, bb_o, to_do, swap);
    return r;
}




/** Determine the relative vertical distance between our shapes and another one.
 *
 * For detailed explanation and parameters, see
 * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
 */
inline double ContourList::OffsetBelow(const Contour &below, double &touchpoint, double offset) const
{
    if (below.further.size())
        offset = OffsetBelow(below.further, touchpoint, offset);
    return OffsetBelow(below.first.outline, touchpoint, offset);
}

inline double HoledSimpleContour::Distance(const XY &o, XY &ret) const
{
    const double d = outline.Distance(o, ret);
    if (d>=0) return d;
    XY tmp;
    const double dd = -holes.Distance(o, tmp);
    if (fabs(dd)<fabs(d)) {
        ret = tmp;
        return dd;
    }
    return d;
}


/** Calculates the distance between a point and us by finding our closest point and returns two tangent points.
* Same as Distance(const XY &o, XY &ret), but in addition we return two tangent points
* from the tangent of the shape at `ret`. See @ref contour for a description of tangents.
*
* @param [in] o The point to take the distance from.
* @param [out] ret We return the point on our contour closes to `o`.
* @param [out] t1 The forward tangent point.
* @param [out] t2 The backward tangent point.
* @return The distance, negative if `o` is inside us. `CONTOUR_INFINITY` if we are empty.*/
inline double HoledSimpleContour::DistanceWithTangents(const XY &o, XY &ret, XY &t1, XY &t2) const
{
    const double d = outline.DistanceWithTangents(o, ret, t1, t2);
    if (d>=0) return d;
    XY tmp, _1, _2;
    const double dd = -holes.DistanceWithTangents(o, tmp, _1, _2);
    if (fabs(dd)<fabs(d)) {
        ret = tmp;
        t1 = _1;
        t2 = _2;
        return dd;
    }
    return d;
}


/** Returns a cut of the list of shapes along an edge.
*
* Takes all crosspoints of a (finite length, possibly curvy) edge and the shapes
* and returns the two outermost. We return `pos` values corresponding to the edge,
* thus both values are between 0 and 1. It is possible that a single value is
* returned if the edge touches one shape or if its start is inside one shape, but
* its end is outside. If the edge does not cross nor touch any shape, an invalid
* range is returned.*/
inline Range ContourList::Cut(const Edge &e) const
{
    Range ret;
    ret.MakeInvalid();
    for (auto i = begin(); i!=end(); i++)
        ret += i->Cut(e);
    return ret;
}

/** Returns a cut of the shapes along an edge.
*
* Same as Cut(const Edge &s), but also returns the coordinates of the positions
* returned and a forward tangent point for them. In case the edge cuts the shape
* at a non-smooth vertex, then we take the tangent line of both edges of the vertex
* average them and return a point from this middle line in the forward direction.
*
* @param [in] e The edge to cut with.
* @param [out] from Values correspond to the position returned in `from`. `first` is the coordinate of the point, `second` is the forward tangent point.
* @param [out] till Values correspond to the position returned in `till`. `first` is the coordinate of the point, `second` is the forward tangent point.
* @return The `pos` value corresponding to the two outmost crosspoints.*/
inline Range ContourList::CutWithTangent(const Edge &e, std::pair<XY, XY> &from, std::pair<XY, XY> &till) const
{
    Range ret;
    ret.MakeInvalid();
    std::pair<XY, XY> f, t;
    for (auto i = begin(); i!=end(); i++) {
        const Range r = i->CutWithTangent(e, f, t);
        if (ret.from > r.from) {
            ret.from = r.from;
            from = f;
        }
        if (ret.till < r.till) {
            ret.till = r.till;
            till = t;
        }
    }
    return ret;
}

/** Creates a cross-section (or cut) of the shapes along a straight line.
*
* We return all crosspoints of an infinite line specified by `A` and `B`.
* We return them in `pos` terms, that is, at `A` we return 0, at `B` we return 1 and
* linearly intrapolate and extrapolate between them and outside, respectively.
*
* @param [in] A One point of the infinite line.
* @param [in] B Another point of the infinite line.
* @param [out] map A DoubleMap containing true for points inside the shapes, false for outside.*/
inline void ContourList::Cut(const XY &A, const XY &B, DoubleMap<bool> &map) const
{
    for (auto &cwh : *this)
        cwh.Cut(A, B, map);
}

/** Calculates the crosspoints with another path. See @ref CrossPoints for details.*/
inline bool ContourList::CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do, bool swap) const
{
    bool r = size()==1;
    for (auto &c : *this)
        r &= c.CrossPoints(ret, e, to_do, swap);
    return r;
}



/** Calculates the touchpoint of tangents drawn from a given point.
*
* Given the point `from` draw tangents to the shapes (two can be drawn)
* and calculate where these tangents touch one the shapes.
* Tangent is anything touching a shape (at a vertex, at the middle of a curvy edge
* or going along, in parallel with a full straight edge), but not crossing any
* other shape.
* In this context the *clockwise tangent* is the one which is traversed from
* `from` towards the shape touches the given shape in the clockwise direction.
* @param [in] from The point from which the tangents are drawn.
* @param [out] clockwise The point where the clockwise tangent touches the shape.
* @param [out] cclockwise The point where the counterclockwise tangent touches the shape.
* @returns True if success, false if `from` is inside or on the shape.
*/
inline bool ContourList::TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const
{
    if (size()==0) return false;
    if (!begin()->outline.TangentFrom(from, clockwise, cclockwise))
        return false;
    XY c, cc;
    for (auto i=++begin(); i!=end(); i++) {
        if (!i->outline.TangentFrom(from, c, cc)) return false;
        clockwise =  minmax_clockwise(from, clockwise,  c,  true);
        cclockwise = minmax_clockwise(from, cclockwise, cc, false);
    }
    return true;
}


///////HoledSimpleContour///////////////////////////////////////

/** Return the contiguous surfaces that have 'point' inside or on their edges.
 * If 'has_to_be_inside' is true, there can only be one such contiguous surface.
 * Returns true if we have added to the list.
 * if 'invert' is true, we are actually a hole, so we need to search in our holes.
 * The pointer returned is still owner by us.*/
inline bool HoledSimpleContour::FindSimpleContour(const XY & point, bool has_to_be_inside, bool invert,
                                                std::list<const SimpleContour *> &list) const
{
    if (!invert) {
        const EPointRelationType t = outline.IsWithin(point);
        if (t==EPointRelationType::WI_OUTSIDE || (has_to_be_inside && t!=EPointRelationType::WI_INSIDE))
            return false;
    }
    //If we have not found it in a hole, we have to insert our outline
    //XXX: if a surface inside a hole touches the outline at 'point' we will miss
    //adding the outline...
    if (holes.FindSimpleContour(point, has_to_be_inside, !invert, list))
        return true;
    list.push_back(&outline);
    return true;
}

/** Return the contiguous surfaces that have 'point' inside or on their edges.
 * If 'has_to_be_inside' is true, there can only be one such contiguous surface.
 * Returns true if we have added to the list.
 * if 'invert' is true, we are actually a hole, so we need to search in our holes.
 * The pointer returned is still owner by us - but it can be emptied via std::move() or swap().
 * However, in that case we become invalid. (especially bounding box)*/
inline bool HoledSimpleContour::FindSimpleContour(const XY & point, bool has_to_be_inside, bool invert,
                                                std::list<SimpleContour *> &list)
{
    if (!invert) {
        const EPointRelationType t = outline.IsWithin(point);
        if (t==EPointRelationType::WI_OUTSIDE || (has_to_be_inside && t!=EPointRelationType::WI_INSIDE))
            return false;
    }
    //If we have not found it in a hole, we have to insert our outline
    //XXX: if a surface inside a hole touches the outline at 'point' we will miss
    //adding the outline...
    if (!holes.FindSimpleContour(point, has_to_be_inside, !invert, list))
        list.push_back(&outline);
    return true;
}


/** Returns the relation of a point to us. Ignores clockwiseness:
 * four counterclockwise outlines we return WI_INSIDE if the point
 * is inside the finite space they enclose the same way as if they
 * were clockwise.*/
inline EPointRelationType HoledSimpleContour::IsWithin(const XY &p) const noexcept
{
    EPointRelationType ret = outline.IsWithin(p);
    if (ret!=WI_INSIDE || holes.size()==0) return ret;
    ret = holes.IsWithin(p);
    switch (ret) {
    case WI_INSIDE: return WI_IN_HOLE;
    case WI_IN_HOLE:
    case WI_OUTSIDE: return WI_INSIDE;
    default: return ret;
    }
}

inline std::array<CPData, 2> HoledSimpleContour::CutEx(const Edge &e) const
{
    auto a = outline.CutEx(e);
    std::array<CPData, 2> ret = {a[0], a[1]};
    ret[1].contour_me = ret[0].contour_me = &outline;
    return ret;
}

inline bool HoledSimpleContour::CrossPoints(std::vector<CPData> &ret, const Edge &e,
                                            CPTasks to_do, bool swap) const
{
    bool r = outline.CrossPoints(ret, e, to_do, swap) && holes.empty();
    holes.CrossPoints(ret, e, to_do, swap);
    return r;
}


/** Calculate the relation of a point and the shape and return tangents, if the point is on the shape.
*
* @param [in] p The point in question.
* @param [out] t1 the forward tangent from 'p', undefined if p is not on the circumference.
* @param [out] t2 the backward tangent from 'p', undefined if p is not on the circumference.
* @returns The relation. Note that for counterclockwise 'inside' is the same as if the
contour were clockwise, that is in the limited space enclosed.
*/
inline EPointRelationType HoledSimpleContour::Tangents(const XY &p, XY &t1, XY &t2) const
{
    PathPos pos;
    EPointRelationType ret = outline.IsWithin(p, &pos.edge, &pos.pos);
    if (ret==WI_ON_EDGE || ret==WI_ON_VERTEX) {
        t1 = outline.PrevTangentPoint(pos);
        t2 = outline.NextTangentPoint(pos);
        return ret;
    }
    if (ret!=WI_INSIDE || holes.size()==0) return ret;
    ret = holes.Tangents(p, t1, t2);
    switch (ret) {
    case WI_INSIDE: return WI_IN_HOLE;
    case WI_IN_HOLE:
    case WI_OUTSIDE: return WI_INSIDE;
    default: return ret;
    }
}


///////Contour///////////////////////////////////////

template<typename Edgeish>
Contour & Contour::assign(std::span<const Edgeish> v, ECloseType close, EForceClockwise force_clockwise, bool winding)
{
    Path ptmp;
    for (auto &e: ConvertToClosed<Path>(v.begin(), v.end(), close))
        ptmp.append(std::move(e));
    Contour ctmp;
    ctmp.assign_dont_check(std::move(ptmp));
    Untangle(std::move(ctmp), force_clockwise, winding);
    return *this;
}


/** Return the contiguous surfaces that have 'point' inside or on their edges.
 * If 'has_to_be_inside' is true, there can only be one such contiguous surface.
 * The pointers returned is still owner by us.*/
inline std::list<const SimpleContour *> Contour::FindSimpleContour(const XY &point, bool has_to_be_inside) const
{
    std::list<const SimpleContour *> ret;
    first.FindSimpleContour(point, has_to_be_inside, false, ret);
    further.FindSimpleContour(point, has_to_be_inside, false, ret);
    return ret;
}

/** Return one of the contiguous surfaces that have 'point' inside or on their edges.
* If 'has_to_be_inside' is true, there can only be one such contiguous surface.
* Returns nullptr if 'point' is not inside us.
* The pointers returned is still owner by us - but it can be emptied via std::move() or swap().
* However, in that case we become invalid. (especially bounding box)*/
inline std::list<SimpleContour *> Contour::FindSimpleContour(const XY &point, bool has_to_be_inside)
{
    std::list<SimpleContour *> ret;
    first.FindSimpleContour(point, has_to_be_inside, false, ret);
    further.FindSimpleContour(point, has_to_be_inside, false, ret);
    return ret;
}

/** Determine the relative vertical distance between two shapes.
 *
 * For detailed explanation and parameters, see
 * Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset).
 */
inline double Contour::OffsetBelow(const SimpleContour &below, double &touchpoint, double offset) const
{
    if (further.size())
        offset = further.OffsetBelow(below, touchpoint, offset);
    return first.outline.OffsetBelow(below, touchpoint, offset);
}

/** Determine the relative vertical distance between two shapes.
 *
 * Assuming we are higher than `below` (lower y coordinates), how much can `below`
 * be shifted upwards before it collides with us (called *offset*).
 * If `below` and us do not share any points with the same x coordinate, the offset is `CONTOUR_INFINITY`
 * (`o` can pass infinitely up besides us without collision).
 * If `o` and us share such points, but `o` is above us, the offset is negative, since
 * `o` needs to be pulled down to be below us. This is not viewed as an error case but as normal operation.
 *
 * This function is constructed to allow merging of the result of multiple OffsetBelow calls. Assume two
 * Contours are made of multiple SimpleContours. In this case we call this function multiple times, pairwise
 * for constituent SimpleContours. The smallest of the returned offsets (and the touchpoint associated)
 * will be the ultimate offset between the two Contours. Therefore we can supply a previous `offset` value
 * and this function will return the offset between the two SimpleContours only if it is smaller than the
 * `offset` parameter. Also, `touchpoint` is changed only in this case.
 *
 * @param [in] below The other shape below us.
 * @param [out] touchpoint After moving `below` up, we return the point on us where we touched. However,
 *                         if the offset determined for us and `below` as described above is not smaller than
 *                         the `offset` parameter, we leave `touchpoint` intact.
 * @param [in] offset The offset calculated from some previous operation. Set to `CONTOUR_INFINITY` if no such op.
 * @return The smaller of `offset` and how much `below` was moved up before it hit us.
 */
inline double Contour::OffsetBelow(const Contour &below, double &touchpoint, double offset) const
{
    if (further.size())
        offset = further.OffsetBelow(below, touchpoint, offset);
    if (below.further.size())
        for (auto i = below.further.begin(); i!=below.further.end(); i++)
            offset = OffsetBelow(i->Outline(), touchpoint, offset);
    return first.outline.OffsetBelow(below.first.outline, touchpoint, offset);
}

inline Contour &Contour::Expand(double gap, EExpandType et4pos, EExpandType et4neg,
                                double miter_limit_positive, double miter_limit_negative)
{
    if (!gap) return *this;
    Contour res;
    Expand(et4pos, et4neg, gap, res, miter_limit_positive, miter_limit_negative);
    swap(res);
    return *this;
}

inline Contour Contour::CreateExpand(double gap, EExpandType et4pos, EExpandType et4neg,
                                     double miter_limit_positive, double miter_limit_negative) const
{
    if (!gap) return *this;
    Contour res;
    Expand(et4pos, et4neg, gap, res, miter_limit_positive, miter_limit_negative);
    return res;
}

inline Contour& Contour::Expand2D(const XY &gap)
{
    if (test_zero(gap.x) && test_zero(gap.y)) return *this;
    return operator =(CreateExpand2D(gap));
}



inline void Contour::Distance(const Contour &c, DistanceType &dist_so_far) const
{
    if (IsEmpty() || c.IsEmpty() || dist_so_far.IsZero()) return;
    first.Distance(c.first, dist_so_far);
    if (dist_so_far.IsZero()) return;
    if (!c.further.IsEmpty()) {
        dist_so_far.SwapPoints();
        c.further.Distance(first, dist_so_far);
        if (dist_so_far.IsZero()) return;
        dist_so_far.SwapPoints();
    }
    if (!further.IsEmpty()) {
        further.Distance(c.first, dist_so_far);
        if (dist_so_far.IsZero()) return;
    }
    if (!c.further.IsEmpty() && !further.IsEmpty())
        further.Distance(c.further, dist_so_far);
}


inline std::vector<CPData> Contour::CutExAll(const XY &A, const XY &B) const
{
    std::vector<CPData> ret;
    auto p = GetBoundingBox().CutEx(A, B);
    if (!p) return ret;
    const Edge e(p.value()[0], p.value()[1]);
    CrossPoints(ret, e);
    //Now calculate pos_other values from e.start->e.end back to A->B
    for (auto &e: ret)
        e.other.pos = get_pos_on_segment(A, B, e.xy);
    return ret;
}

inline std::vector<CPData> Contour::CutExAll2(const XY &A, const XY &B) const
{
    std::vector<CPData> tmp, ret = CutExAll(A, B);
    if (ret.size()<2) return ret;
    if (ret.size()==2) {
        if (ret[0].other.pos>ret[1].other.pos)
            std::swap(ret[0], ret[1]);
        return ret;
    }
    //find outer ones
    auto i = std::minmax_element(ret.begin(), ret.end(),
                                 [](const CPData &a, const CPData &b)
                                 {return a.other.pos<b.other.pos; });
    tmp.reserve(2);
    tmp.push_back(*i.first);
    tmp.push_back(*i.second);
    tmp.swap(ret);
    return ret;
}

inline bool Contour::CrossPoints(std::vector<CPData> &ret, const Edge &e, CPTasks to_do, bool swap) const
{
    bool r = first.CrossPoints(ret, e, to_do, swap) && further.empty();
    further.CrossPoints(ret, e, to_do, swap);
    return r;
}

template <typename iter>
inline bool Contour::CrossPoints(std::vector<CPData> &ret, iter from, iter last, bool closed, const Block *bb_o,
                                 CPTasks to_do, bool swap) const {
    bool r = first.CrossPoints(ret, from, last, closed, bb_o, to_do, swap) && further.empty();
    further.CrossPoints(ret, from, last, closed, bb_o, to_do, swap);
    return r;
}

inline bool Contour::TangentFrom(const XY &from, XY &clockwise, XY &cclockwise) const
{
    if (first.outline.TangentFrom(from, clockwise, cclockwise)) {
        XY c, cc;
        if (further.TangentFrom(from, c, cc)) {
            clockwise =  minmax_clockwise(from, clockwise,  c,  true);
            cclockwise = minmax_clockwise(from, cclockwise, cc, false);
        }
        return true;
    }
    return false;
}


template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(const SimpleContour &o) : EdgeVector(o.GetEdges()) {}
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(SimpleContour &&o) : EdgeVector(std::move(o.edges)) {}
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(const ContourList &o) { for (auto &c: o) append(c); }
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(const HoledSimpleContour &o) : EdgeVector(o.outline) { for (auto &c: o.holes) append(c); }
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(HoledSimpleContour &&o) : EdgeVector(std::move(o.outline)) { for (auto &c: o.holes) append(c); }
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(const Contour &o) :EdgeVector(o.first) { for (auto &c: o.further) append(c); }
template <typename Edgeish>
EdgeVector<Edgeish>::EdgeVector(Contour &&o) : EdgeVector(std::move(o.first)) { for (auto &c: o.further) append(c); }


template <typename Edgeish>
std::vector<CPData> EdgeVector<Edgeish>::CrossPoints(bool me_closed, const Block *bb_o, const Contour &o,
                                                     CPTasks to_do, bool swap) const {
    std::vector<CPData> ret;
    o.first.CrossPoints(ret, *this, me_closed, bb_o, to_do, !swap);
    o.further.CrossPoints(ret, *this, me_closed, bb_o, to_do, !swap);
    return ret;
}

/** Creates a contour that is covered by the path, when it is moved left and right
 * by offset1 and offset2 and closed. We assume offset1>=0 and offset2<=0.
 * Line cap is added as per AppendLineCap(), but if you specify EXPAND_MITER,
 * we still add like EXPAND_MITER_SQUARE, since we need a continuous, closed
 * contour at the end.
 * Lineap added will be 'mark'-ed.*/
template <typename Edgeish>
Contour EdgeVector<Edgeish>::SimpleWidenAsymmetric(double offset1, double offset2, EExpandType line_cap, EExpandType line_join, double miter_limit) const
{
    _ASSERT(offset1>=0 && offset2<=0);
    Contour ret;
    if (line_cap == EXPAND_MITER) line_cap = EXPAND_MITER_SQUARE;
    //make path contain Edge:s not Edgeish:es
    auto paths = ::contour::Split<Path>(begin(), end(), false);
    for (auto &p : paths) {
        p.Simplify(0.1, true);
        Path pp1 = p.CreateSimpleExpand(offset1, true, line_join, miter_limit);
        if (pp1.size()==0) continue;
        Path pp2 = p.CreateSimpleExpand(offset2, true, line_join, miter_limit);
        if (pp2.size()==0) continue;
        if (p.IsClosed()) {
            auto c = Contour(pp1.append(pp2), ECloseType::CLOSE_ALL_CONNECTED, EForceClockwise::DONT);
            if (c.GetClockWise()) ret += c;
            else ret -= c;
            //ret -= Contour(pp2, ECloseType::CLOSE_ALL_CONNECTED);
            continue;
        }
        pp2.Invert();
        for (auto i = AppendLineCap(pp1, line_cap, pp1.back().GetEnd(), pp2.front().GetStart(),
                               pp1.back().IsVisible() && pp2.front().IsVisible());
             i!= pp1.end(); i++)
            i->SetInternalMark();
        pp1.append(std::move(pp2));
        for (auto i = AppendLineCap(pp1, line_cap, pp1.back().GetEnd(), pp1.front().GetStart(),
                      pp1.back().IsVisible() && pp1.front().IsVisible());
             i!= pp1.end(); i++)
            i->SetInternalMark();
        ret += Contour(pp1, true, ECloseType::CLOSE_ALL_CONNECTED);
    }
    return ret;
}

template <typename Edgeish>
Contour EdgeVector<Edgeish>::SimpleWiden(double width, EExpandType line_cap, EExpandType line_join, double miter_limit) const
{
    return width>0 ? SimpleWidenAsymmetric(width/2, -width/2, line_cap, line_join, miter_limit) : Contour();
}

/** Computes a contour of a shape resulting from drawing 'path' with a variable-width pen.
 * The pen width may grow or shrink linearly. Used to draw bent arrowheads.
 * The starting and ending side of the line (where line cap would come) is returned invisible.
 * @param [in] pos The position where we start the drawing.
 * @param [in] gap1 The pen width at the start of the drawing: 'pos'. Must be nonnegative.
 * @param [in] gap2 The pen width at the end of the drawing: 'length' distance from 'pos'. Must be nonnegative.
 * @param [in] length How long segment of 'path' do we draw.
 * @param [in] trim If true, we stop if the path is too short. If false, we extend it with a tangent
 *                  and widen that.*/
template <typename Edgeish>
Contour EdgeVector<Edgeish>::LinearWiden(const PathPos &pos, double gap1, double gap2, double length,
                                 bool trim) const
{
    const double g[2] = {gap1, gap2};
    return LinearWiden(pos, 1, g, &length, trim);
}



/** Makes the path double-line.
 * Crossings are drawn nice, such as:
 @verbatim
                                          +--------+
            +-----+                       |  +--+  |
            |     |                       |  |  |  |
  This: ----+-----+   becomes this:   ----+  +--+  |
            |                         ----+  +-----+
            |                             |  |
 @endverbatim
 * @param [in] offset The distance from the midline to the midline of the double lines.
 *                    The two double lines will be twice this far apart.
 * @param [in] line_cap The type of line cap we add, EXPAND_MITER if none. (See AppendLineCap())
 * @param [in] line_join The type of line join to apply
 * @param [in] miter_limit The max length of miters for the EXPAND_MITER line join
 * @returns the resulting path (to stroke), which is not actually a contour. You need to apply
 *          SimpleWiden() in addition to create a fillable surface.*/
template <typename Edgeish>
EdgeVector<Edge> EdgeVector<Edgeish>::DoubleWiden(double offset, EExpandType line_cap, EExpandType line_join, double miter_limit) const
{
    //create a contour, whose outer edges will be the result path
    EdgeVector<Edge> ret = SimpleWiden(offset*2, line_cap, line_join, miter_limit);
    //take away any line cap, we added
    if (line_cap==EXPAND_MITER)
        ret.resize(std::remove_if(ret.begin(), ret.end(), [](const auto &e) {return e.IsInternalMarked(); })-ret.begin());
    return ret;
}



/** Computes a contour of a shape resulting from drawing 'path' with a variable-width pen.
 * The pen width may vary in connected linear segments and may be different at the left and right
 * side of the path.
 * Used to draw bent arrowheads.
 * The starting and ending side of the line (where line cap would come) is returned invisible.
 * @param [in] pos The position where we start the drawing.
 * @param [in] num The number of segments
 * @param [in] gap_left The pen widths on at the start/end of each segment.  Thus the first segment
 *                      will be gap_left[0]->gap_left[1], the second gap_left[1]->gap_left[2], etc.
 *                      The pen width varies linearly within a segment.
 *                      There must be num+1 values in this array.
 *                      In case of negative values their absolute value is taken.
 *                      This is the pen width on the left side of the path as viewed by our walk.
 *                      If length is positive, this is the left side of the path viewed towards
 *                      the end of the path. If length is negative, it is left when looking towards
 *                      the start of the path.
 * @param [in] gap_right The pen width on the right side at the start/end of each segment.
 * @param [in] length The length of each segment as measured on the path.It may be negative
 *                    to effect a walk backwards along path from 'pos'. Note that all 'length'
 *                    values must be of the same sign. Zero-length segments are ignored
 *                    including their 'gap' - thus it is not possible to have jumps in pen
 *                    width. There must be 'num' values in this array.
 * @param [in] update_left The mark and visible fields of the edges of original path are kept and copied
 *                         to the extended path. This field can be used to dictate what changes shall
 *                         be applied to the left side. By default, none.
 * @param [in] update_right The mark and visible fields of the edges of original path are kept and copied
 *                          to the extended path. This field can be used to dictate what changes shall
 *                          be applied to the right side. By default, none.
 * @param [in] trim If true, we stop if the path is too short. If false, we extend it with a tangent
 *                  and widen that.*/
template <typename Edgeish>
Contour EdgeVector<Edgeish>::LinearWidenAsymetric(const PathPos &pos, unsigned num,
                                   const double * gap_left, const double * gap_right, const double * length,
                                   Edge::Update update_left, Edge::Update update_right,
                                   bool trim) const
{
    Contour ret;
    if (!IsValidPos(pos) || num==0) //Note pos is always invalid if we are empty
        return ret;
    struct LocSegment
    {
        Edge edge;
        double gap1[2];
        double gap2[2];
        double cos[2];
        double sin[2];
    };
    std::vector<LocSegment> lsegments;
    lsegments.reserve(num);
    double running_gap[2] = {fabs(*gap_left), -fabs(*gap_right)}; //right gap is negative!!

    PathPos start_pos(pos);
    for (unsigned u = 0; u<num; u++) {
        if (length[u]==0) continue;
        _ASSERT((length[u]>0) == (length[0]>0)); //fails if segments[0].length==0

        //determine angles
        const double dist[2] = {sqrt((fabs(gap_left[u+1])-running_gap[0])*(fabs(gap_left[u+1])-running_gap[0]) + length[u]*length[u]),
                                sqrt((fabs(gap_right[u+1])-running_gap[1])*(fabs(gap_right[u+1])-running_gap[1]) + length[u]*length[u])};
        const double cos[2] = {fabs(length[u])/dist[0], fabs(length[u])/dist[1]};
        const double sin[2] = {(fabs(gap_left[u+1])-running_gap[0])/dist[0],
                               -(fabs(gap_right[u+1])+running_gap[1])/dist[1]};
        //create a path of 'length' len that we will expand
        PathPos end_pos(start_pos);
        const double remainder_len = MovePos(end_pos, length[u]);
        Path p;
        if (fabs(remainder_len) < fabs(length[u])) {
            //we could take at least some from path before hitting its end
            p = CopyPart(start_pos, end_pos);
            if (remainder_len && !trim) {
                //path not long enough: add linear extension
                p.LinearExtend(fabs(remainder_len), true);
            }
        } else if (trim) {
            //we could not take anything from path - we are done
            break;
        } else {
            //we could not take anything from path
            _ASSERT(p.size()==0);
            //continue adding linear segments
            //even start_pos is beyond the path (may be its very end)
            const XY start = lsegments.size() ? lsegments.back().edge.GetEnd() : GetPoint(end_pos);
            XY dir = GetTangent(end_pos, length[u]>0, false) - GetPoint(end_pos);
            p = {Edge(start, start+dir.Normalize()*fabs(length[u]))};
        }
        const double increment[2] = {(fabs(gap_left[u+1])-running_gap[0])/fabs(length[u]),
                                     -(fabs(gap_right[u+1])+running_gap[1])/fabs(length[u])};
        //walk through the edges - copy relevant data
        for (auto &e : p) {
            const double elen = e.GetLength();
            lsegments.emplace_back();
            lsegments.back().edge = e;
            for (unsigned v = 0; v<2; v++) {
                lsegments.back().gap1[v] = running_gap[v];
                lsegments.back().gap2[v] = (running_gap[v] += increment[v]*elen);
                lsegments.back().cos[v] = cos[v];
                lsegments.back().sin[v] = sin[v];
            }
        }
        if (remainder_len==0 || !trim) {
            lsegments.back().gap2[0] = fabs(gap_left[u+1]); //snap to precise value
            lsegments.back().gap2[1] = -fabs(gap_right[u+1]); //snap to precise value
        }
        running_gap[0] = fabs(gap_left[u+1]);
        running_gap[1] = -fabs(gap_right[u+1]);
        start_pos = end_pos;
    }

    EdgeVector<Edge> res[2]; //the left and the right side
    XY prev_fw_tangent[2];  //the (true) forward tangents of the last segment
    for (auto s = lsegments.begin(); s!=lsegments.end(); s++) {
        const unsigned bending = [&] {//detemine bending
            if (s==lsegments.begin()) return 0;
            const auto prev_segment = std::prev(s);
            switch (triangle_dir(prev_segment->edge.GetEnd(), prev_segment->edge.NextTangentPoint(1),
                                 s->edge.NextTangentPoint(0))) {
            default: _ASSERT(0); FALLTHROUGH;
            case ETriangleDirType::B_EQUAL_C:
            case ETriangleDirType::IN_LINE:          return 2; //no bend
            case ETriangleDirType::CLOCKWISE:        return 0; //bend to the right, index [0] bends away
            case ETriangleDirType::COUNTERCLOCKWISE: return 1; //bend to the left. index [1] bends away
            }
        }();

        //see if we will bend with the next edge and on which side
        //so we know what to store in new_prev and new_prev_fw_tangent
        XY new_prev_fw_tangent[2];
        XY next_bw_tangent[2];

        EdgeList<Edge> local_res[2];
        for (unsigned v = 0; v<2; v++) {
            if (test_zero(s->gap1[v]) && test_zero(s->gap2[v])) {
                local_res[v].push_back(s->edge);
            } else {
                if (!s->edge.CreateLinearExpand(s->gap1[v], s->gap2[v], s->cos[v], s->sin[v], local_res[v],
                                                next_bw_tangent[v], new_prev_fw_tangent[v], nullptr))
                                                //if edge radius is too short, replace with straight line
                    local_res[v].emplace_back(s->edge.GetStart(), s->edge.GetEnd());
                //for gap==0 snap to original start/end
                else if (test_zero(s->gap1[v]))
                    local_res[v].front().SetStartOnly(s->edge.GetStart());
                else if (test_zero(s->gap2[v]))
                    local_res[v].back().SetEndOnly(s->edge.GetEnd());
            }
            //now we have both sides of this segment in local_res[]
            if (s!=lsegments.begin()) { //if we are the first segment - just copy below
                if (bending==2) {
                    //If we do not bend, align endpoints to the previous one
                    local_res[v].front().SetStartOnly(res[v].back().GetEnd());
                    res[v].append(std::move(local_res[v]));
                } else {
                    //on the side we bend less than 180 degrees, see if the linear extension of the last and
                    //the new sides cross somewhere
                    //first see if the new and old segments actually cross - we take the cp that is closest to
                    // the end of the existing segments 'res'
                    XY r[9];
                    double prev_pos[9], next_pos[9];
                    unsigned n = 0;
                    EdgeVector<Edge>::reverse_iterator prev_i;
                    EdgeList<Edge>::iterator next_i;
                    for (prev_i = res[v].rbegin(); !(prev_i==res[v].rend()) && n==0; prev_i++) {
                        for (next_i = local_res[v].begin(); next_i!=local_res[v].end() && n==0; next_i++) {
                            n = prev_i->Crossing(*next_i, false, r, prev_pos, next_pos);
                            if (n) break;
                        }
                        if (n) break;
                    }
                    if (n) {
                        //prev_i and next_i have been stepped
                        //find cp closest to the end of 'prev_i', with the largest 'prev_pos'
                        int cp = 0;
                        for (unsigned c = 1; c<n; c++)
                            if (prev_pos[c] > prev_pos[cp])
                                cp = c;
                        //OK, trim res and local_res
                        res[v].erase(prev_i.base(), res[v].end());
                        local_res[v].erase(local_res[v].begin(), next_i);
                        //trim the actual edges
                        res[v].back().SetEndIgn(r[cp], prev_pos[cp]);
                        local_res[v].front().SetStartIgn(r[cp], next_pos[cp]);
                    } else {
                        //OK, res does not cross local_res
                        //let us see if the linear extension of 'res' crosses 'local_res'
                        for (next_i = local_res[v].begin(); next_i!=local_res[v].end() && n==0; next_i++) {
                            n = next_i->Crossing(res[v].back().GetEnd(), prev_fw_tangent[v], prev_pos, next_pos);
                            if (n) break;
                        }
                        //find cp closest to the end of 'res', but after it: smallest nonnegative 'next_pos'
                        int cp = 0;
                        for (unsigned c = 1; c<n; c++)
                            if (next_pos[c] < next_pos[cp] && next_pos[c]>0)
                                cp = c;
                        if (n && next_pos[cp]>0) {
                            //if we have a good cp, trim 'local_res' and prepend a linear segment
                            local_res[v].erase(local_res[v].begin(), next_i);
                            local_res[v].front().SetStart(prev_pos[cp]);
                        } else {
                            //OK, the linear extension of 'res' does not cross 'local_res'
                            //try vice versa
                            n = 0;
                            for (prev_i = res[v].rbegin(); !(prev_i==res[v].rend()); prev_i++) {
                                n = prev_i->Crossing(local_res[v].front().GetStart(), next_bw_tangent[v], prev_pos, next_pos);
                                if (n) break;
                            }
                            int cp = 0;
                            for (unsigned c = 1; c<n; c++)
                                if (next_pos[c] < next_pos[cp] && next_pos[c]>0)
                                    cp = c;
                            if (n && next_pos[cp]>0) {
                                //if we have a good cp, trim 'res' and append a linear segment
                                res[v].erase(prev_i.base(), res[v].end());
                                res[v].back().SetEnd(prev_pos[cp]);
                            } else if (bending == v) {
                                // Bending away - only add a linear segment to connect below
                            } else {
                                //side of bending with less than 180 degrees
                                //we insert a segment to the original end
                                XY upcoming[] = {
                                    res[v].back().GetEnd(),
                                    s->edge.GetStart(),
                                    local_res[v].front().GetStart()
                                };
                                res[v].append(upcoming);
                            }
                        }
                    }
                }
            }
            //now copy 'local_res' after 'res', ensuring they connect
            res[v].append(std::move(local_res[v]), true);
            prev_fw_tangent[v] = new_prev_fw_tangent[v];
        }
    }
    //Now we have the entire left side in 'res[0]' and the entire right side in 'res[1]'
    //Apply user margins and visibility
    if (update_left.DoesSomething())
        for (const Edge &e : res[0])
            e.Apply(update_left);
    if (update_right.DoesSomething())
        for (const Edge &e : res[1])
            e.Apply(update_right);
    //convert to a contour
    Path pres;
    pres.append(res[0]);
    pres.emplace_back(res[0].back().GetEnd(), res[1].back().GetEnd(), false);
    res[1].Invert();
    pres.append(res[1]);
    pres.emplace_back(res[1].back().GetEnd(), res[0].front().GetStart(), false);
    pres.RemoveDots(1e-3);
    ret.assign(std::move(pres), ECloseType::CLOSE_ALL_CONNECTED);
    return ret;
}


/** Computes a contour of a shape resulting from drawing 'path' with a variable-width pen.
 * The pen width may vary in connected linear segments.
 * Used to draw bent arrowheads.
 * The starting and ending side of the line (where line cap would come) is returned invisible.
 * @param [in] pos The position where we start the drawing.
 * @param [in] num The number of segments
 * @param [in] gap The pen width at the start/end of each segment. Thus the first segment
 *                 will be gap[0]->gap[1], the second gap[1]->gap2[2], etc.
 *                 The pen width varies linearly within a segment.
 *                 There must be num+1 values in this array.
 *                 In case of negative values their absolute value is taken.
 * @param [in] length The length of each segment as measured on the path.It may be negative
 *                    to effect a walk backwards along path from 'pos'. Note that all 'length'
 *                    values must be of the same sign. Zero-length segments are ignored
 *                    including their 'gap' - thus it is not possible to have jumps in pen
 *                    width. There must be 'num' values in this array.
 * @param [in] trim If true, we stop if the path is too short. If false, we extend it with a tangent
 *                  and widen that.*/
template <typename Edgeish>
Contour EdgeVector<Edgeish>::LinearWiden(const PathPos & pos,
                                 unsigned num, const double *gap, const double * length,
                                 bool trim) const
{
    return LinearWidenAsymetric(pos, num, gap, gap, length,
                                Edge::Update(), Edge::Update(), trim);
}

/** Computes a contour of a shape resulting from drawing 'path' with a variable-width pen.
 * The pen width may be different (potentially even zero) at each side of the path.
 * Used to draw bent arrowheads.
 * The starting and ending side of the line (where line cap would come) is returned invisible.
 * @param [in] pos The position where we start the drawing.
 * @param [in] gap1_left The pen width at the start of the drawing: 'pos'. Its absolute value is taken.
 *                       This is the pen width on the left side of the path as viewed by our walk.
 *                       If length is positive, this is the left side of the path viewed towards
 *                       the end of the path. If length is negative, it is left when looking towards
 *                       the start of the path. Its absolute value is taken.
 * @param [in] gap1_right The pen width on the right side at the start of the walk (at pos).
 *                        Its absolute value is taken.
 * @param [in] gap2_left The pen width on the left side at the end of the walk (pos+length).
 *                       Its absolute value is taken.
 * @param [in] gap2_right The pen width on the right side at the end of the walk (pos+length).
 *                        Its absolute value is taken.
 * @param [in] length The length of the path part to widen. It may be negative
 *                    to effect a walk backwards along path from 'pos'. Empty contour is returned
 *                    if zero.
 * @param [in] update_left The mark and visible fields of the edges of original path are kept and copied
 *                         to the extended path. This field can be used to dictate what changes shall
 *                         be applied to the left side. By default, none.
 * @param [in] update_right The mark and visible fields of the edges of original path are kept and copied
 *                          to the extended path. This field can be used to dictate what changes shall
 *                          be applied to the right side. By default, none.
 * @param [in] trim If true, we stop if the path is too short. If false, we extend it with a tangent
 *                  and widen that.*/
template <typename Edgeish>
Contour EdgeVector<Edgeish>::LinearWidenAsymetric(const PathPos & pos, double gap1_left, double gap1_right,
                                          double gap2_left, double gap2_right, double length,
                                          Edge::Update update_left, Edge::Update update_right,
                                          bool trim) const
{
    const double gl[2] = {gap1_left, gap2_left}, gr[2] = {gap1_right, gap2_right};
    return LinearWidenAsymetric(pos, 1, gl, gr, &length, update_left, update_right, trim);
}

/** Update edge properties depending on if they fall inside, outside or on the
 * edges of a Contour. We may increase path length by splitting edges, where
 * parts of an edge need different application.
 * Called Clip() as by default it sets the edges outside the contour invisible.
 * @param [in] by The contour to clip by.
 * @param [in] outside The update to apply to edges outside 'by'.
 * @param [in] inside The update to apply to edges inside 'by'.
 * @param [in] on_edge The update to apply to edges which lie on the edges of 'by'
 * @param [in] bb_me If you know the bounding box of this path, by all means supply it.
 *             It improves crosspoint finding performance.*/
template <typename Edgeish>
void EdgeVector<Edgeish>::Clip(const Contour& by, Edge::Update outside, Edge::Update inside,
                               Edge::Update on_edge, const Block* bb_me) {
    if (empty()) return;
    auto cps = CrossPoints(true, bb_me, by, CalcRelation);
    if (cps.empty()) {
        const bool is_inside = by.IsWithin(front().GetStart()) == WI_INSIDE;
        Apply(is_inside ? inside : outside);
        return;
    }
    std::ranges::sort(cps, [](const CPData& a, const CPData& b) { return a.me<b.me; });
    int inserted = 0;
    PathPos last(0,0);
    const PathPos end{size(), 0};
    //apply 'u' from last->till (potentially splitting edges, if needed)
    //We add 'inserted' to the edge number of both 'last' and 'till'.
    //If we had to split the edge at 'till', we also increase 'inserted'
    const auto apply = [&inserted, &last, this](PathPos till, Edge::Update update, Edge::Update next) {
        _ASSERT(last.edge<=till.edge);
        int add = 0;
        if (till.pos && update!=next) {
            const auto i = emplace(begin()+till.edge+inserted); //Assume 'Edgeish' is default constructible
            *i = *std::next(i);
            const double pos = last.edge==till.edge ? 1-(1-till.pos)/(1-last.pos) : till.pos;
            i->Chop(0, pos);
            std::next(i)->Chop(pos, 1);
            add = 1;
        }
        if (update.DoesSomething())
            for (size_t u = last.edge+inserted; u<till.edge+inserted+add; u++)
                at(u).Apply(update);
        inserted += add;
        last = till;
    };
    Edge::Update next;
    for (const CPData& cp : cps)
        switch (cp.type.rel) {
        case CPRel::Overlap: continue;
        case CPRel::Touch: continue;
        case CPRel::Fork:
            if (cp.type.me_overlap_fw!=Tri::True) {
                next = cp.type.other_left_side == Tri::True ? outside : inside;
                apply(cp.me, on_edge, next);
            } else {
                FALLTHROUGH;
        case CPRel::Cross:
            next = cp.type.other_left_side == Tri::True ? outside : inside;
            apply(cp.me, cp.type.other_left_side == Tri::True ? inside : outside, next);
            }
        }
    apply(end, next, {});
}

/** Removes edges (or parts of) outside (or inside) a contour.
 * This may make a closed and/or connected path open and/or unconnected.
 * If the path is closed (or at least its start==end, we Cycle the remaining edges to keep this connection.
 * Thus, e.g., if you clip a circle in half, you are guaranteed to end up in a single connected segment
 * irrespective of which was the first edge.
 * @param [in] by The contour by which we clip.
 * @param [in] invert If true, we clip by the inverse of 'by', that is, remove edges *inside* by not outside.
 * @param [in] remove_on_edge If true, we remove edges that run along the perimeter of 'by', else we keep them.
 * @param [in] bb_me if the bounding box of this path is known, supplying that may increase performance.
 * @param [in] use By default we first mark edges to be removed with an internal marker and remove the
 *             marked edges. Here you can also specify another way to mark the edges, set_visible() comes
 *             to mind. We ASSERT that no edge of the incoming path has such a marking.*/
template <typename Edgeish>
void EdgeVector<Edgeish>::ClipRemove(const Contour& by, bool invert, bool remove_on_edge, const Block* bb_me, Edge::Update use) {
    if (empty()) return;
    _ASSERT(FindNot(use.Invert()) == end()); //assert none of the edges are internally marked
    const bool connected = front().GetStart() == back().GetEnd();
    Clip(by, invert ? Edge::Update{} : use,
         invert ? use : Edge::Update{},
         remove_on_edge ? use : Edge::Update{},
         bb_me);
    if (connected)
        Cycle(use.Invert());
    std::erase_if(*this, [](const Edgeish& e) { return e.IsInternalMarked(); });
}


} //namespace


#endif //CONTOUR_CONTOURS_H
