From 1ff4b2b4eb2d04bcf4ff1f4b480dc8d7cff59eba Mon Sep 17 00:00:00 2001
From: Seth Hillbrand <seth@kipro-pcb.com>
Date: Tue, 28 Nov 2023 16:01:43 -0800
Subject: [PATCH] Upgrade Clipper2 to 1.3.0

Fixes a number of minor inflation issues including slivers when
overlapping points are inflated

Fixes https://gitlab.com/kicad/code/kicad/-/issues/16182

(cherry picked from commit daf178b64f3931b3e5a22e395600c135bd21b1ad)

Fixes https://gitlab.com/kicad/code/kicad/-/issues/16241
---
 libs/kimath/src/geometry/shape_poly_set.cpp   |  19 +-
 .../include/clipper2/clipper.core.h           | 110 ++-
 .../include/clipper2/clipper.engine.h         |  36 +-
 .../include/clipper2/clipper.export.h         | 835 +++++++-----------
 .../Clipper2Lib/include/clipper2/clipper.h    |  80 +-
 .../include/clipper2/clipper.minkowski.h      |   4 +-
 .../include/clipper2/clipper.offset.h         |  34 +-
 .../include/clipper2/clipper.rectclip.h       |   5 +-
 .../include/clipper2/clipper.version.h        |   6 +
 .../Clipper2Lib/src/clipper.engine.cpp        |  99 ++-
 .../Clipper2Lib/src/clipper.offset.cpp        | 463 +++++-----
 .../Clipper2Lib/src/clipper.rectclip.cpp      | 130 ++-
 12 files changed, 867 insertions(+), 954 deletions(-)
 create mode 100644 thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h

diff --git a/libs/kimath/src/geometry/shape_poly_set.cpp b/libs/kimath/src/geometry/shape_poly_set.cpp
index 49a080d905..edabe75ed5 100644
--- a/libs/kimath/src/geometry/shape_poly_set.cpp
+++ b/libs/kimath/src/geometry/shape_poly_set.cpp
@@ -499,7 +499,7 @@ bool SHAPE_POLY_SET::IsPolygonSelfIntersecting( int aPolygonIndex ) const
                 break;
 
             int index_diff = std::abs( firstSegment.Index() - secondSegment.Index() );
-            bool adjacent = ( index_diff == 1) || (index_diff == (segments.size() - 1) ); 
+            bool adjacent = ( index_diff == 1) || (index_diff == ((int)segments.size() - 1) );
 
             // Check whether the two segments built collide, only when they are not adjacent.
             if( !adjacent && firstSegment.Collide( secondSegment, 0 ) )
@@ -1009,28 +1009,27 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY
     // http://www.angusj.com/delphi/clipper/documentation/Docs/Units/ClipperLib/Types/JoinType.htm
     JoinType joinType = JoinType::Round;    // The way corners are offsetted
     double   miterLimit = 2.0;      // Smaller value when using jtMiter for joinType
-    JoinType miterFallback = JoinType::Square;
 
     switch( aCornerStrategy )
     {
-    case ALLOW_ACUTE_CORNERS:
+    case CORNER_STRATEGY::ALLOW_ACUTE_CORNERS:
         joinType = JoinType::Miter;
         miterLimit = 10;        // Allows large spikes
         break;
 
-    case CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
+    case CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS: // Acute angles are chamfered
         joinType = JoinType::Miter;
         break;
 
-    case ROUND_ACUTE_CORNERS:   // Acute angles are rounded
+    case CORNER_STRATEGY::ROUND_ACUTE_CORNERS: // Acute angles are rounded
         joinType = JoinType::Miter;
         break;
 
-    case CHAMFER_ALL_CORNERS:   // All angles are chamfered.
+    case CORNER_STRATEGY::CHAMFER_ALL_CORNERS: // All angles are chamfered.
         joinType = JoinType::Square;
         break;
 
-    case ROUND_ALL_CORNERS:     // All angles are rounded.
+    case CORNER_STRATEGY::ROUND_ALL_CORNERS: // All angles are rounded.
         joinType = JoinType::Round;
         break;
     }
@@ -1079,11 +1078,11 @@ void SHAPE_POLY_SET::inflate2( int aAmount, int aCircleSegCount, CORNER_STRATEGY
         Paths64 paths;
         c.Execute( aAmount, paths );
 
-        Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, false );
+        Clipper2Lib::SimplifyPaths( paths, std::abs( aAmount ) * coeff, true );
 
         Clipper64 c2;
-        c2.PreserveCollinear = false;
-        c2.ReverseSolution = false;
+        c2.PreserveCollinear( false );
+        c2.ReverseSolution( false );
         c2.AddSubject( paths );
         c2.Execute(ClipType::Union, FillRule::Positive, tree);
     }
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h
index 7a32d4c4aa..b3dddeeaa2 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.core.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  26 July 2023                                                    *
+* Date      :  24 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  Core Clipper Library structures and functions                   *
@@ -19,6 +19,7 @@
 #include <algorithm>
 #include <climits>
 #include <numeric>
+#include "clipper2/clipper.version.h"
 
 namespace Clipper2Lib
 {
@@ -42,18 +43,27 @@ namespace Clipper2Lib
     "Invalid scale (either 0 or too large)";
   static const char* non_pair_error =
     "There must be 2 values for each coordinate";
+  static const char* undefined_error =
+    "There is an undefined error in Clipper2";
 #endif
 
   // error codes (2^n)
-  const int precision_error_i   = 1; // non-fatal
-  const int scale_error_i       = 2; // non-fatal 
-  const int non_pair_error_i    = 4; // non-fatal 
-  const int range_error_i = 64;
+  const int precision_error_i   = 1;  // non-fatal
+  const int scale_error_i       = 2;  // non-fatal 
+  const int non_pair_error_i    = 4;  // non-fatal 
+  const int undefined_error_i   = 32; // fatal 
+  const int range_error_i       = 64;
 
 #ifndef PI
   static const double PI = 3.141592653589793238;
 #endif
-  static const int MAX_DECIMAL_PRECISION = 8; // see https://github.com/AngusJohnson/Clipper2/discussions/564
+
+#ifdef CLIPPER2_MAX_PRECISION
+  const int MAX_DECIMAL_PRECISION = CLIPPER2_MAX_PRECISION;
+#else
+  const int MAX_DECIMAL_PRECISION = 8; // see Discussions #564
+#endif
+
   static const int64_t MAX_COORD = INT64_MAX >> 2;
   static const int64_t MIN_COORD = -MAX_COORD;
   static const int64_t INVALID = INT64_MAX;
@@ -73,6 +83,8 @@ namespace Clipper2Lib
       throw Clipper2Exception(scale_error);
     case non_pair_error_i:
       throw Clipper2Exception(non_pair_error);
+    case undefined_error_i:
+      throw Clipper2Exception(undefined_error);
     case range_error_i:
       throw Clipper2Exception(range_error);
     }
@@ -81,6 +93,7 @@ namespace Clipper2Lib
 #endif
   }
 
+
   //By far the most widely used filling rules for polygons are EvenOdd
   //and NonZero, sometimes called Alternate and Winding respectively.
   //https://en.wikipedia.org/wiki/Nonzero-rule
@@ -137,7 +150,7 @@ namespace Clipper2Lib
 
     friend std::ostream& operator<<(std::ostream& os, const Point& point)
     {
-      os << point.x << "," << point.y << "," << point.z << " ";
+      os << point.x << "," << point.y << "," << point.z;
       return os;
     }
 
@@ -174,7 +187,7 @@ namespace Clipper2Lib
 
     friend std::ostream& operator<<(std::ostream& os, const Point& point)
     {
-      os << point.x << "," << point.y << " ";
+      os << point.x << "," << point.y;
       return os;
     }
 #endif
@@ -222,6 +235,14 @@ namespace Clipper2Lib
   using Paths64 = std::vector< Path64>;
   using PathsD = std::vector< PathD>;
 
+  static const Point64 InvalidPoint64 = Point64(
+    (std::numeric_limits<int64_t>::max)(),
+    (std::numeric_limits<int64_t>::max)());
+  static const PointD InvalidPointD = PointD(
+    (std::numeric_limits<double>::max)(),
+    (std::numeric_limits<double>::max)());
+
+
   // Rect ------------------------------------------------------------------------
 
   template <typename T>
@@ -237,19 +258,13 @@ namespace Clipper2Lib
     T right;
     T bottom;
 
-    Rect() :
-      left(0),
-      top(0),
-      right(0),
-      bottom(0) {}
-
     Rect(T l, T t, T r, T b) :
       left(l),
       top(t),
       right(r),
       bottom(b) {}
 
-    Rect(bool is_valid)
+    Rect(bool is_valid = true)
     {
       if (is_valid)
       {
@@ -258,10 +273,12 @@ namespace Clipper2Lib
       else
       {
         left = top = (std::numeric_limits<T>::max)();
-        right = bottom = -(std::numeric_limits<int64_t>::max)();
+        right = bottom = (std::numeric_limits<T>::lowest)();
       }
     }
 
+    bool IsValid() const { return left != (std::numeric_limits<T>::max)(); }
+
     T Width() const { return right - left; }
     T Height() const { return bottom - top; }
     void Width(T width) { right = left + width; }
@@ -309,10 +326,13 @@ namespace Clipper2Lib
         ((std::max)(top, rec.top) <= (std::min)(bottom, rec.bottom));
     };
 
+    bool operator==(const Rect<T>& other) const {
+      return left == other.left && right == other.right && 
+        top == other.top && bottom == other.bottom;
+    }
+
     friend std::ostream& operator<<(std::ostream& os, const Rect<T>& rect) {
-      os << "("
-        << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom
-        << ")";
+      os << "(" << rect.left << "," << rect.top << "," << rect.right << "," << rect.bottom << ") ";
       return os;
     }
   };
@@ -340,10 +360,16 @@ namespace Clipper2Lib
     return result;
   }
 
-  static const Rect64 MaxInvalidRect64 = Rect64(
-    INT64_MAX, INT64_MAX, INT64_MIN, INT64_MIN);
-  static const RectD MaxInvalidRectD = RectD(
-    MAX_DBL, MAX_DBL, -MAX_DBL, -MAX_DBL);
+  static const Rect64 InvalidRect64 = Rect64(
+    (std::numeric_limits<int64_t>::max)(), 
+    (std::numeric_limits<int64_t>::max)(), 
+    (std::numeric_limits<int64_t>::lowest)(),
+    (std::numeric_limits<int64_t>::lowest)());
+  static const RectD InvalidRectD = RectD(
+    (std::numeric_limits<double>::max)(),
+    (std::numeric_limits<double>::max)(),
+    (std::numeric_limits<double>::lowest)(),
+    (std::numeric_limits<double>::lowest)());
 
   template <typename T>
   Rect<T> GetBounds(const Path<T>& path)
@@ -490,26 +516,6 @@ namespace Clipper2Lib
     return result;
   }
 
-  inline PathD Path64ToPathD(const Path64& path)
-  {
-    return TransformPath<double, int64_t>(path);
-  }
-
-  inline PathsD Paths64ToPathsD(const Paths64& paths)
-  {
-    return TransformPaths<double, int64_t>(paths);
-  }
-
-  inline Path64 PathDToPath64(const PathD& path)
-  {
-    return TransformPath<int64_t, double>(path);
-  }
-
-  inline Paths64 PathsDToPaths64(const PathsD& paths)
-  {
-    return TransformPaths<int64_t, double>(paths);
-  }
-
   template<typename T>
   inline double Sqr(T val)
   {
@@ -565,7 +571,7 @@ namespace Clipper2Lib
   inline void StripDuplicates( Path<T>& path, bool is_closed_path)
   {
     //https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector#:~:text=Let%27s%20compare%20three%20approaches%3A
-    path.erase(std::unique(path.begin(), path.end()),path.end());
+    path.erase(std::unique(path.begin(), path.end()), path.end());
     if (is_closed_path)
       while (path.size() > 1 && path.back() == path.front()) path.pop_back();
   }
@@ -723,8 +729,9 @@ namespace Clipper2Lib
     }
   }
 
-  inline Point64 GetClosestPointOnSegment(const Point64& offPt,
-    const Point64& seg1, const Point64& seg2)
+  template<typename T>
+  inline Point<T> GetClosestPointOnSegment(const Point<T>& offPt,
+    const Point<T>& seg1, const Point<T>& seg2)
   {
     if (seg1.x == seg2.x && seg1.y == seg2.y) return seg1;
     double dx = static_cast<double>(seg2.x - seg1.x);
@@ -734,9 +741,14 @@ namespace Clipper2Lib
         static_cast<double>(offPt.y - seg1.y) * dy) /
       (Sqr(dx) + Sqr(dy));
     if (q < 0) q = 0; else if (q > 1) q = 1;
-    return Point64(
-      seg1.x + static_cast<int64_t>(nearbyint(q * dx)),
-      seg1.y + static_cast<int64_t>(nearbyint(q * dy)));
+    if constexpr (std::numeric_limits<T>::is_integer)
+      return Point<T>(
+        seg1.x + static_cast<T>(nearbyint(q * dx)),
+        seg1.y + static_cast<T>(nearbyint(q * dy)));
+    else
+      return Point<T>(
+        seg1.x + static_cast<T>(q * dx),
+        seg1.y + static_cast<T>(q * dy));
   }
 
   enum class PointInPolygonResult { IsOn, IsInside, IsOutside };
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h
index 239f705804..a48f058e5b 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.engine.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  26 July 2023                                                    *
+* Date      :  22 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This is the main polygon clipping module                        *
@@ -10,8 +10,6 @@
 #ifndef CLIPPER_ENGINE_H
 #define CLIPPER_ENGINE_H
 
-constexpr auto CLIPPER2_VERSION = "1.2.2";
-
 #include <cstdlib>
 #include <stdint.h> //#541
 #include <iostream>
@@ -21,12 +19,11 @@ constexpr auto CLIPPER2_VERSION = "1.2.2";
 #include <numeric>
 #include <memory>
 
-#include "clipper.core.h"
+#include "clipper2/clipper.core.h"
 
 #ifdef None
 #undef None
 #endif
-
 namespace Clipper2Lib {
 
 	struct Scanline;
@@ -268,6 +265,8 @@ namespace Clipper2Lib {
 		inline void CheckJoinRight(Active& e,
 			const Point64& pt, bool check_curr_x = false);
 	protected:
+		bool preserve_collinear_ = true;
+		bool reverse_solution_ = false;
 		int error_code_ = 0;
 		bool has_open_paths_ = false;
 		bool succeeded_ = true;
@@ -286,9 +285,11 @@ namespace Clipper2Lib {
 		void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
 	public:
 		virtual ~ClipperBase();
-		int ErrorCode() { return error_code_; };
-		bool PreserveCollinear = true;
-		bool ReverseSolution = false;
+		int ErrorCode() const { return error_code_; };
+		void PreserveCollinear(bool val) { preserve_collinear_ = val; };
+		bool PreserveCollinear() const { return preserve_collinear_;};
+		void ReverseSolution(bool val) { reverse_solution_ = val; };
+		bool ReverseSolution() const { return reverse_solution_; };
 		void Clear();
 		void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
 #ifdef USINGZ
@@ -350,12 +351,12 @@ namespace Clipper2Lib {
 			childs_.resize(0);
 		}
 
-		const PolyPath64* operator [] (size_t index) const
+		PolyPath64* operator [] (size_t index) const
 		{
 			return childs_[index].get(); //std::unique_ptr
 		}
 
-		const PolyPath64* Child(size_t index) const
+		PolyPath64* Child(size_t index) const
 		{
 			return childs_[index].get();
 		}
@@ -407,12 +408,12 @@ namespace Clipper2Lib {
 			childs_.resize(0);
 		}
 
-		const PolyPathD* operator [] (size_t index) const
+		PolyPathD* operator [] (size_t index) const
 		{
 			return childs_[index].get();
 		}
 
-		const PolyPathD* Child(size_t index) const
+		PolyPathD* Child(size_t index) const
 		{
 			return childs_[index].get();
 		}
@@ -421,7 +422,8 @@ namespace Clipper2Lib {
 		PolyPathDList::const_iterator end() const { return childs_.cend(); }
 
 		void SetScale(double value) { scale_ = value; }
-		double Scale() { return scale_; }
+		double Scale() const { return scale_; }
+
 		PolyPathD* AddChild(const Path64& path) override
 		{
 			int error_code = 0;
@@ -431,6 +433,14 @@ namespace Clipper2Lib {
 			return result;
 		}
 
+		PolyPathD* AddChild(const PathD& path)
+		{
+			auto p = std::make_unique<PolyPathD>(this);
+			PolyPathD* result = childs_.emplace_back(std::move(p)).get();
+			result->polygon_ = path;
+			return result;
+		}
+
 		void Clear() override
 		{
 			childs_.resize(0);
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h
index f5f81d2a8b..d7286132a4 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.export.h
@@ -1,39 +1,78 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  30 May 2023                                                     *
+* Date      :  26 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This module exports the Clipper2 Library (ie DLL/so)            *
 * License   :  http://www.boost.org/LICENSE_1_0.txt                            *
 *******************************************************************************/
 
-// The exported functions below refer to simple structures that
-// can be understood across multiple languages. Consequently
-// Path64, PathD, Polytree64 etc are converted from C++ classes
-// (std::vector<> etc) into the following data structures:
-//
-// CPath64 (int64_t*) & CPathD (double_t*):
-// Path64 and PathD are converted into arrays of x,y coordinates.
-// However in these arrays the first x,y coordinate pair is a
-// counter with 'x' containing the number of following coordinate
-// pairs. ('y' should be 0, with one exception explained below.)
-// __________________________________
-// |counter|coord1|coord2|...|coordN|
-// |N ,0   |x1, y1|x2, y2|...|xN, yN|
-// __________________________________
-//
-// CPaths64 (int64_t**) & CPathsD (double_t**):
-// These are arrays of pointers to CPath64 and CPathD where
-// the first pointer is to a 'counter path'. This 'counter
-// path' has a single x,y coord pair with 'y' (not 'x')
-// containing the number of paths that follow. ('x' = 0).
-// _______________________________
-// |counter|path1|path2|...|pathN|
-// |addr0  |addr1|addr2|...|addrN| (*addr0[0]=0; *addr0[1]=N)
-// _______________________________
-//
-// The structures of CPolytree64 and CPolytreeD are defined
-// below and these structures don't need to be explained here.
+
+/* 
+ Boolean clipping:
+ cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4
+ fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
+
+ Polygon offsetting (inflate/deflate):
+ jointype: Square=0, Bevel=1, Round=2, Miter=3
+ endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
+
+The path structures used extensively in other parts of this library are all
+based on std::vector classes. Since C++ classes can't be accessed by other
+languages, these paths must be converted into simple C data structures that
+can be understood by just about any programming language. And these C style
+path structures are simple arrays of int64_t (CPath64) and double (CPathD).
+
+CPath64 and CPathD:
+These are arrays of consecutive x and y path coordinates preceeded by  
+a pair of values containing the path's length (N) and a 0 value.
+__________________________________
+|counter|coord1|coord2|...|coordN|
+|N, 0   |x1, y1|x2, y2|...|xN, yN|
+__________________________________
+
+CPaths64 and CPathsD:
+These are also arrays containing any number of consecutive CPath64 or
+CPathD  structures. But preceeding these consecutive paths, there is pair of
+values that contain the total length of the array (A) structure and 
+the number (C) of CPath64 or CPathD it contains.
+_______________________________
+|counter|path1|path2|...|pathC|
+|A  , C |                     |
+_______________________________
+
+CPolytree64 and CPolytreeD:
+These are also arrays consisting of CPolyPath structures that represent 
+individual paths in a tree structure. However, the very first (ie top)
+CPolyPath is just the tree container that won't have a path. And because
+of that, its structure will be very slightly different from the remaining
+CPolyPath. This difference will be discussed below.
+
+CPolyPath64 and CPolyPathD:
+These are simple arrays consisting of a series of path coordinates followed 
+by any number of child (ie nested) CPolyPath. Preceeding these are two values 
+indicating the length of the path (N) and the number of child CPolyPath (C).
+____________________________________________________________
+|counter|coord1|coord2|...|coordN| child1|child2|...|childC|
+|N  , C |x1, y1|x2, y2|...|xN, yN|                         |
+____________________________________________________________
+
+As mentioned above, the very first CPolyPath structure is just a container
+that owns (both directly and indirectly) every other CPolyPath in the tree. 
+Since this first CPolyPath has no path, instead of a path length, its very
+first value will contain the total length of the CPolytree array structure.
+
+All theses exported structures (CPaths64, CPathsD, CPolyTree64 & CPolyTreeD)
+are arrays of type int64_t or double. And the first value in these arrays 
+will always contain the length of that array.
+
+These array structures are allocated in heap memory which will eventually 
+need to be released. But since applications dynamically linking to these 
+functions may use different memory managers, the only safe way to free up
+this memory is to use the exported DisposeArray64 and  DisposeArrayD 
+functions below.
+*/
+
 
 #ifndef CLIPPER2_EXPORT_H
 #define CLIPPER2_EXPORT_H
@@ -49,25 +88,14 @@
 namespace Clipper2Lib {
 
 typedef int64_t* CPath64;
-typedef int64_t** CPaths64;
-typedef double* CPathD;
-typedef double** CPathsD;
+typedef int64_t* CPaths64;
+typedef double*  CPathD;
+typedef double*  CPathsD;
 
-typedef struct CPolyPath64 {
-  CPath64       polygon;
-  uint32_t      is_hole;
-  uint32_t      child_count;
-  CPolyPath64*  childs;
-}
-CPolyTree64;
-
-typedef struct CPolyPathD {
-  CPathD        polygon;
-  uint32_t      is_hole;
-  uint32_t      child_count;
-  CPolyPathD*   childs;
-}
-CPolyTreeD;
+typedef int64_t* CPolyPath64;
+typedef int64_t* CPolyTree64;
+typedef double* CPolyPathD;
+typedef double* CPolyTreeD;
 
 template <typename T>
 struct CRect {
@@ -97,57 +125,53 @@ inline Rect<T> CRectToRect(const CRect<T>& rect)
   return result;
 }
 
-#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
+#ifdef _WIN32
+  #define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
+#else
+  #define EXTERN_DLL_EXPORT extern "C" 
+#endif
+
 
 //////////////////////////////////////////////////////
-// EXPORTED FUNCTION DEFINITIONS
+// EXPORTED FUNCTION DECLARATIONS
 //////////////////////////////////////////////////////
 
 EXTERN_DLL_EXPORT const char* Version();
 
-// Some of the functions below will return data in the various CPath
-// and CPolyTree structures which are pointers to heap allocated
-// memory. Eventually this memory will need to be released with one
-// of the following 'DisposeExported' functions.  (This may be the
-// only safe way to release this memory since the executable
-// accessing these exported functions may use a memory manager that
-// allocates and releases heap memory in a different way. Also,
-// CPath structures that have been constructed by the executable
-// should not be destroyed using these 'DisposeExported' functions.)
-EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p);
-EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp);
-EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p);
-EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp);
-EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt);
-EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt);
+EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p)
+{
+  delete[] p;
+}
+
+EXTERN_DLL_EXPORT void DisposeArrayD(double*& p)
+{
+  delete[] p;
+}
 
-// Boolean clipping:
-// cliptype: None=0, Intersection=1, Union=2, Difference=3, Xor=4
-// fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
 EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
   uint8_t fillrule, const CPaths64 subjects,
   const CPaths64 subjects_open, const CPaths64 clips,
   CPaths64& solution, CPaths64& solution_open,
   bool preserve_collinear = true, bool reverse_solution = false);
-EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
+
+EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
   uint8_t fillrule, const CPaths64 subjects,
   const CPaths64 subjects_open, const CPaths64 clips,
-  CPolyTree64*& solution, CPaths64& solution_open,
+  CPolyTree64& sol_tree, CPaths64& solution_open,
   bool preserve_collinear = true, bool reverse_solution = false);
+
 EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
   uint8_t fillrule, const CPathsD subjects,
   const CPathsD subjects_open, const CPathsD clips,
   CPathsD& solution, CPathsD& solution_open, int precision = 2,
   bool preserve_collinear = true, bool reverse_solution = false);
-EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
+
+EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
   uint8_t fillrule, const CPathsD subjects,
   const CPathsD subjects_open, const CPathsD clips,
-  CPolyTreeD*& solution, CPathsD& solution_open, int precision = 2,
+  CPolyTreeD& solution, CPathsD& solution_open, int precision = 2,
   bool preserve_collinear = true, bool reverse_solution = false);
 
-// Polygon offsetting (inflate/deflate):
-// jointype: Square=0, Round=1, Miter=2
-// endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
 EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
   double delta, uint8_t jointype, uint8_t endtype, 
   double miter_limit = 2.0, double arc_tolerance = 0.0, 
@@ -171,66 +195,173 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
 // INTERNAL FUNCTIONS
 //////////////////////////////////////////////////////
 
-inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2);
-inline CPath64 CreateCPath64(const Path64& p);
-inline CPaths64 CreateCPaths64(const Paths64& pp);
-inline Path64 ConvertCPath64(const CPath64& p);
-inline Paths64 ConvertCPaths64(const CPaths64& pp);
+template <typename T>
+static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
+  size_t& cnt, size_t& array_len)
+{
+  array_len = 2;
+  cnt = 0;
+  for (const Path<T>& path : paths)
+    if (path.size())
+    {
+      array_len += path.size() * 2 + 2;
+      ++cnt;
+    }
+}
 
-inline CPathD CreateCPathD(size_t cnt1, size_t cnt2);
-inline CPathD CreateCPathD(const PathD& p);
-inline CPathsD CreateCPathsD(const PathsD& pp);
-inline PathD ConvertCPathD(const CPathD& p);
-inline PathsD ConvertCPathsD(const CPathsD& pp);
+static size_t GetPolyPath64ArrayLen(const PolyPath64& pp)
+{
+  size_t result = 2; // poly_length + child_count
+  result += pp.Polygon().size() * 2;
+  //plus nested children :)
+  for (size_t i = 0; i < pp.Count(); ++i)
+    result += GetPolyPath64ArrayLen(*pp[i]);
+  return result;
+}
 
-// the following function avoid multiple conversions
-inline CPathD CreateCPathD(const Path64& p, double scale);
-inline CPathsD CreateCPathsD(const Paths64& pp, double scale);
-inline Path64 ConvertCPathD(const CPathD& p, double scale);
-inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale);
+static void GetPolytreeCountAndCStorageSize(const PolyTree64& tree, 
+  size_t& cnt, size_t& array_len)
+{
+  cnt = tree.Count(); // nb: top level count only 
+  array_len = GetPolyPath64ArrayLen(tree);
+}
 
-inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt);
-inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale);
+template <typename T>
+static T* CreateCPaths(const Paths<T>& paths)
+{
+  size_t cnt = 0, array_len = 0;
+  GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
+  T* result = new T[array_len], * v = result;
+  *v++ = array_len;
+  *v++ = cnt;
+  for (const Path<T>& path : paths)
+  {
+    if (!path.size()) continue;
+    *v++ = path.size();
+    *v++ = 0;
+    for (const Point<T>& pt : path)
+    {
+      *v++ = pt.x;
+      *v++ = pt.y;
+    }
+  }
+  return result;
+}
+
+
+CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
+{
+  if (!paths.size()) return nullptr;
+  size_t cnt, array_len;
+  GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
+  CPathsD result = new double[array_len], v = result;
+  *v++ = (double)array_len;
+  *v++ = (double)cnt;
+  for (const Path64& path : paths)
+  {
+    if (!path.size()) continue;
+    *v = (double)path.size();
+    ++v; *v++ = 0;
+    for (const Point64& pt : path)
+    {
+      *v++ = pt.x * scale;
+      *v++ = pt.y * scale;
+    }
+  }
+  return result;
+}
+
+template <typename T>
+static Paths<T> ConvertCPaths(T* paths)
+{
+  Paths<T> result;
+  if (!paths) return result;
+  T* v = paths; ++v;
+  size_t cnt = *v++;
+  result.reserve(cnt);
+  for (size_t i = 0; i < cnt; ++i)
+  {
+    size_t cnt2 = *v;
+    v += 2;
+    Path<T> path;
+    path.reserve(cnt2);
+    for (size_t j = 0; j < cnt2; ++j)
+    {
+      T x = *v++, y = *v++;
+      path.push_back(Point<T>(x, y));
+    }
+    result.push_back(path);
+  }
+  return result;
+}
+
+
+static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
+{
+  Paths64 result;
+  if (!paths) return result;
+  double* v = paths; 
+  ++v; // skip the first value (0)
+  int64_t cnt = (int64_t)*v++;
+  result.reserve(cnt);
+  for (int i = 0; i < cnt; ++i)
+  {
+    int64_t cnt2 = (int64_t)*v;
+    v += 2;
+    Path64 path;
+    path.reserve(cnt2);
+    for (int j = 0; j < cnt2; ++j)
+    {
+      double x = *v++ * scale;
+      double y = *v++ * scale;
+      path.push_back(Point64(x, y));
+    }
+    result.push_back(path);
+  }
+  return result;
+}
+
+template <typename T>
+static void CreateCPolyPath(const PolyPath64* pp, T*& v, T scale)
+{
+  *v++ = static_cast<T>(pp->Polygon().size());
+  *v++ = static_cast<T>(pp->Count());
+  for (const Point64& pt : pp->Polygon())
+  {
+    *v++ = static_cast<T>(pt.x * scale);
+    *v++ = static_cast<T>(pt.y * scale);
+  }
+  for (size_t i = 0; i < pp->Count(); ++i)
+    CreateCPolyPath(pp->Child(i), v, scale);
+}
+
+template <typename T>
+static T* CreateCPolyTree(const PolyTree64& tree, T scale)
+{
+  if (scale == 0) scale = 1;
+  size_t cnt, array_len;
+  GetPolytreeCountAndCStorageSize(tree, cnt, array_len);
+  if (!cnt) return nullptr;
+  // allocate storage
+  T* result = new T[array_len];
+  T* v = result;
+
+  *v++ = static_cast<T>(array_len);
+  *v++ = static_cast<T>(tree.Count());
+  for (size_t i = 0; i < tree.Count(); ++i)
+    CreateCPolyPath(tree.Child(i), v, scale);
+  return result;
+}
+
+//////////////////////////////////////////////////////
+// EXPORTED FUNCTION DEFINITIONS
+//////////////////////////////////////////////////////
 
 EXTERN_DLL_EXPORT const char* Version()
 {
   return CLIPPER2_VERSION;
 }
 
-EXTERN_DLL_EXPORT void DisposeExportedCPath64(CPath64 p)
-{
-  if (p) delete[] p;
-}
-
-EXTERN_DLL_EXPORT void DisposeExportedCPaths64(CPaths64& pp)
-{
-  if (!pp) return;
-  CPaths64 v = pp;
-  CPath64 cnts = *v;
-  const size_t cnt = static_cast<size_t>(cnts[1]);
-  for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
-    DisposeExportedCPath64(*v++);
-  delete[] pp;
-  pp = nullptr;
-}
-
-EXTERN_DLL_EXPORT void DisposeExportedCPathD(CPathD p)
-{
-  if (p) delete[] p;
-}
-
-EXTERN_DLL_EXPORT void DisposeExportedCPathsD(CPathsD& pp)
-{
-  if (!pp) return;
-  CPathsD v = pp;
-  CPathD cnts = *v;
-  size_t cnt = static_cast<size_t>(cnts[1]);
-  for (size_t i = 0; i <= cnt; ++i) //nb: cnt +1
-    DisposeExportedCPathD(*v++);
-  delete[] pp;
-  pp = nullptr;
-}
-
 EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype, 
   uint8_t fillrule, const CPaths64 subjects,
   const CPaths64 subjects_open, const CPaths64 clips,
@@ -241,48 +372,48 @@ EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
   if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
   
   Paths64 sub, sub_open, clp, sol, sol_open;
-  sub       = ConvertCPaths64(subjects);
-  sub_open  = ConvertCPaths64(subjects_open);
-  clp       = ConvertCPaths64(clips);
+  sub       = ConvertCPaths(subjects);
+  sub_open  = ConvertCPaths(subjects_open);
+  clp       = ConvertCPaths(clips);
 
   Clipper64 clipper;
-  clipper.PreserveCollinear = preserve_collinear;
-  clipper.ReverseSolution = reverse_solution;
+  clipper.PreserveCollinear(preserve_collinear);
+  clipper.ReverseSolution(reverse_solution);
   if (sub.size() > 0) clipper.AddSubject(sub);
   if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
   if (clp.size() > 0) clipper.AddClip(clp);
   if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open)) 
     return -1; // clipping bug - should never happen :)
-  solution = CreateCPaths64(sol);
-  solution_open = CreateCPaths64(sol_open);
+  solution = CreateCPaths(sol);
+  solution_open = CreateCPaths(sol_open);
   return 0; //success !!
 }
 
-EXTERN_DLL_EXPORT int BooleanOpPt64(uint8_t cliptype,
+EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
   uint8_t fillrule, const CPaths64 subjects,
   const CPaths64 subjects_open, const CPaths64 clips,
-  CPolyTree64*& solution, CPaths64& solution_open,
+  CPolyTree64& sol_tree, CPaths64& solution_open,
   bool preserve_collinear, bool reverse_solution)
 {
   if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
   if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
   Paths64 sub, sub_open, clp, sol_open;
-  sub = ConvertCPaths64(subjects);
-  sub_open = ConvertCPaths64(subjects_open);
-  clp = ConvertCPaths64(clips);
+  sub = ConvertCPaths(subjects);
+  sub_open = ConvertCPaths(subjects_open);
+  clp = ConvertCPaths(clips);
 
-  PolyTree64 pt;
+  PolyTree64 tree;
   Clipper64 clipper;
-  clipper.PreserveCollinear = preserve_collinear;
-  clipper.ReverseSolution = reverse_solution;
+  clipper.PreserveCollinear(preserve_collinear);
+  clipper.ReverseSolution(reverse_solution);
   if (sub.size() > 0) clipper.AddSubject(sub);
   if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
   if (clp.size() > 0) clipper.AddClip(clp);
-  if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), pt, sol_open))
+  if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
     return -1; // clipping bug - should never happen :)
 
-  solution = CreateCPolyTree64(pt);
-  solution_open = CreateCPaths64(sol_open);
+  sol_tree = CreateCPolyTree(tree, (int64_t)1);
+  solution_open = CreateCPaths(sol_open);
   return 0; //success !!
 }
 
@@ -298,57 +429,54 @@ EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
   const double scale = std::pow(10, precision);
 
   Paths64 sub, sub_open, clp, sol, sol_open;
-  sub       = ConvertCPathsD(subjects, scale);
-  sub_open  = ConvertCPathsD(subjects_open, scale);
-  clp       = ConvertCPathsD(clips, scale);
+  sub       = ConvertCPathsDToPaths64(subjects, scale);
+  sub_open  = ConvertCPathsDToPaths64(subjects_open, scale);
+  clp       = ConvertCPathsDToPaths64(clips, scale);
 
   Clipper64 clipper;
-  clipper.PreserveCollinear = preserve_collinear;
-  clipper.ReverseSolution = reverse_solution;
+  clipper.PreserveCollinear(preserve_collinear);
+  clipper.ReverseSolution(reverse_solution);
   if (sub.size() > 0) clipper.AddSubject(sub);
-  if (sub_open.size() > 0)
-    clipper.AddOpenSubject(sub_open);
+  if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
   if (clp.size() > 0) clipper.AddClip(clp);
   if (!clipper.Execute(ClipType(cliptype),
     FillRule(fillrule), sol, sol_open)) return -1;
-
-  if (sol.size() > 0) solution = CreateCPathsD(sol, 1 / scale);
-  if (sol_open.size() > 0)
-    solution_open = CreateCPathsD(sol_open, 1 / scale);
+  solution = CreateCPathsDFromPaths64(sol, 1 / scale);
+  solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale);
   return 0;
 }
 
-EXTERN_DLL_EXPORT int BooleanOpPtD(uint8_t cliptype,
+EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
   uint8_t fillrule, const CPathsD subjects,
   const CPathsD subjects_open, const CPathsD clips,
-  CPolyTreeD*& solution, CPathsD& solution_open, int precision,
+  CPolyTreeD& solution, CPathsD& solution_open, int precision,
   bool preserve_collinear, bool reverse_solution)
 {
   if (precision < -8 || precision > 8) return -5;
   if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
   if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
   
-  const double scale = std::pow(10, precision);
+  double scale = std::pow(10, precision);
+
+  int err = 0;
   Paths64 sub, sub_open, clp, sol_open;
-  sub       = ConvertCPathsD(subjects, scale);
-  sub_open  = ConvertCPathsD(subjects_open, scale);
-  clp       = ConvertCPathsD(clips, scale);
+  sub = ConvertCPathsDToPaths64(subjects, scale);
+  sub_open = ConvertCPathsDToPaths64(subjects_open, scale);
+  clp = ConvertCPathsDToPaths64(clips, scale);
 
-  PolyTree64 sol;
+  PolyTree64 tree;
   Clipper64 clipper;
-  clipper.PreserveCollinear = preserve_collinear;
-  clipper.ReverseSolution = reverse_solution;
+  clipper.PreserveCollinear(preserve_collinear);
+  clipper.ReverseSolution(reverse_solution);
   if (sub.size() > 0) clipper.AddSubject(sub);
-  if (sub_open.size() > 0)
-    clipper.AddOpenSubject(sub_open);
+  if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
   if (clp.size() > 0) clipper.AddClip(clp);
-  if (!clipper.Execute(ClipType(cliptype),
-    FillRule(fillrule), sol, sol_open)) return -1;
+  if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
+    return -1; // clipping bug - should never happen :)
 
-  solution = CreateCPolyTreeD(sol, 1 / scale);
-  if (sol_open.size() > 0)
-    solution_open = CreateCPathsD(sol_open, 1 / scale);
-  return 0;
+  solution = CreateCPolyTree(tree, 1/scale);
+  solution_open = CreateCPathsDFromPaths64(sol_open, 1 / scale);
+  return 0; //success !!
 }
 
 EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
@@ -356,14 +484,13 @@ EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
   double arc_tolerance, bool reverse_solution)
 {
   Paths64 pp;
-  pp = ConvertCPaths64(paths);
-
+  pp = ConvertCPaths(paths);
   ClipperOffset clip_offset( miter_limit, 
     arc_tolerance, reverse_solution);
   clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
   Paths64 result; 
   clip_offset.Execute(delta, result);
-  return CreateCPaths64(result);
+  return CreateCPaths(result);
 }
 
 EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
@@ -372,13 +499,15 @@ EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
   double arc_tolerance, bool reverse_solution)
 {
   if (precision < -8 || precision > 8 || !paths) return nullptr;
+
   const double scale = std::pow(10, precision);
   ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
-  Paths64 pp = ConvertCPathsD(paths, scale);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
   clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
   Paths64 result;
   clip_offset.Execute(delta * scale, result);
-  return CreateCPathsD(result, 1/scale);
+
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
 EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
@@ -386,9 +515,9 @@ EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   Rect64 r64 = CRectToRect(rect);
   class RectClip64 rc(r64);
-  Paths64 pp = ConvertCPaths64(paths);
+  Paths64 pp = ConvertCPaths(paths);
   Paths64 result = rc.Execute(pp);
-  return CreateCPaths64(result);
+  return CreateCPaths(result);
 }
 
 EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
@@ -399,10 +528,11 @@ EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int
 
   RectD r = CRectToRect(rect);
   Rect64 rec = ScaleRect<int64_t, double>(r, scale);
-  Paths64 pp = ConvertCPathsD(paths, scale);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
   class RectClip64 rc(rec);
   Paths64 result = rc.Execute(pp);
-  return CreateCPathsD(result, 1/scale);
+
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
 EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
@@ -411,9 +541,9 @@ EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   Rect64 r = CRectToRect(rect);
   class RectClipLines64 rcl (r);
-  Paths64 pp = ConvertCPaths64(paths);
+  Paths64 pp = ConvertCPaths(paths);
   Paths64 result = rcl.Execute(pp);
-  return CreateCPaths64(result);
+  return CreateCPaths(result);
 }
 
 EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
@@ -421,350 +551,13 @@ EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
 {
   if (CRectIsEmpty(rect) || !paths) return nullptr;
   if (precision < -8 || precision > 8) return nullptr;
+
   const double scale = std::pow(10, precision);
   Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
   class RectClipLines64 rcl(r);
-  Paths64 pp = ConvertCPathsD(paths, scale);
+  Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
   Paths64 result = rcl.Execute(pp);
-  return CreateCPathsD(result, 1/scale);
-}
-
-inline CPath64 CreateCPath64(size_t cnt1, size_t cnt2)
-{
-  // allocates memory for CPath64, fills in the counter, and
-  // returns the structure ready to be filled with path data
-  CPath64 result = new int64_t[2 + cnt1 *2];
-  result[0] = cnt1;
-  result[1] = cnt2;
-  return result;
-}
-
-inline CPath64 CreateCPath64(const Path64& p)
-{
-  // allocates memory for CPath64, fills the counter
-  // and returns the memory filled with path data
-  size_t cnt = p.size();
-  if (!cnt) return nullptr;
-  CPath64 result = CreateCPath64(cnt, 0);
-  CPath64 v = result;
-  v += 2; // skip counters
-  for (const Point64& pt : p)
-  {
-    *v++ = pt.x;
-    *v++ = pt.y;
-  }
-  return result;
-}
-
-inline Path64 ConvertCPath64(const CPath64& p)
-{
-  Path64 result;
-  if (p && *p)
-  {
-    CPath64 v = p;
-    const size_t cnt = static_cast<size_t>(p[0]);
-    v += 2; // skip counters
-    result.reserve(cnt);
-    for (size_t i = 0; i < cnt; ++i)
-    {
-      // x,y here avoids right to left function evaluation
-      // result.push_back(Point64(*v++, *v++));
-      int64_t x = *v++;
-      int64_t y = *v++;
-      result.push_back(Point64(x, y));
-    }
-  }
-  return result;
-}
-
-inline CPaths64 CreateCPaths64(const Paths64& pp)
-{
-  // allocates memory for multiple CPath64 and
-  // and returns this memory filled with path data
-  size_t cnt = pp.size(), cnt2 = cnt;
-
-  // don't allocate space for empty paths
-  for (size_t i = 0; i < cnt; ++i)
-    if (!pp[i].size()) --cnt2;
-  if (!cnt2) return nullptr;
-
-  CPaths64 result = new int64_t* [cnt2 + 1];
-  CPaths64 v = result;
-  *v++ = CreateCPath64(0, cnt2); // assign a counter path
-  for (const Path64& p : pp)
-  {
-    *v = CreateCPath64(p);
-    if (*v) ++v;
-  }
-  return result;
-}
-
-inline Paths64 ConvertCPaths64(const CPaths64& pp)
-{
-  Paths64 result;
-  if (pp) 
-  {
-    CPaths64 v = pp;
-    CPath64 cnts = pp[0];
-    const size_t cnt = static_cast<size_t>(cnts[1]); // nb 2nd cnt
-    ++v; // skip cnts
-    result.reserve(cnt);
-    for (size_t i = 0; i < cnt; ++i)
-      result.push_back(ConvertCPath64(*v++));
-  }
-  return result;
-}
-
-inline CPathD CreateCPathD(size_t cnt1, size_t cnt2)
-{
-  // allocates memory for CPathD, fills in the counter, and
-  // returns the structure ready to be filled with path data
-  CPathD result = new double[2 + cnt1 * 2];
-  result[0] = static_cast<double>(cnt1);
-  result[1] = static_cast<double>(cnt2);
-  return result;
-}
-
-inline CPathD CreateCPathD(const PathD& p)
-{
-  // allocates memory for CPath, fills the counter
-  // and returns the memory fills with path data
-  size_t cnt = p.size();
-  if (!cnt) return nullptr; 
-  CPathD result = CreateCPathD(cnt, 0);
-  CPathD v = result;
-  v += 2; // skip counters
-  for (const PointD& pt : p)
-  {
-    *v++ = pt.x;
-    *v++ = pt.y;
-  }
-  return result;
-}
-
-inline PathD ConvertCPathD(const CPathD& p)
-{
-  PathD result;
-  if (p)
-  {
-    CPathD v = p;
-    size_t cnt = static_cast<size_t>(v[0]);
-    v += 2; // skip counters
-    result.reserve(cnt);
-    for (size_t i = 0; i < cnt; ++i)
-    {
-      // x,y here avoids right to left function evaluation
-      // result.push_back(PointD(*v++, *v++));
-      double x = *v++;
-      double y = *v++;
-      result.push_back(PointD(x, y));
-    }
-  }
-  return result;
-}
-
-inline CPathsD CreateCPathsD(const PathsD& pp)
-{
-  size_t cnt = pp.size(), cnt2 = cnt;
-  // don't allocate space for empty paths
-  for (size_t i = 0; i < cnt; ++i)
-    if (!pp[i].size()) --cnt2;
-  if (!cnt2) return nullptr;
-  CPathsD result = new double * [cnt2 + 1];
-  CPathsD v = result;
-  *v++ = CreateCPathD(0, cnt2); // assign counter path
-  for (const PathD& p : pp)
-  {
-    *v = CreateCPathD(p);
-    if (*v) { ++v; }
-  }
-  return result;
-}
-
-inline PathsD ConvertCPathsD(const CPathsD& pp)
-{
-  PathsD result;
-  if (pp)
-  {
-    CPathsD v = pp;
-    CPathD cnts = v[0];
-    size_t cnt = static_cast<size_t>(cnts[1]);
-    ++v; // skip cnts path
-    result.reserve(cnt);
-    for (size_t i = 0; i < cnt; ++i)
-      result.push_back(ConvertCPathD(*v++));
-  }
-  return result;
-}
-
-inline Path64 ConvertCPathD(const CPathD& p, double scale)
-{
-  Path64 result;
-  if (p)
-  {
-    CPathD v = p;
-    size_t cnt = static_cast<size_t>(*v);
-    v += 2; // skip counters
-    result.reserve(cnt);
-    for (size_t i = 0; i < cnt; ++i)
-    {
-      // x,y here avoids right to left function evaluation
-      // result.push_back(PointD(*v++, *v++));
-      double x = *v++ * scale;
-      double y = *v++ * scale;
-      result.push_back(Point64(x, y));
-    }
-  }
-  return result;
-}
-
-inline Paths64 ConvertCPathsD(const CPathsD& pp, double scale)
-{
-  Paths64 result;
-  if (pp)
-  {
-    CPathsD v = pp;
-    CPathD cnts = v[0];
-    size_t cnt = static_cast<size_t>(cnts[1]);
-    result.reserve(cnt);
-    ++v; // skip cnts path
-    for (size_t i = 0; i < cnt; ++i)
-      result.push_back(ConvertCPathD(*v++, scale));
-  }
-  return result;
-}
-
-inline CPathD CreateCPathD(const Path64& p, double scale)
-{
-  // allocates memory for CPathD, fills in the counter, and
-  // returns the structure filled with *scaled* path data
-  size_t cnt = p.size();
-  if (!cnt) return nullptr;
-  CPathD result = CreateCPathD(cnt, 0);
-  CPathD v = result;
-  v += 2; // skip cnts 
-  for (const Point64& pt : p)
-  {
-    *v++ = pt.x * scale;
-    *v++ = pt.y * scale;
-  }
-  return result;
-}
-
-inline CPathsD CreateCPathsD(const Paths64& pp, double scale)
-{
-  // allocates memory for *multiple* CPathD, and
-  // returns the structure filled with scaled path data
-  size_t cnt = pp.size(), cnt2 = cnt;
-  // don't allocate space for empty paths
-  for (size_t i = 0; i < cnt; ++i)
-    if (!pp[i].size()) --cnt2;
-  if (!cnt2) return nullptr;
-  CPathsD result = new double* [cnt2 + 1];
-  CPathsD v = result;
-  *v++ = CreateCPathD(0, cnt2);
-  for (const Path64& p : pp)
-  {
-    *v = CreateCPathD(p, scale);
-    if (*v) ++v;
-  }
-  return result;
-}
-
-inline void InitCPolyPath64(CPolyTree64* cpt, 
-  bool is_hole, const std::unique_ptr <PolyPath64>& pp)
-{
-  cpt->polygon = CreateCPath64(pp->Polygon());
-  cpt->is_hole = is_hole;
-  size_t child_cnt = pp->Count();
-  cpt->child_count = static_cast<uint32_t>(child_cnt);
-  cpt->childs = nullptr;
-  if (!child_cnt) return;
-  cpt->childs = new CPolyPath64[child_cnt];
-  CPolyPath64* child = cpt->childs;
-  for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
-    InitCPolyPath64(child++, !is_hole, pp_child);  
-}
-
-inline CPolyTree64* CreateCPolyTree64(const PolyTree64& pt)
-{
-  CPolyTree64* result = new CPolyTree64();
-  result->polygon = nullptr;
-  result->is_hole = false;
-  size_t child_cnt = pt.Count();
-  result->childs = nullptr;
-  result->child_count = static_cast<uint32_t>(child_cnt);
-  if (!child_cnt) return result;
-  result->childs = new CPolyPath64[child_cnt];
-  CPolyPath64* child = result->childs;
-  for (const std::unique_ptr <PolyPath64>& pp : pt)
-    InitCPolyPath64(child++, true, pp);
-  return result;
-}
-
-inline void DisposeCPolyPath64(CPolyPath64* cpp) 
-{
-  if (!cpp->child_count) return;
-  CPolyPath64* child = cpp->childs;
-  for (size_t i = 0; i < cpp->child_count; ++i)
-    DisposeCPolyPath64(child);
-  delete[] cpp->childs;
-}
-
-EXTERN_DLL_EXPORT void DisposeExportedCPolyTree64(CPolyTree64*& cpt)
-{
-  if (!cpt) return;
-  DisposeCPolyPath64(cpt);
-  delete cpt;
-  cpt = nullptr;
-}
-
-inline void InitCPolyPathD(CPolyTreeD* cpt,
-  bool is_hole, const std::unique_ptr <PolyPath64>& pp, double scale)
-{
-  cpt->polygon = CreateCPathD(pp->Polygon(), scale);
-  cpt->is_hole = is_hole;
-  size_t child_cnt = pp->Count();
-  cpt->child_count = static_cast<uint32_t>(child_cnt);
-  cpt->childs = nullptr;
-  if (!child_cnt) return;
-  cpt->childs = new CPolyPathD[child_cnt];
-  CPolyPathD* child = cpt->childs;
-  for (const std::unique_ptr <PolyPath64>& pp_child : *pp)
-    InitCPolyPathD(child++, !is_hole, pp_child, scale);
-}
-
-inline CPolyTreeD* CreateCPolyTreeD(const PolyTree64& pt, double scale)
-{
-  CPolyTreeD* result = new CPolyTreeD();
-  result->polygon = nullptr;
-  result->is_hole = false;
-  size_t child_cnt = pt.Count();
-  result->child_count = static_cast<uint32_t>(child_cnt);
-  result->childs = nullptr;
-  if (!child_cnt) return result;
-  result->childs = new CPolyPathD[child_cnt];
-  CPolyPathD* child = result->childs;
-  for (const std::unique_ptr <PolyPath64>& pp : pt)
-    InitCPolyPathD(child++, true, pp, scale);
-  return result;
-}
-
-inline void DisposeCPolyPathD(CPolyPathD* cpp)
-{
-  if (!cpp->child_count) return;
-  CPolyPathD* child = cpp->childs;
-  for (size_t i = 0; i < cpp->child_count; ++i)
-    DisposeCPolyPathD(child++);
-  delete[] cpp->childs;
-}
-
-EXTERN_DLL_EXPORT void DisposeExportedCPolyTreeD(CPolyTreeD*& cpt)
-{
-  if (!cpt) return;
-  DisposeCPolyPathD(cpt);
-  delete cpt;
-  cpt = nullptr;
+  return CreateCPathsDFromPaths64(result, 1 / scale);
 }
 
 }  // end Clipper2Lib namespace
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h
index b5ac6aa9a1..0f516b60e8 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  16 July 2023                                                    *
+* Date      :  18 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This module provides a simple interface to the Clipper Library  *
@@ -14,11 +14,11 @@
 #include <type_traits>
 #include <vector>
 
-#include "clipper.core.h"
-#include "clipper.engine.h"
-#include "clipper.offset.h"
-#include "clipper.minkowski.h"
-#include "clipper.rectclip.h"
+#include "clipper2/clipper.core.h"
+#include "clipper2/clipper.engine.h"
+#include "clipper2/clipper.offset.h"
+#include "clipper2/clipper.minkowski.h"
+#include "clipper2/clipper.rectclip.h"
 
 namespace Clipper2Lib {
 
@@ -341,6 +341,19 @@ namespace Clipper2Lib {
           details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + "  ");
     }
 
+    template<typename T, typename U>
+    inline constexpr void MakePathGeneric(const T an_array, 
+      size_t array_size, std::vector<U>& result)
+    {
+      result.reserve(array_size / 2);
+      for (size_t i = 0; i < array_size; i +=2)
+#ifdef USINGZ
+        result.push_back( U{ an_array[i], an_array[i +1], 0} );
+#else
+        result.push_back( U{ an_array[i], an_array[i + 1]} );
+#endif
+    }
+
   } // end details namespace 
 
   inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
@@ -391,22 +404,6 @@ namespace Clipper2Lib {
     return true;
   }
 
-  namespace details {
-
-    template<typename T, typename U>
-    inline constexpr void MakePathGeneric(const T list, size_t size,
-      std::vector<U>& result)
-    {
-      for (size_t i = 0; i < size; ++i)
-#ifdef USINGZ
-        result[i / 2] = U{list[i], list[++i], 0};
-#else
-        result[i / 2] = U{list[i], list[++i]};
-#endif
-    }
-
-  } // end details namespace
-
   template<typename T,
     typename std::enable_if<
       std::is_integral<T>::value &&
@@ -417,7 +414,7 @@ namespace Clipper2Lib {
     const auto size = list.size() - list.size() % 2;
     if (list.size() != size)
       DoError(non_pair_error_i);  // non-fatal without exception handling
-    Path64 result(size / 2);      // else ignores unpaired value
+    Path64 result;
     details::MakePathGeneric(list, size, result);
     return result;
   }
@@ -431,7 +428,7 @@ namespace Clipper2Lib {
   {
     // Make the compiler error on unpaired value (i.e. no runtime effects).
     static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
-    Path64 result(N / 2);
+    Path64 result;
     details::MakePathGeneric(list, N, result);
     return result;
   }
@@ -446,7 +443,7 @@ namespace Clipper2Lib {
     const auto size = list.size() - list.size() % 2;
     if (list.size() != size)
       DoError(non_pair_error_i);  // non-fatal without exception handling
-    PathD result(size / 2);       // else ignores unpaired value
+    PathD result;
     details::MakePathGeneric(list, size, result);
     return result;
   }
@@ -460,7 +457,7 @@ namespace Clipper2Lib {
   {
     // Make the compiler error on unpaired value (i.e. no runtime effects).
     static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
-    PathD result(N / 2);
+    PathD result;
     details::MakePathGeneric(list, N, result);
     return result;
   }
@@ -653,7 +650,7 @@ namespace Clipper2Lib {
   }
 
   template <typename T>
-  inline Path<T> SimplifyPath(const Path<T> path, 
+  inline Path<T> SimplifyPath(const Path<T> &path, 
     double epsilon, bool isClosedPath = true)
   {
     const size_t len = path.size(), high = len -1;
@@ -662,7 +659,7 @@ namespace Clipper2Lib {
 
     std::vector<bool> flags(len);
     std::vector<double> distSqr(len);
-    size_t prior = high, curr = 0, start, next, prior2, next2;
+    size_t prior = high, curr = 0, start, next, prior2;
     if (isClosedPath)
     {
       distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
@@ -692,26 +689,25 @@ namespace Clipper2Lib {
       next = GetNext(curr, high, flags);
       if (next == prior) break;
 
+      // flag for removal the smaller of adjacent 'distances'
       if (distSqr[next] < distSqr[curr])
       {
-        flags[next] = true;
-        next = GetNext(next, high, flags);
-        next2 = GetNext(next, high, flags);
-        distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
-        if (next != high || isClosedPath)
-          distSqr[next] = PerpendicDistFromLineSqrd(path[next], path[curr], path[next2]);
+        prior2 = prior;
+        prior = curr;
         curr = next;
+        next = GetNext(next, high, flags);
       }
       else
-      {
-        flags[curr] = true;
-        curr = next;
-        next = GetNext(next, high, flags);
         prior2 = GetPrior(prior, high, flags);
+        
+      flags[curr] = true;
+      curr = next;
+      next = GetNext(next, high, flags);
+
+      if (isClosedPath || ((curr != high) && (curr != 0)))
         distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
-        if (prior != 0 || isClosedPath)
-          distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
-      }
+      if (isClosedPath || ((prior != 0) && (prior != high)))
+        distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
     }
     Path<T> result;
     result.reserve(len);
@@ -721,7 +717,7 @@ namespace Clipper2Lib {
   }
 
   template <typename T>
-  inline Paths<T> SimplifyPaths(const Paths<T> paths, 
+  inline Paths<T> SimplifyPaths(const Paths<T> &paths, 
     double epsilon, bool isClosedPath = true)
   {
     Paths<T> result;
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h
index 71c221bb50..ebddd08a59 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.minkowski.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  28 January 2023                                                 *
+* Date      :  1 November 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  Minkowski Sum and Difference                                    *
@@ -13,7 +13,7 @@
 #include <cstdlib>
 #include <vector>
 #include <string>
-#include "clipper.core.h"
+#include "clipper2/clipper.core.h"
 
 namespace Clipper2Lib 
 {
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h
index 8835fb0f45..30992bfa55 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.offset.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  15 May 2023                                                     *
+* Date      :  19 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
@@ -15,7 +15,9 @@
 
 namespace Clipper2Lib {
 
-enum class JoinType { Square, Round, Miter };
+enum class JoinType { Square, Bevel, Round, Miter };
+//Square : Joins are 'squared' at exactly the offset distance (more complex code)
+//Bevel  : Similar to Square, but the offset distance varies with angle (simple code & faster)
 
 enum class EndType {Polygon, Joined, Butt, Square, Round};
 //Butt   : offsets both sides of a path, with square blunt ends
@@ -32,13 +34,13 @@ private:
 	class Group {
 	public:
 		Paths64 paths_in;
-		Paths64 paths_out;
-		Path64 path;
+		std::vector<bool> is_hole_list;
+		std::vector<Rect64> bounds_list;
+		int lowest_path_idx = -1;
 		bool is_reversed = false;
 		JoinType join_type;
 		EndType end_type;
-		Group(const Paths64& _paths, JoinType _join_type, EndType _end_type) :
-			paths_in(_paths), join_type(_join_type), end_type(_end_type) {}
+		Group(const Paths64& _paths, JoinType _join_type, EndType _end_type);
 	};
 
 	int   error_code_ = 0;
@@ -49,9 +51,10 @@ private:
 	double step_sin_ = 0.0;
 	double step_cos_ = 0.0;
 	PathD norms;
+	Path64 path_out;
 	Paths64 solution;
 	std::vector<Group> groups_;
-	JoinType join_type_ = JoinType::Square;
+	JoinType join_type_ = JoinType::Bevel;
 	EndType end_type_ = EndType::Polygon;
 
 	double miter_limit_ = 0.0;
@@ -64,14 +67,17 @@ private:
 #endif
 	DeltaCallback64 deltaCallback64_ = nullptr;
 
-	void DoSquare(Group& group, const Path64& path, size_t j, size_t k);
-	void DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a);
-	void DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle);
+	size_t CalcSolutionCapacity();
+	bool CheckReverseOrientation();
+	void DoBevel(const Path64& path, size_t j, size_t k);
+	void DoSquare(const Path64& path, size_t j, size_t k);
+	void DoMiter(const Path64& path, size_t j, size_t k, double cos_a);
+	void DoRound(const Path64& path, size_t j, size_t k, double angle);
 	void BuildNormals(const Path64& path);
-	void OffsetPolygon(Group& group, Path64& path);
-	void OffsetOpenJoined(Group& group, Path64& path);
-	void OffsetOpenPath(Group& group, Path64& path);
-	void OffsetPoint(Group& group, Path64& path, size_t j, size_t k);
+	void OffsetPolygon(Group& group, const Path64& path);
+	void OffsetOpenJoined(Group& group, const Path64& path);
+	void OffsetOpenPath(Group& group, const Path64& path);
+	void OffsetPoint(Group& group, const Path64& path, size_t j, size_t k);
 	void DoGroupOffset(Group &group);
 	void ExecuteInternal(double delta);
 public:
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h
index bcbe7f436c..ff043f25f0 100644
--- a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.rectclip.h
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  30 May 2023                                                     *
+* Date      :  1 November 2023                                                 *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  FAST rectangular clipping                                       *
@@ -13,8 +13,7 @@
 #include <cstdlib>
 #include <vector>
 #include <queue>
-#include "clipper.h"
-#include "clipper.core.h"
+#include "clipper2/clipper.core.h"
 
 namespace Clipper2Lib
 {
diff --git a/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h
new file mode 100644
index 0000000000..d7644067e2
--- /dev/null
+++ b/thirdparty/clipper2/Clipper2Lib/include/clipper2/clipper.version.h
@@ -0,0 +1,6 @@
+#ifndef CLIPPER_VERSION_H
+#define CLIPPER_VERSION_H
+
+constexpr auto CLIPPER2_VERSION = "1.3.0";
+
+#endif  // CLIPPER_VERSION_H
diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp
index bed48b1541..9358b74b70 100644
--- a/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp
+++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.engine.cpp
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  27 August 2023                                                  *
+* Date      :  22 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  This is the main polygon clipping module                        *
@@ -15,6 +15,7 @@
 #include <algorithm>
 
 #include "clipper2/clipper.engine.h"
+#include "clipper2/clipper.h"
 
 // https://github.com/AngusJohnson/Clipper2/discussions/334
 // #discussioncomment-4248602
@@ -1469,13 +1470,14 @@ namespace Clipper2Lib {
     e2.outrec->front_edge = nullptr;
     e2.outrec->back_edge = nullptr;
     e2.outrec->pts = nullptr;
-    SetOwner(e2.outrec, e1.outrec);
 
     if (IsOpenEnd(e1))
     {
       e2.outrec->pts = e1.outrec->pts;
       e1.outrec->pts = nullptr;
     }
+    else 
+      SetOwner(e2.outrec, e1.outrec);
 
     //and e1 and e2 are maxima and are about to be dropped from the Actives list.
     e1.outrec = nullptr;
@@ -1541,7 +1543,7 @@ namespace Clipper2Lib {
       //NB if preserveCollinear == true, then only remove 180 deg. spikes
       if ((CrossProduct(op2->prev->pt, op2->pt, op2->next->pt) == 0) &&
         (op2->pt == op2->prev->pt ||
-          op2->pt == op2->next->pt || !PreserveCollinear ||
+          op2->pt == op2->next->pt || !preserve_collinear_ ||
           DotProduct(op2->prev->pt, op2->pt, op2->next->pt) < 0))
       {
 
@@ -1706,6 +1708,28 @@ namespace Clipper2Lib {
     return op;
   }
 
+  inline void TrimHorz(Active& horzEdge, bool preserveCollinear)
+  {
+    bool wasTrimmed = false;
+    Point64 pt = NextVertex(horzEdge)->pt;
+    while (pt.y == horzEdge.top.y)
+    {
+      //always trim 180 deg. spikes (in closed paths)
+      //but otherwise break if preserveCollinear = true
+      if (preserveCollinear &&
+        ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))
+        break;
+
+      horzEdge.vertex_top = NextVertex(horzEdge);
+      horzEdge.top = pt;
+      wasTrimmed = true;
+      if (IsMaxima(horzEdge)) break;
+      pt = NextVertex(horzEdge)->pt;
+    }
+
+    if (wasTrimmed) SetDx(horzEdge); // +/-infinity
+  }
+
 
   inline void ClipperBase::UpdateEdgeIntoAEL(Active* e)
   {
@@ -1717,9 +1741,13 @@ namespace Clipper2Lib {
 
     if (IsJoined(*e)) Split(*e, e->bot);
 
-    if (IsHorizontal(*e)) return;
-    InsertScanline(e->top.y);
+    if (IsHorizontal(*e))
+    {
+      if (!IsOpen(*e)) TrimHorz(*e, preserve_collinear_);
+      return;
+    }
 
+    InsertScanline(e->top.y);
     CheckJoinLeft(*e, e->bot);
     CheckJoinRight(*e, e->bot, true); // (#500)
   }
@@ -2139,7 +2167,7 @@ namespace Clipper2Lib {
       horz_seg_list_.end(),
       [](HorzSegment& hs) { return UpdateHorzSegment(hs); });
     if (j < 2) return;
-    std::sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());
+    std::stable_sort(horz_seg_list_.begin(), horz_seg_list_.end(), HorzSegSorter());
 
     HorzSegmentList::iterator hs1 = horz_seg_list_.begin(), hs2;
     HorzSegmentList::iterator hs_end = hs1 +j;
@@ -2451,35 +2479,6 @@ namespace Clipper2Lib {
     }
   }
 
-  inline bool HorzIsSpike(const Active& horzEdge)
-  {
-    Point64 nextPt = NextVertex(horzEdge)->pt;
-    return (nextPt.y == horzEdge.bot.y) &&
-      (horzEdge.bot.x < horzEdge.top.x) != (horzEdge.top.x < nextPt.x);
-  }
-
-  inline void TrimHorz(Active& horzEdge, bool preserveCollinear)
-  {
-    bool wasTrimmed = false;
-    Point64 pt = NextVertex(horzEdge)->pt;
-    while (pt.y == horzEdge.top.y)
-    {
-      //always trim 180 deg. spikes (in closed paths)
-      //but otherwise break if preserveCollinear = true
-      if (preserveCollinear &&
-        ((pt.x < horzEdge.top.x) != (horzEdge.bot.x < horzEdge.top.x)))
-        break;
-
-      horzEdge.vertex_top = NextVertex(horzEdge);
-      horzEdge.top = pt;
-      wasTrimmed = true;
-      if (IsMaxima(horzEdge)) break;
-      pt = NextVertex(horzEdge)->pt;
-    }
-
-    if (wasTrimmed) SetDx(horzEdge); // +/-infinity
-  }
-
   void ClipperBase::DoHorizontal(Active& horz)
     /*******************************************************************************
         * Notes: Horizontal edges (HEs) at scanline intersections (ie at the top or    *
@@ -2505,10 +2504,10 @@ namespace Clipper2Lib {
     else
       vertex_max = GetCurrYMaximaVertex(horz);
 
-    // remove 180 deg.spikes and also simplify
-    // consecutive horizontals when PreserveCollinear = true
-    if (vertex_max && !horzIsOpen && vertex_max != horz.vertex_top)
-      TrimHorz(horz, PreserveCollinear);
+    //// remove 180 deg.spikes and also simplify
+    //// consecutive horizontals when PreserveCollinear = true
+    //if (!horzIsOpen && vertex_max != horz.vertex_top)
+    //  TrimHorz(horz, PreserveCollinear);
 
     int64_t horz_left, horz_right;
     bool is_left_to_right =
@@ -2537,6 +2536,9 @@ namespace Clipper2Lib {
           if (IsHotEdge(horz) && IsJoined(*e))
             Split(*e, e->top);
 
+          //if (IsHotEdge(horz) != IsHotEdge(*e)) 
+          //  DoError(undefined_error_i);
+
           if (IsHotEdge(horz))
           {
             while (horz.vertex_top != vertex_max)
@@ -2591,6 +2593,7 @@ namespace Clipper2Lib {
         {
           IntersectEdges(horz, *e, pt);
           SwapPositionsInAEL(horz, *e);
+          CheckJoinLeft(*e, pt);
           horz.curr_x = e->curr_x;
           e = horz.next_in_ael;
         }
@@ -2598,6 +2601,7 @@ namespace Clipper2Lib {
         {
           IntersectEdges(*e, horz, pt);
           SwapPositionsInAEL(*e, horz);
+          CheckJoinRight(*e, pt);
           horz.curr_x = e->curr_x;
           e = horz.prev_in_ael;
         }
@@ -2633,9 +2637,6 @@ namespace Clipper2Lib {
         AddOutPt(horz, horz.top);
       UpdateEdgeIntoAEL(&horz);
 
-      if (PreserveCollinear && !horzIsOpen && HorzIsSpike(horz))
-        TrimHorz(horz, true);
-
       is_left_to_right =
         ResetHorzDirection(horz, vertex_max, horz_left, horz_right);
     }
@@ -2872,7 +2873,7 @@ namespace Clipper2Lib {
     if (!outrec->bounds.IsEmpty()) return true;
     CleanCollinear(outrec);
     if (!outrec->pts || 
-      !BuildPath64(outrec->pts, ReverseSolution, false, outrec->path)){ 
+      !BuildPath64(outrec->pts, reverse_solution_, false, outrec->path)){ 
         return false;}
     outrec->bounds = GetBounds(outrec->path);
     return true;
@@ -2947,7 +2948,7 @@ namespace Clipper2Lib {
       Path64 path;
       if (solutionOpen && outrec->is_open)
       {
-        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+        if (BuildPath64(outrec->pts, reverse_solution_, true, path))
           solutionOpen->emplace_back(std::move(path));
       }
       else
@@ -2955,7 +2956,7 @@ namespace Clipper2Lib {
         // nb: CleanCollinear can add to outrec_list_
         CleanCollinear(outrec);
         //closed paths should always return a Positive orientation
-        if (BuildPath64(outrec->pts, ReverseSolution, false, path))
+        if (BuildPath64(outrec->pts, reverse_solution_, false, path))
           solutionClosed.emplace_back(std::move(path));
       }
     }
@@ -2978,7 +2979,7 @@ namespace Clipper2Lib {
       if (outrec->is_open)
       {
         Path64 path;
-        if (BuildPath64(outrec->pts, ReverseSolution, true, path))
+        if (BuildPath64(outrec->pts, reverse_solution_, true, path))
           open_paths.push_back(path);
         continue;
       }
@@ -3055,14 +3056,14 @@ namespace Clipper2Lib {
       PathD path;
       if (solutionOpen && outrec->is_open)
       {
-        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+        if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))
           solutionOpen->emplace_back(std::move(path));
       }
       else
       {
         CleanCollinear(outrec);
         //closed paths should always return a Positive orientation
-        if (BuildPathD(outrec->pts, ReverseSolution, false, path, invScale_))
+        if (BuildPathD(outrec->pts, reverse_solution_, false, path, invScale_))
           solutionClosed.emplace_back(std::move(path));
       }
     }
@@ -3084,7 +3085,7 @@ namespace Clipper2Lib {
       if (outrec->is_open)
       {
         PathD path;
-        if (BuildPathD(outrec->pts, ReverseSolution, true, path, invScale_))
+        if (BuildPathD(outrec->pts, reverse_solution_, true, path, invScale_))
           open_paths.push_back(path);
         continue;
       }
diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp
index 5faa26176d..0282aa49bb 100644
--- a/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp
+++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.offset.cpp
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  7 August 2023                                                   *
+* Date      :  28 November 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  Path Offset (Inflate/Shrink)                                    *
@@ -20,38 +20,63 @@ const double floating_point_tolerance = 1e-12;
 // Miscellaneous methods
 //------------------------------------------------------------------------------
 
-void GetBoundsAndLowestPolyIdx(const Paths64& paths, Rect64& r, int & idx)
+inline bool ToggleBoolIf(bool val, bool condition)
 {
-	idx = -1;
-	r = MaxInvalidRect64;
-	int64_t lpx = 0;
-	for (int i = 0; i < static_cast<int>(paths.size()); ++i)
-		for (const Point64& p : paths[i])
-		{
-			if (p.y >= r.bottom)
-			{
-				if (p.y > r.bottom || p.x < lpx)
-				{
-					idx = i;
-					lpx = p.x;
-					r.bottom = p.y;
-				}
-			}
-			else if (p.y < r.top) r.top = p.y;
-			if (p.x > r.right) r.right = p.x;
-			else if (p.x < r.left) r.left = p.x;
-		}
-	//if (idx < 0) r = Rect64(0, 0, 0, 0);
-	//if (r.top == INT64_MIN) r.bottom = r.top;
-	//if (r.left == INT64_MIN) r.left = r.right;
+	return condition ? !val : val;
 }
 
-bool IsSafeOffset(const Rect64& r, double abs_delta)
+void GetMultiBounds(const Paths64& paths, std::vector<Rect64>& recList)
 {
-	return r.left > min_coord + abs_delta &&
-		r.right < max_coord - abs_delta &&
-		r.top > min_coord + abs_delta &&
-		r.bottom < max_coord - abs_delta;
+	recList.reserve(paths.size());
+	for (const Path64& path : paths)
+	{ 
+		if (path.size() < 1)
+		{
+			recList.push_back(InvalidRect64);
+			continue;
+		}
+		int64_t x = path[0].x, y = path[0].y;
+		Rect64 r = Rect64(x, y, x, y);
+		for (const Point64& pt : path)
+		{
+			if (pt.y > r.bottom) r.bottom = pt.y;
+			else if (pt.y < r.top) r.top = pt.y;
+			if (pt.x > r.right) r.right = pt.x;
+			else if (pt.x < r.left) r.left = pt.x;
+		}
+		recList.push_back(r);
+	}
+}
+
+bool ValidateBounds(std::vector<Rect64>& recList, double delta)
+{
+	int64_t int_delta = static_cast<int64_t>(delta);
+	int64_t big = MAX_COORD - int_delta;
+	int64_t small = MIN_COORD + int_delta;
+	for (const Rect64& r : recList)
+	{
+		if (!r.IsValid()) continue; // ignore invalid paths
+		else if (r.left < small || r.right > big ||
+			r.top < small || r.bottom > big) return false;
+	}
+	return true;
+}
+
+int GetLowestClosedPathIdx(std::vector<Rect64>& boundsList)
+{
+	int i = -1, result = -1;
+	Point64 botPt = Point64(INT64_MAX, INT64_MIN);
+	for (const Rect64& r : boundsList)
+	{		
+		++i;
+		if (!r.IsValid()) continue; // ignore invalid paths
+		else if (r.bottom > botPt.y || (r.bottom == botPt.y && r.left < botPt.x))
+		{
+			botPt = Point64(r.left, r.bottom);
+			result = static_cast<int>(i);
+		}
+	}
+	return result;
 }
 
 PointD GetUnitNormal(const Point64& pt1, const Point64& pt2)
@@ -125,6 +150,44 @@ inline void NegatePath(PathD& path)
 	}
 }
 
+
+//------------------------------------------------------------------------------
+// ClipperOffset::Group methods
+//------------------------------------------------------------------------------
+
+ClipperOffset::Group::Group(const Paths64& _paths, JoinType _join_type, EndType _end_type):
+	paths_in(_paths), join_type(_join_type), end_type(_end_type)
+{
+	bool is_joined =
+		(end_type == EndType::Polygon) ||
+		(end_type == EndType::Joined);
+	for (Path64& p: paths_in)
+	  StripDuplicates(p, is_joined);
+
+	// get bounds of each path --> bounds_list
+	GetMultiBounds(paths_in, bounds_list);
+
+	if (end_type == EndType::Polygon)
+	{
+		is_hole_list.reserve(paths_in.size());
+		for (const Path64& path : paths_in)
+			is_hole_list.push_back(Area(path) < 0);
+		lowest_path_idx = GetLowestClosedPathIdx(bounds_list);
+		// the lowermost path must be an outer path, so if its orientation is negative,
+		// then flag the whole group is 'reversed' (will negate delta etc.)
+		// as this is much more efficient than reversing every path.
+		is_reversed = (lowest_path_idx >= 0) && is_hole_list[lowest_path_idx];
+		if (is_reversed) is_hole_list.flip();
+	}
+	else
+	{
+		lowest_path_idx = -1;
+		is_reversed = false;
+		is_hole_list.resize(paths_in.size());
+	}
+}
+
+
 //------------------------------------------------------------------------------
 // ClipperOffset methods
 //------------------------------------------------------------------------------
@@ -147,10 +210,10 @@ void ClipperOffset::BuildNormals(const Path64& path)
 	norms.clear();
 	norms.reserve(path.size());
 	if (path.size() == 0) return;
-	Path64::const_iterator path_iter, path_last_iter = --path.cend();
-	for (path_iter = path.cbegin(); path_iter != path_last_iter; ++path_iter)
+	Path64::const_iterator path_iter, path_stop_iter = --path.cend();
+	for (path_iter = path.cbegin(); path_iter != path_stop_iter; ++path_iter)
 		norms.push_back(GetUnitNormal(*path_iter,*(path_iter +1)));
-	norms.push_back(GetUnitNormal(*path_last_iter, *(path.cbegin())));
+	norms.push_back(GetUnitNormal(*path_stop_iter, *(path.cbegin())));
 }
 
 inline PointD TranslatePoint(const PointD& pt, double dx, double dy)
@@ -200,7 +263,25 @@ PointD IntersectPoint(const PointD& pt1a, const PointD& pt1b,
 	}
 }
 
-void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t k)
+void ClipperOffset::DoBevel(const Path64& path, size_t j, size_t k)
+{
+	PointD pt1, pt2;
+	if (j == k)
+	{
+		double abs_delta = std::abs(group_delta_);
+		pt1 = PointD(path[j].x - abs_delta * norms[j].x, path[j].y - abs_delta * norms[j].y);
+		pt2 = PointD(path[j].x + abs_delta * norms[j].x, path[j].y + abs_delta * norms[j].y);
+	} 
+	else
+	{
+		pt1 = PointD(path[j].x + group_delta_ * norms[k].x, path[j].y + group_delta_ * norms[k].y);
+		pt2 = PointD(path[j].x + group_delta_ * norms[j].x, path[j].y + group_delta_ * norms[j].y);
+	}
+	path_out.push_back(Point64(pt1));
+	path_out.push_back(Point64(pt2));
+}
+
+void ClipperOffset::DoSquare(const Path64& path, size_t j, size_t k)
 {
 	PointD vec;
 	if (j == k) 
@@ -228,8 +309,8 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
 		pt.z = ptQ.z;
 #endif
 		//get the second intersect point through reflecion
-		group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
-		group.path.push_back(Point64(pt));
+		path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
+		path_out.push_back(Point64(pt));
 	}
 	else
 	{
@@ -238,28 +319,28 @@ void ClipperOffset::DoSquare(Group& group, const Path64& path, size_t j, size_t
 #ifdef USINGZ
 		pt.z = ptQ.z;
 #endif
-		group.path.push_back(Point64(pt));
+		path_out.push_back(Point64(pt));
 		//get the second intersect point through reflecion
-		group.path.push_back(Point64(ReflectPoint(pt, ptQ)));
+		path_out.push_back(Point64(ReflectPoint(pt, ptQ)));
 	}
 }
 
-void ClipperOffset::DoMiter(Group& group, const Path64& path, size_t j, size_t k, double cos_a)
+void ClipperOffset::DoMiter(const Path64& path, size_t j, size_t k, double cos_a)
 {
 	double q = group_delta_ / (cos_a + 1);
 #ifdef USINGZ
-	group.path.push_back(Point64(
+	path_out.push_back(Point64(
 		path[j].x + (norms[k].x + norms[j].x) * q,
 		path[j].y + (norms[k].y + norms[j].y) * q,
 		path[j].z));
 #else
-	group.path.push_back(Point64(
+	path_out.push_back(Point64(
 		path[j].x + (norms[k].x + norms[j].x) * q,
 		path[j].y + (norms[k].y + norms[j].y) * q));
 #endif
 }
 
-void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k, double angle)
+void ClipperOffset::DoRound(const Path64& path, size_t j, size_t k, double angle)
 {
 	if (deltaCallback64_) {
 		// when deltaCallback64_ is assigned, group_delta_ won't be constant, 
@@ -280,29 +361,25 @@ void ClipperOffset::DoRound(Group& group, const Path64& path, size_t j, size_t k
 
 	if (j == k) offsetVec.Negate();
 #ifdef USINGZ
-	group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+	path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
 #else
-	group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+	path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
 #endif
-	if (angle > -PI + 0.01)	// avoid 180deg concave
+	int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
+	for (int i = 1; i < steps; ++i) // ie 1 less than steps
 	{
-		int steps = static_cast<int>(std::ceil(steps_per_rad_ * std::abs(angle))); // #448, #456
-		for (int i = 1; i < steps; ++i) // ie 1 less than steps
-		{
-			offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
-				offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
+		offsetVec = PointD(offsetVec.x * step_cos_ - step_sin_ * offsetVec.y,
+			offsetVec.x * step_sin_ + offsetVec.y * step_cos_);
 #ifdef USINGZ
-			group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
+		path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y, pt.z));
 #else
-			group.path.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
+		path_out.push_back(Point64(pt.x + offsetVec.x, pt.y + offsetVec.y));
 #endif
-
-		}
 	}
-	group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+	path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
 }
 
-void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k)
+void ClipperOffset::OffsetPoint(Group& group, const Path64& path, size_t j, size_t k)
 {
 	// Let A = change in angle where edges join
 	// A == 0: ie no change in angle (flat join)
@@ -323,57 +400,51 @@ void ClipperOffset::OffsetPoint(Group& group, Path64& path, size_t j, size_t k)
 	}
 	if (std::fabs(group_delta_) <= floating_point_tolerance)
 	{
-		group.path.push_back(path[j]);
+		path_out.push_back(path[j]);
 		return;
 	}
 
-	if (cos_a > 0.999) // almost straight - less than 2.5 degree (#424, #526) 
-	{
-		DoMiter(group, path, j, k, cos_a);
-	}
-	else if (cos_a > -0.99 && (sin_a * group_delta_ < 0))
+	if (cos_a > -0.99 && (sin_a * group_delta_ < 0)) // test for concavity first (#593)
 	{
 		// is concave
-		group.path.push_back(GetPerpendic(path[j], norms[k], group_delta_));
+		path_out.push_back(GetPerpendic(path[j], norms[k], group_delta_));
 		// this extra point is the only (simple) way to ensure that
 	  // path reversals are fully cleaned with the trailing clipper		
-		group.path.push_back(path[j]); // (#405)
-		group.path.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+		path_out.push_back(path[j]); // (#405)
+		path_out.push_back(GetPerpendic(path[j], norms[j], group_delta_));
+	}
+	else if (cos_a > 0.999 && join_type_ != JoinType::Round) 
+	{
+		// almost straight - less than 2.5 degree (#424, #482, #526 & #724) 
+		DoMiter(path, j, k, cos_a);
 	}
 	else if (join_type_ == JoinType::Miter)
 	{
-		// miter unless the angle is so acute the miter would exceeds ML
-		if (cos_a > temp_lim_ - 1) DoMiter(group, path, j, k, cos_a);
-		else DoSquare(group, path, j, k);
+		// miter unless the angle is sufficiently acute to exceed ML
+		if (cos_a > temp_lim_ - 1) DoMiter(path, j, k, cos_a);
+		else DoSquare(path, j, k);
 	}
-	else if (cos_a > 0.99 || join_type_ == JoinType::Square) // 0.99 ~= 8.1 deg.
-		DoSquare(group, path, j, k);
+	else if (join_type_ == JoinType::Round)
+		DoRound(path, j, k, std::atan2(sin_a, cos_a));
+	else if ( join_type_ == JoinType::Bevel)
+		DoBevel(path, j, k);
 	else
-		DoRound(group, path, j, k, std::atan2(sin_a, cos_a));
+		DoSquare(path, j, k);
 }
 
-void ClipperOffset::OffsetPolygon(Group& group, Path64& path)
+void ClipperOffset::OffsetPolygon(Group& group, const Path64& path)
 {
-	// when the path is contracting, make sure 
-	// there is sufficient space to do so.                //#593
-	// nb: this will have a small impact on performance
-	double a = Area(path);
-	// contracting when orientation is opposite offset direction
-	if ((a < 0) != (group_delta_ < 0)) 
-	{
-		Rect64 rec = GetBounds(path);
-		if (std::fabs(group_delta_) * 2 > rec.Width()) return;
-	}
-
+	path_out.clear();
 	for (Path64::size_type j = 0, k = path.size() -1; j < path.size(); k = j, ++j)
 		OffsetPoint(group, path, j, k);
-	group.paths_out.push_back(group.path);
+	solution.push_back(path_out);
 }
 
-void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
+void ClipperOffset::OffsetOpenJoined(Group& group, const Path64& path)
 {
 	OffsetPolygon(group, path);
-	std::reverse(path.begin(), path.end());
+	Path64 reverse_path(path);
+	std::reverse(reverse_path.begin(), reverse_path.end());
 	
 	//rebuild normals // BuildNormals(path);
 	std::reverse(norms.begin(), norms.end());
@@ -381,39 +452,28 @@ void ClipperOffset::OffsetOpenJoined(Group& group, Path64& path)
 	norms.erase(norms.begin());
 	NegatePath(norms);
 
-	group.path.clear();
-	OffsetPolygon(group, path);
+	OffsetPolygon(group, reverse_path);
 }
 
-void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
+void ClipperOffset::OffsetOpenPath(Group& group, const Path64& path)
 {
 	// do the line start cap
 	if (deltaCallback64_) group_delta_ = deltaCallback64_(path, norms, 0, 0);
 	
 	if (std::fabs(group_delta_) <= floating_point_tolerance)
-		group.path.push_back(path[0]);
+		path_out.push_back(path[0]);
 	else
 	{
 		switch (end_type_)
 		{
 		case EndType::Butt:
-#ifdef USINGZ
-			group.path.push_back(Point64(
-				path[0].x - norms[0].x * group_delta_,
-				path[0].y - norms[0].y * group_delta_,
-				path[0].z));
-#else
-			group.path.push_back(Point64(
-				path[0].x - norms[0].x * group_delta_,
-				path[0].y - norms[0].y * group_delta_));
-#endif
-			group.path.push_back(GetPerpendic(path[0], norms[0], group_delta_));
+			DoBevel(path, 0, 0);
 			break;
 		case EndType::Round:
-			DoRound(group, path, 0, 0, PI);
+			DoRound(path, 0, 0, PI);
 			break;
 		default:
-			DoSquare(group, path, 0, 0);
+			DoSquare(path, 0, 0);
 			break;
 		}
 	}
@@ -433,64 +493,42 @@ void ClipperOffset::OffsetOpenPath(Group& group, Path64& path)
 		group_delta_ = deltaCallback64_(path, norms, highI, highI);
 
 	if (std::fabs(group_delta_) <= floating_point_tolerance)
-		group.path.push_back(path[highI]);
+		path_out.push_back(path[highI]);
 	else
 	{
 		switch (end_type_)
 		{
 		case EndType::Butt:
-#ifdef USINGZ
-			group.path.push_back(Point64(
-				path[highI].x - norms[highI].x * group_delta_,
-				path[highI].y - norms[highI].y * group_delta_,
-				path[highI].z));
-#else
-			group.path.push_back(Point64(
-				path[highI].x - norms[highI].x * group_delta_,
-				path[highI].y - norms[highI].y * group_delta_));
-#endif
-			group.path.push_back(GetPerpendic(path[highI], norms[highI], group_delta_));
+			DoBevel(path, highI, highI);
 			break;
 		case EndType::Round:
-			DoRound(group, path, highI, highI, PI);
+			DoRound(path, highI, highI, PI);
 			break;
 		default:
-			DoSquare(group, path, highI, highI);
+			DoSquare(path, highI, highI);
 			break;
 		}
 	}
 
 	for (size_t j = highI, k = 0; j > 0; k = j, --j)
 		OffsetPoint(group, path, j, k);
-	group.paths_out.push_back(group.path);
+	solution.push_back(path_out);
 }
 
 void ClipperOffset::DoGroupOffset(Group& group)
 {
-	Rect64 r;
-	int idx = -1;
-	//the lowermost polygon must be an outer polygon. So we can use that as the
-	//designated orientation for outer polygons (needed for tidy-up clipping)
-	GetBoundsAndLowestPolyIdx(group.paths_in, r, idx);
-	if (idx < 0) return;
-
 	if (group.end_type == EndType::Polygon)
 	{
-		double area = Area(group.paths_in[idx]);
-		//if (area == 0) return; // probably unhelpful (#430)
-		group.is_reversed = (area < 0);
-		if (group.is_reversed) group_delta_ = -delta_;
-		else group_delta_ = delta_;
-	} 
-	else
-	{
-		group.is_reversed = false;
-		group_delta_ = std::abs(delta_) * 0.5;
+		// a straight path (2 points) can now also be 'polygon' offset 
+		// where the ends will be treated as (180 deg.) joins
+		if (group.lowest_path_idx < 0) delta_ = std::abs(delta_);
+		group_delta_ = (group.is_reversed) ? -delta_ : delta_;
 	}
+	else
+		group_delta_ = std::abs(delta_);// *0.5;
 
 	double abs_delta = std::fabs(group_delta_);
-	// do range checking
-	if (!IsSafeOffset(r, abs_delta))
+	if (!ValidateBounds(group.bounds_list, abs_delta))
 	{
 		DoError(range_error_i);
 		error_code_ |= range_error_i;
@@ -500,10 +538,9 @@ void ClipperOffset::DoGroupOffset(Group& group)
 	join_type_	= group.join_type;
 	end_type_ = group.end_type;
 
-	if (!deltaCallback64_ && 
-		(group.join_type == JoinType::Round || group.end_type == EndType::Round))
+	if (group.join_type == JoinType::Round || group.end_type == EndType::Round)
 	{
-		//calculate a sensible number of steps (for 360 deg for the given offset)
+		// calculate a sensible number of steps (for 360 deg for the given offset)
 		// arcTol - when arc_tolerance_ is undefined (0), the amount of 
 		// curve imprecision that's allowed is based on the size of the 
 		// offset (delta). Obviously very large offsets will almost always 
@@ -519,61 +556,80 @@ void ClipperOffset::DoGroupOffset(Group& group)
 		steps_per_rad_ = steps_per_360 / (2 * PI);
 	}
 
-	bool is_joined =
-		(end_type_ == EndType::Polygon) ||
-		(end_type_ == EndType::Joined);
-	Paths64::iterator path_iter;
-	for(path_iter = group.paths_in.begin(); path_iter != group.paths_in.end(); ++path_iter)
+	std::vector<Rect64>::const_iterator path_rect_it = group.bounds_list.cbegin();
+	std::vector<bool>::const_iterator is_hole_it = group.is_hole_list.cbegin();
+	Paths64::const_iterator path_in_it = group.paths_in.cbegin();
+	for ( ; path_in_it != group.paths_in.cend(); ++path_in_it, ++path_rect_it, ++is_hole_it)
 	{
-		Path64 &path = *path_iter;
-		StripDuplicates(path, is_joined);
-		Path64::size_type cnt = path.size();
-		if (cnt == 0 || ((cnt < 3) && group.end_type == EndType::Polygon)) 
-			continue;
+		if (!path_rect_it->IsValid()) continue;
+		Path64::size_type pathLen = path_in_it->size();
+		path_out.clear();
 
-		group.path.clear();
-		if (cnt == 1) // single point - only valid with open paths
+		if (pathLen == 1) // single point
 		{
 			if (group_delta_ < 1) continue;
+			const Point64& pt = (*path_in_it)[0];
 			//single vertex so build a circle or square ...
 			if (group.join_type == JoinType::Round)
 			{
 				double radius = abs_delta;
-				group.path = Ellipse(path[0], radius, radius);
+				int steps = static_cast<int>(std::ceil(steps_per_rad_ * 2 * PI)); //#617
+				path_out = Ellipse(pt, radius, radius, steps);
 #ifdef USINGZ
-				for (auto& p : group.path) p.z = path[0].z;
+				for (auto& p : path_out) p.z = pt.z;
 #endif
 			}
 			else
 			{
 				int d = (int)std::ceil(abs_delta);
-				r = Rect64(path[0].x - d, path[0].y - d, path[0].x + d, path[0].y + d);
-				group.path = r.AsPath();
+				Rect64 r = Rect64(pt.x - d, pt.y - d, pt.x + d, pt.y + d);
+				path_out = r.AsPath();
 #ifdef USINGZ
-				for (auto& p : group.path) p.z = path[0].z;
+				for (auto& p : path_out) p.z = pt.z;
 #endif
 			}
-			group.paths_out.push_back(group.path);
-		}
-		else
-		{
-			if ((cnt == 2) && (group.end_type == EndType::Joined))
-			{
-				if (group.join_type == JoinType::Round)
-					end_type_ = EndType::Round;
-				else
-					end_type_ = EndType::Square;
-			}
+			solution.push_back(path_out);
+			continue;
+		} // end of offsetting a single point 
 
-			BuildNormals(path);
-			if (end_type_ == EndType::Polygon) OffsetPolygon(group, path);
-			else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, path);
-			else OffsetOpenPath(group, path);
-		}
+		// when shrinking outer paths, make sure they can shrink this far (#593)
+		// also when shrinking holes, make sure they too can shrink this far (#715)
+		if ((group_delta_ > 0) == ToggleBoolIf(*is_hole_it, group.is_reversed) &&
+			(std::min(path_rect_it->Width(), path_rect_it->Height()) <= -group_delta_ * 2) )
+				  continue;
+
+		if ((pathLen == 2) && (group.end_type == EndType::Joined))
+			end_type_ = (group.join_type == JoinType::Round) ? 
+			  EndType::Round : 
+			  EndType::Square;
+
+		BuildNormals(*path_in_it);
+		if (end_type_ == EndType::Polygon) OffsetPolygon(group, *path_in_it);
+		else if (end_type_ == EndType::Joined) OffsetOpenJoined(group, *path_in_it);
+		else OffsetOpenPath(group, *path_in_it);
 	}
-	solution.reserve(solution.size() + group.paths_out.size());
-	copy(group.paths_out.begin(), group.paths_out.end(), back_inserter(solution));
-	group.paths_out.clear();
+}
+
+
+size_t ClipperOffset::CalcSolutionCapacity()
+{
+	size_t result = 0;
+	for (const Group& g : groups_)
+		result += (g.end_type == EndType::Joined) ? g.paths_in.size() * 2 : g.paths_in.size();
+	return result;
+}
+
+bool ClipperOffset::CheckReverseOrientation()
+{
+	// nb: this assumes there's consistency in orientation between groups
+	bool is_reversed_orientation = false;
+	for (const Group& g : groups_)
+		if (g.end_type == EndType::Polygon)
+		{
+			is_reversed_orientation = g.is_reversed;
+			break;
+		}
+	return is_reversed_orientation;
 }
 
 void ClipperOffset::ExecuteInternal(double delta)
@@ -581,29 +637,29 @@ void ClipperOffset::ExecuteInternal(double delta)
 	error_code_ = 0;
 	solution.clear();
 	if (groups_.size() == 0) return;
+	solution.reserve(CalcSolutionCapacity());
 
-	if (std::abs(delta) < 0.5)
+	if (std::abs(delta) < 0.5) // ie: offset is insignificant 
 	{
+		Paths64::size_type sol_size = 0;
+		for (const Group& group : groups_) sol_size += group.paths_in.size();
+		solution.reserve(sol_size);
 		for (const Group& group : groups_)
-		{
-			solution.reserve(solution.size() + group.paths_in.size());
 			copy(group.paths_in.begin(), group.paths_in.end(), back_inserter(solution));
-		}
-	} 
-	else
-	{
-		temp_lim_ = (miter_limit_ <= 1) ?
-			2.0 :
-			2.0 / (miter_limit_ * miter_limit_);
+		return;
+	}
 
-		delta_ = delta;
-		std::vector<Group>::iterator git;
-		for (git = groups_.begin(); git != groups_.end(); ++git)
-		{
-			DoGroupOffset(*git);
-			if (!error_code_) continue; // all OK
-			solution.clear();
-		}
+	temp_lim_ = (miter_limit_ <= 1) ?
+		2.0 :
+		2.0 / (miter_limit_ * miter_limit_);
+
+	delta_ = delta;
+	std::vector<Group>::iterator git;
+	for (git = groups_.begin(); git != groups_.end(); ++git)
+	{
+		DoGroupOffset(*git);
+		if (!error_code_) continue; // all OK
+		solution.clear();
 	}
 }
 
@@ -614,19 +670,17 @@ void ClipperOffset::Execute(double delta, Paths64& paths)
 	ExecuteInternal(delta);
 	if (!solution.size()) return;
 
-	paths = solution;
+	bool paths_reversed = CheckReverseOrientation();
 	//clean up self-intersections ...
 	Clipper64 c;
-	c.PreserveCollinear = false;
+	c.PreserveCollinear(false);
 	//the solution should retain the orientation of the input
-	c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
+	c.ReverseSolution(reverse_solution_ != paths_reversed);
 #ifdef USINGZ
-	if (zCallback64_) {
-		c.SetZCallback(zCallback64_);
-	}
+	if (zCallback64_) { c.SetZCallback(zCallback64_); }
 #endif
 	c.AddSubject(solution);
-	if (groups_[0].is_reversed)
+	if (paths_reversed)
 		c.Execute(ClipType::Union, FillRule::Negative, paths);
 	else
 		c.Execute(ClipType::Union, FillRule::Positive, paths);
@@ -640,18 +694,21 @@ void ClipperOffset::Execute(double delta, PolyTree64& polytree)
 	ExecuteInternal(delta);
 	if (!solution.size()) return;
 
+	bool paths_reversed = CheckReverseOrientation();
 	//clean up self-intersections ...
 	Clipper64 c;
-	c.PreserveCollinear = false;
+	c.PreserveCollinear(false);
 	//the solution should retain the orientation of the input
-	c.ReverseSolution = reverse_solution_ != groups_[0].is_reversed;
+	c.ReverseSolution (reverse_solution_ != paths_reversed);
 #ifdef USINGZ
 	if (zCallback64_) {
 		c.SetZCallback(zCallback64_);
 	}
 #endif
 	c.AddSubject(solution);
-	if (groups_[0].is_reversed)
+
+
+	if (paths_reversed)
 		c.Execute(ClipType::Union, FillRule::Negative, polytree);
 	else
 		c.Execute(ClipType::Union, FillRule::Positive, polytree);
diff --git a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
index 221cd9bb49..9aa0fc0f76 100644
--- a/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
+++ b/thirdparty/clipper2/Clipper2Lib/src/clipper.rectclip.cpp
@@ -1,6 +1,6 @@
 /*******************************************************************************
 * Author    :  Angus Johnson                                                   *
-* Date      :  6 August 2023                                                   *
+* Date      :  8 September 2023                                                *
 * Website   :  http://www.angusj.com                                           *
 * Copyright :  Angus Johnson 2010-2023                                         *
 * Purpose   :  FAST rectangular clipping                                       *
@@ -24,11 +24,11 @@ namespace Clipper2Lib {
     for (const Point64& pt : path2)
     {
       PointInPolygonResult pip = PointInPolygon(pt, path1);
-      switch (pip) 
+      switch (pip)
       {
       case PointInPolygonResult::IsOutside: ++io_count; break;
-        case PointInPolygonResult::IsInside: --io_count; break;
-        default: continue;
+      case PointInPolygonResult::IsInside: --io_count; break;
+      default: continue;
       }
       if (std::abs(io_count) > 1) break;
     }
@@ -66,6 +66,56 @@ namespace Clipper2Lib {
     return true;
   }
 
+  inline bool IsHorizontal(const Point64& pt1, const Point64& pt2)
+  {
+    return pt1.y == pt2.y;
+  }
+
+  inline bool GetSegmentIntersection(const Point64& p1,
+    const Point64& p2, const Point64& p3, const Point64& p4, Point64& ip)
+  {
+    double res1 = CrossProduct(p1, p3, p4);
+    double res2 = CrossProduct(p2, p3, p4);
+    if (res1 == 0)
+    {
+      ip = p1;
+      if (res2 == 0) return false; // segments are collinear
+      else if (p1 == p3 || p1 == p4) return true;
+      //else if (p2 == p3 || p2 == p4) { ip = p2; return true; }
+      else if (IsHorizontal(p3, p4)) return ((p1.x > p3.x) == (p1.x < p4.x));
+      else return ((p1.y > p3.y) == (p1.y < p4.y));
+    }
+    else if (res2 == 0)
+    {
+      ip = p2;
+      if (p2 == p3 || p2 == p4) return true;
+      else if (IsHorizontal(p3, p4)) return ((p2.x > p3.x) == (p2.x < p4.x));
+      else return ((p2.y > p3.y) == (p2.y < p4.y));
+    }
+    if ((res1 > 0) == (res2 > 0)) return false;
+
+    double res3 = CrossProduct(p3, p1, p2);
+    double res4 = CrossProduct(p4, p1, p2);
+    if (res3 == 0)
+    {
+      ip = p3;
+      if (p3 == p1 || p3 == p2) return true;
+      else if (IsHorizontal(p1, p2)) return ((p3.x > p1.x) == (p3.x < p2.x));
+      else return ((p3.y > p1.y) == (p3.y < p2.y));
+    }
+    else if (res4 == 0)
+    {
+      ip = p4;
+      if (p4 == p1 || p4 == p2) return true;
+      else if (IsHorizontal(p1, p2)) return ((p4.x > p1.x) == (p4.x < p2.x));
+      else return ((p4.y > p1.y) == (p4.y < p2.y));
+    }
+    if ((res3 > 0) == (res4 > 0)) return false;
+
+    // segments must intersect to get here
+    return GetIntersectPoint(p1, p2, p3, p4, ip);
+  }
+
   inline bool GetIntersection(const Path64& rectPath,
     const Point64& p, const Point64& p2, Location& loc, Point64& ip)
   {
@@ -74,100 +124,84 @@ namespace Clipper2Lib {
     switch (loc)
     {
     case Location::Left:
-      if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
-      else if (p.y < rectPath[0].y &&
-        SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
+      if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) return true;
+      else if ((p.y < rectPath[0].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))        
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
         loc = Location::Top;
+        return true;
       }
-      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
         loc = Location::Bottom;
+        return true;
       }
       else return false;
-      break;
 
     case Location::Top:
-      if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
-      else if (p.x < rectPath[0].x &&
-        SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
+      if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip)) return true;
+      else if ((p.x < rectPath[0].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
         loc = Location::Left;
+        return true;
       }
-      else if (p.x > rectPath[1].x &&
-        SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
         loc = Location::Right;
+        return true;
       }
       else return false;
-      break;
 
     case Location::Right:
-      if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
-        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
-      else if (p.y < rectPath[0].y &&
-        SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
+      if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip)) return true;
+      else if ((p.y < rectPath[1].y) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
         loc = Location::Top;
+        return true;
       }
-      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
         loc = Location::Bottom;
+        return true;
       }
       else return false;
-      break;
 
     case Location::Bottom:
-      if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
-        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
-      else if (p.x < rectPath[3].x &&
-        SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
+      if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip)) return true;
+      else if ((p.x < rectPath[3].x) && GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
         loc = Location::Left;
+        return true;
       }
-      else if (p.x > rectPath[2].x &&
-        SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
         loc = Location::Right;
+        return true;
       }
       else return false;
-      break;
 
     default: // loc == rInside
-      if (SegmentsIntersect(p, p2, rectPath[0], rectPath[3], true))
+      if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[3], ip)) 
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[3], ip);
         loc = Location::Left;
+        return true;
       }
-      else if (SegmentsIntersect(p, p2, rectPath[0], rectPath[1], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[0], rectPath[1], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[0], rectPath[1], ip);
         loc = Location::Top;
+        return true;
       }
-      else if (SegmentsIntersect(p, p2, rectPath[1], rectPath[2], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[1], rectPath[2], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[1], rectPath[2], ip);
         loc = Location::Right;
+        return true;
       }
-      else if (SegmentsIntersect(p, p2, rectPath[2], rectPath[3], true))
+      else if (GetSegmentIntersection(p, p2, rectPath[2], rectPath[3], ip))
       {
-        GetIntersectPoint(p, p2, rectPath[2], rectPath[3], ip);
         loc = Location::Bottom;
+        return true;
       }
       else return false;
-      break;
     }
-    return true;
   }
 
   inline Location GetAdjacentLocation(Location loc, bool isClockwise)