iOverlay is a high-performance polygon overlay engine for Rust. It solves robust boolean operations on complex polygons for GIS, CAD, and graphics workflows, built for developers who need reliable geometry at scale across integer and floating-point APIs.
iOverlay powers polygon boolean operations in geo.
For specialized geometry, see iCurve for polygon boolean operations with Bézier curves and xOverlay for high-performance boolean operations on orthogonal (Manhattan) polygons.
- Why iOverlay?
- Features
- Demo
- Performance
- Getting Started
- Boolean Operations
- Spatial Predicates
- Custom Point Type Support
- Slicing & Clipping
- Buffering
- Integer Coordinate Limits
- Floating-Point Coordinate Limits
- FAQ
- License
- Built for robust polygon overlays where precision matters (GIS, CAD, graphics).
- High performance with predictable results across complex inputs.
- Supports both integer and floating-point APIs for flexible pipelines.
- OGC-valid output is available when strict topology is required.
- Core overlay engine used in geo.
- Boolean Operations: union, intersection, difference, and exclusion.
- Spatial Predicates:
intersects,disjoint,interiors_intersect,touches,within,coverswith early-exit optimization. - Polyline Operations: clip and slice.
- Polygons: with holes, self-intersections, and multiple contours.
- Flat Shape Hierarchy: FFI-friendly shape, hole, and nested-island relationships.
- Simplification: removes degenerate vertices and merges collinear edges.
- Buffering: offsets paths and polygons.
- Fill Rules: even-odd, non-zero, positive and negative.
- Data Types: supports
i16/i32/i64integer APIs andf32/f64floating-point APIs.
iOverlay supports:
i16/i32/i64integer engines- Optional multithreading for large inputs through the
allow_multithreadingCargo feature
[dependencies]
i_overlay = { version = "^8.1", features = ["allow_multithreading"] }For bigger data sets, the math engine and multithreading mode have a larger impact on runtime.
See the detailed reports: Performance Comparison and Rust Solver Benchmarks
Add the following to your Cargo.toml:
[dependencies]
i_overlay = "^8.1"Read full documentation
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
let subj = [[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]];
let clip = [[2.0, 2.0], [6.0, 2.0], [6.0, 6.0], [2.0, 6.0]];
let result = subj.overlay(&clip, OverlayRule::Intersect, FillRule::EvenOdd);
println!("result: {:?}", result);Here's an example of performing a union operation between two polygons:
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
// Define the subject "O"
let subj = [
// main contour
vec![
[1.0, 0.0],
[4.0, 0.0],
[4.0, 5.0],
[1.0, 5.0], // the contour is auto closed!
],
// hole contour
vec![
[2.0, 1.0],
[2.0, 4.0],
[3.0, 4.0],
[3.0, 1.0], // the contour is auto closed!
],
];
// Define the clip "-"
let clip = [
// main contour
[0.0, 2.0],
[5.0, 2.0],
[5.0, 3.0],
[0.0, 3.0], // the contour is auto closed!
];
let result = subj.overlay(&clip, OverlayRule::Union, FillRule::EvenOdd);
println!("result: {:?}", result);
The result is a vec of shapes:
[
// first shape
[
// main contour (counterclockwise order)
[
[0.0, 3.0], [0.0, 2.0], [1.0, 2.0], [1.0, 0.0], [4.0, 0.0], [4.0, 2.0], [5.0, 2.0], [5.0, 3.0], [4.0, 3.0], [4.0, 5.0], [1.0, 5.0], [1.0, 3.0]
],
// first hole (clockwise order)
[
[2.0, 1.0], [2.0, 2.0], [3.0, 2.0], [3.0, 1.0]
],
// second hole (clockwise order)
[
[2.0, 3.0], [2.0, 4.0], [3.0, 4.0], [3.0, 3.0]
]
]
// ... other shapes if present
]
The overlay function returns Shapes<P>, which is an alias for Vec<Shape<P>>:
Shapes<P>: a collection of shapes.Shape<P>: a shape made up of:Vec<Contour<P>>: a list of contours.- The first contour is the outer boundary (counterclockwise), and subsequent contours represent holes (clockwise).
Contour<P>: a sequence of points (Vec<P>) forming a closed contour, wherePimplementsFloatPointCompatible.
Note: By default, outer boundaries are counterclockwise and holes are clockwise—unless main_direction is set. More information about contours.
The regular overlay result groups each outer contour with its holes, but shapes nested inside those holes are separate entries. Use overlay_hierarchy when you also need the immediate parent-hole relationship.
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay::Overlay;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::i_shape::int_shape;
let subject = int_shape![
[[0, 0], [100, 0], [100, 100], [0, 100]],
[[10, 10], [10, 90], [90, 90], [90, 10]],
[[20, 20], [80, 20], [80, 80], [20, 80]],
];
let mut overlay = Overlay::from_subj(&subject);
let result = overlay.overlay_hierarchy(OverlayRule::Subject, FillRule::EvenOdd);
assert_eq!(result.shapes.shape_ranges.len(), 2);
assert_eq!(result.links.len(), 1);
let link = result.links[0];
assert_eq!(link.parent_shape_index, 0);
assert_eq!(link.parent_contour_index, 1);
assert_eq!(link.child_shape_index, 1);
| A,B | A ∪ B | A ∩ B | A - B | B - A | A ⊕ B |
|---|---|---|---|---|---|
Use EdgeOverlay when the result boundary needs to keep data from the original input edges: source ids, layer ids, material ids, constraints, styles, or any other edge provenance. The regular Overlay API remains optimized for plain geometry; this API is opt-in and works with user-defined payloads.
When an input edge is split by intersections, its data is copied by default. When coincident edges are merged, your OverlayEdgeData implementation decides what the resulting data means. A common policy is to keep identical values and mark conflicts as Undefined.
use i_overlay::core::edge_data::{EdgeDataMerge, OverlayEdgeData};
use i_overlay::core::edge_overlay::{EdgeOverlay, InputEdge};
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay::ShapeType;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::i_float::int::point::IntPoint;
use i_overlay::segm::boolean::ShapeCountBoolean;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum EdgeKind {
Red,
Green,
Undefined,
}
impl OverlayEdgeData for EdgeKind {
type Store = ();
fn merge(ctx: EdgeDataMerge<ShapeCountBoolean, Self>, _store: &mut Self::Store) -> Self {
match (ctx.lhs_data, ctx.rhs_data) {
(EdgeKind::Red, EdgeKind::Red) => EdgeKind::Red,
(EdgeKind::Green, EdgeKind::Green) => EdgeKind::Green,
_ => EdgeKind::Undefined,
}
}
}
let mut overlay = EdgeOverlay::new(8);
let red_square = [
[0, 0], [4, 0], [4, 4], [0, 4],
];
let green_square = [
[2, 0], [6, 0], [6, 4], [2, 4],
];
for edge in red_square.windows(2).map(|w| (w[0], w[1]))
.chain([(red_square[3], red_square[0])])
{
overlay.add_edge(InputEdge {
a: IntPoint::new(edge.0[0], edge.0[1]),
b: IntPoint::new(edge.1[0], edge.1[1]),
data: EdgeKind::Red,
}, ShapeType::Subject);
}
for edge in green_square.windows(2).map(|w| (w[0], w[1]))
.chain([(green_square[3], green_square[0])])
{
overlay.add_edge(InputEdge {
a: IntPoint::new(edge.0[0], edge.0[1]),
b: IntPoint::new(edge.1[0], edge.1[1]),
data: EdgeKind::Green,
}, ShapeType::Clip);
}
let shapes = overlay.build_vector_shapes(OverlayRule::Union, FillRule::NonZero);
// The result is grouped as shapes -> contours -> edges.
// Each output edge contains geometry, fill, and the propagated user data.
assert_eq!(shapes.len(), 1);
assert!(shapes[0][0].iter().any(|edge| edge.data == EdgeKind::Undefined));This API currently targets integer boolean operations and exports the result as vector shapes. It keeps the same contour structure as the regular polygon API, but each contour item is an edge with propagated data. Collinear edges are simplified only when their data is equal, so attribute boundaries are preserved.
When you only need to know whether two shapes have a spatial relationship—not compute their intersection geometry—use spatial predicates for better performance:
use i_overlay::float::relate::FloatRelate;
let outer = vec![[0.0, 0.0], [0.0, 20.0], [20.0, 20.0], [20.0, 0.0]];
let inner = vec![[5.0, 5.0], [5.0, 15.0], [15.0, 15.0], [15.0, 5.0]];
let adjacent = vec![[20.0, 0.0], [20.0, 10.0], [30.0, 10.0], [30.0, 0.0]];
let distant = vec![[100.0, 100.0], [100.0, 110.0], [110.0, 110.0], [110.0, 100.0]];
// intersects: shapes share any point (interior or boundary)
assert!(outer.intersects(&inner));
assert!(outer.intersects(&adjacent)); // edge contact counts
// disjoint: shapes share no points (negation of intersects)
assert!(outer.disjoint(&distant));
// interiors_intersect: interiors overlap (stricter than intersects)
assert!(outer.interiors_intersect(&inner));
assert!(!outer.interiors_intersect(&adjacent)); // edge-only contact
// touches: boundaries intersect but interiors don't
assert!(outer.touches(&adjacent));
assert!(!outer.touches(&inner)); // interiors overlap
// within: first shape completely inside second
assert!(inner.within(&outer));
assert!(!outer.within(&inner));
// covers: first shape completely contains second
assert!(outer.covers(&inner));
assert!(!inner.covers(&outer));These methods use early-exit optimization, returning as soon as the predicate can be determined without processing remaining segments.
For consistent precision across operations, use FixedScaleFloatRelate:
use i_overlay::float::scale::FixedScaleFloatRelate;
let square = vec![[0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0]];
let other = vec![[5.0, 5.0], [5.0, 15.0], [15.0, 15.0], [15.0, 5.0]];
let scale = 1000.0; // or 1.0 / grid_size
let result = square.intersects_with_fixed_scale(&other, scale);
assert!(result.unwrap());For more control, use FloatPredicateOverlay directly:
use i_overlay::float::relate::FloatPredicateOverlay;
let square = vec![[0.0, 0.0], [0.0, 10.0], [10.0, 10.0], [10.0, 0.0]];
let clip = vec![[5.0, 5.0], [5.0, 15.0], [15.0, 15.0], [15.0, 5.0]];
// Use fixed-scale constructor
let mut overlay = FloatPredicateOverlay::with_subj_and_clip_fixed_scale(
&square, &clip, 1000.0
).unwrap();
assert!(overlay.intersects());
iOverlay allows users to define custom point types, as long as they implement the FloatPointCompatible trait.
use i_overlay::i_float::float::compatible::FloatPointCompatible;
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
#[derive(Clone, Copy, Debug)]
struct CustomPoint {
x: f32,
y: f32,
}
impl FloatPointCompatible for CustomPoint {
type Scalar = f32;
fn from_xy(x: f32, y: f32) -> Self {
Self { x, y }
}
fn x(&self) -> f32 {
self.x
}
fn y(&self) -> f32 {
self.y
}
}
let subj = [
CustomPoint { x: 0.0, y: 0.0 },
CustomPoint { x: 0.0, y: 3.0 },
CustomPoint { x: 3.0, y: 3.0 },
CustomPoint { x: 3.0, y: 0.0 },
];
let clip = [
CustomPoint { x: 1.0, y: 1.0 },
CustomPoint { x: 1.0, y: 2.0 },
CustomPoint { x: 2.0, y: 2.0 },
CustomPoint { x: 2.0, y: 1.0 },
];
let result = subj.overlay(&clip, OverlayRule::Difference, FillRule::EvenOdd);
println!("result: {:?}", result);
use i_overlay::core::fill_rule::FillRule;
use i_overlay::float::slice::FloatSlice;
let polygon = [
[1.0, 1.0],
[1.0, 4.0],
[4.0, 4.0],
[4.0, 1.0],
];
let slicing_line = [
[3.0, 5.0],
[2.0, 2.0],
[3.0, 3.0],
[2.0, 0.0],
];
let result = polygon.slice_by(&slicing_line, FillRule::NonZero);
println!("result: {:?}", result);
use i_overlay::core::fill_rule::FillRule;
use i_overlay::float::clip::FloatClip;
use i_overlay::string::clip::ClipRule;
let polygon = [
[1.0, 1.0],
[1.0, 4.0],
[4.0, 4.0],
[4.0, 1.0],
];
let string_line = [
[3.0, 5.0],
[2.0, 2.0],
[3.0, 3.0],
[2.0, 0.0],
];
let clip_rule = ClipRule { invert: false, boundary_included: false };
let result = string_line.clip_by(&polygon, FillRule::NonZero, clip_rule);
println!("result: {:?}", result);
Outline, stroke, and variable-width stroke geometry is built by mesh::int.
The mesh::float APIs select a scale, convert input and styles, delegate
construction to the integer core, and convert the result back. Fixed-scale
methods retain the supplied grid. Integer rounding and CORDIC arc subdivision can change individual vertices
compared with the former float builders.
Use IntOutlineOffset, IntStrokeOffset, or IntVariableStrokeOffset from the
corresponding mesh::int::{outline,stroke,variable_stroke}::offset module.
Outline and stroke styles live in mesh::int::style; variable-width vertices and
styles live in mesh::int::variable_stroke. Bevel, clipped miter, and round joins
are supported. Stroke caps can be butt, square, round, or custom; variable-width
strokes use round caps and joins. Round geometry uses ArcOptions, including
configurable CORDIC rotation precision (default: 5).
Integer construction math limits miter joins to a minimum interior angle of
5 degrees, clipping sharper corners. Float construction math retains its
1.8-degree minimum. In both modes, almost straight corners with interior angles
above 175 degrees use bevel joins by default to avoid unstable intersections of
rounded offset lines. Stroke and outline styles expose .miter_min_turn(angle)
to configure this minimum turn independently of the sharp-corner clipping angle:
use Angle in the integer API or radians in the float API. The default is 5 degrees
for both math modes. Zero disables this guard; smaller values allow less stable
intersections.
Integer distances use input coordinate units without automatic rescaling.
Stroke radius is ceil(max(width, 0) / 2); radii at most 1 are degenerate.
Use validate_outline(&style), validate_stroke(&style), or
validate_variable_stroke() for an optional conservative coordinate-range check.
Construction requires input and temporary coordinates to stay in the safe range.
All three APIs provide *_into methods that replace a reusable flat output buffer.
Constant-width stroke also offers float construction math through
StrokeStyle::math(MathMode::Float) or IntStrokeStyle::math(MathMode::Float),
with MathMode in mesh::math. Directions are stored as UnitIntVector; integer
coordinates and boolean operations are retained. Integer remains the default.
Choose MathMode::Integer for cross-platform deterministic construction;
otherwise prefer MathMode::Float for higher precision and generally better speed.
Outline and variable-width stroke currently use Integer only.
See stroke construction math.
use i_overlay::mesh::float::stroke::offset::StrokeOffset;
use i_overlay::mesh::float::style::{LineCap, LineJoin, StrokeStyle};
let path = [
[ 2.0, 1.0],
[ 5.0, 1.0],
[ 8.0, 4.0],
[11.0, 4.0],
[11.0, 1.0],
[ 8.0, 1.0],
[ 5.0, 4.0],
[ 2.0, 4.0],
];
let style = StrokeStyle::new(1.0)
.line_join(LineJoin::Miter(1.0))
.start_cap(LineCap::Round(0.1))
.end_cap(LineCap::Square);
let shapes = path.stroke(style, false);
println!("result: {:?}", shapes);
use i_overlay::mesh::float::outline::offset::OutlineOffset;
use i_overlay::mesh::float::style::{LineJoin, OutlineStyle};
let shape = vec![
vec![
[2.0, 1.0],
[4.0, 1.0],
[5.0, 2.0],
[13.0, 2.0],
[13.0, 3.0],
[12.0, 3.0],
[12.0, 4.0],
[11.0, 4.0],
[11.0, 3.0],
[10.0, 3.0],
[9.0, 4.0],
[8.0, 4.0],
[8.0, 3.0],
[5.0, 3.0],
[5.0, 4.0],
[4.0, 5.0],
[2.0, 5.0],
[1.0, 4.0],
[1.0, 2.0]
],
vec![
[2.0, 4.0],
[4.0, 4.0],
[4.0, 2.0],
[2.0, 2.0]
],
];
let style = OutlineStyle::new(0.2).line_join(LineJoin::Round(0.1));
let shapes = shape.outline(&style);
println!("shapes: {:?}", &shapes);Note:
-
Offsetting a polygon works reliably only with valid polygons. Ensure that:
- No self-intersections.
- Outer boundaries are counterclockwise, holes are clockwise—unless
main_directionis set.
If polygon validity cannot be guaranteed, it is recommended to apply the simplify_shape operation before offsetting.
More information on contour orientation.
| Butt | Square | Round | Custom |
|---|---|---|---|
| Bevel | Miter | Round |
|---|---|---|
For an N-bit engine, keep each input coordinate within
-2^(N - 2)..=2^(N - 2) - 1 (inclusive):
| Engine | Minimum x or y | Maximum x or y |
|---|---|---|
i16 |
-16,384 | 16,383 |
i32 |
-1,073,741,824 | 1,073,741,823 |
i64 |
-4,611,686,018,427,387,904 | 4,611,686,018,427,387,903 |
These limits leave room for coordinate differences and their products. They apply to all inputs and solver strategies. Integer APIs do not check them; exceeding these bounds can cause overflow or incorrect results. Use a wider engine or rescale larger inputs. See the range derivation and arithmetic audit for details.
For float APIs, the limits apply after conversion. Automatic conversion and checked
fixed-scale methods use FloatPointAdapter::CONSERVATIVE_COORDINATE_BITS
(I::BITS - 3), reserving an extra bit for rounding inside the arithmetic range.
Custom adapters must respect the integer range; use
FloatPointAdapter::new_conservative(rect) for the same budget.
Input coordinates must be finite, with absolute values at most 2^60 for f32
or 2^500 for f64. Stroke and outline bounds, including padding for widths,
offsets, joins, and caps, must also fit these limits.
Infallible APIs panic on invalid bounds. Fixed-scale APIs return
FixedScaleOverlayError::InvalidRect. Scales must be positive and finite, have a
finite reciprocal in the input scalar type, and fit the coordinate budget.
A scale whose reciprocal overflows returns ScaleTooSmall; one exceeding the
budget returns ScaleTooLarge. Coordinate limits do not themselves limit scales.
Empty valid inputs remain supported.
-
Use
FloatOverlaywhen you perform repeated overlay operations:use i_overlay::core::fill_rule::FillRule; use i_overlay::core::overlay_rule::OverlayRule; use i_overlay::float::overlay::FloatOverlay; let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]]; let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]]; let next_clip = vec![[1.0, 1.0], [1.0, 3.0], [3.0, 3.0], [3.0, 1.0]]; let mut overlay = FloatOverlay::with_subj_and_clip(&subj, &clip); let result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd); overlay.reinit_with_subj_and_clip(&subj, &next_clip); let next_result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd); // ...
-
Use
SingleFloatOverlaytrait for one-shot operations. -
Use
FloatOverlayGraphif you need to extract multiple boolean results (e.g. union and intersection) from the same input geometry without recomputing.
Use the simplify_shape operation:
use i_overlay::core::fill_rule::FillRule;
use i_overlay::float::simplify::SimplifyShape;
let shapes = vec![
vec![[0.0, 0.0], [0.0, 2.0], [2.0, 2.0], [2.0, 0.0]],
vec![[2.0, 0.0], [2.0, 2.0], [4.0, 2.0], [4.0, 0.0]],
];
let result = shapes.simplify_shape(FillRule::EvenOdd);It internally merges shapes efficiently and is typically faster and more robust than chaining many overlay() calls manually.
Use FixedScaleFloatOverlay or FloatOverlay::with_subj_and_clip_fixed_scale. The scale is
scale = 1.0 / grid_size.
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::scale::FixedScaleFloatOverlay;
let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]];
let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]];
let grid_size = 0.001;
let scale = 1.0 / grid_size;
let result = subj
.overlay_with_fixed_scale(&clip, OverlayRule::Difference, FillRule::EvenOdd, scale)
.expect("scale does not fit input bounds");If you need more control, use FloatPointAdapter::with_scale and FloatOverlay::with_adapter.
If you need control over the float-to-integer precision range, select the integer engine at
compile time with the from_* constructors. The supported engines are i16, i32, and i64.
The default engine is i32; use i64 when the input bounds need a wider integer range.
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::overlay::FloatOverlay;
let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]];
let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]];
let mut overlay = FloatOverlay::<[f64; 2], i64>::from_subj_and_clip(&subj, &clip);
let result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd);The sugar traits also use i32 by default. Use the *_as::<I> methods when you want to keep
the shorthand API and still select the integer engine explicitly:
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::single::SingleFloatOverlay;
let subj = vec![[0.0, 0.0], [0.0, 5.0], [5.0, 5.0], [5.0, 0.0]];
let clip = vec![[2.0, 2.0], [2.0, 4.0], [4.0, 4.0], [4.0, 2.0]];
let result = subj.overlay_as::<i64>(&clip, OverlayRule::Difference, FillRule::EvenOdd);Set the ogc flag in OverlayOptions.
use i_overlay::core::fill_rule::FillRule;
use i_overlay::core::overlay_rule::OverlayRule;
use i_overlay::float::overlay::{FloatOverlay, OverlayOptions};
// 0 1 2 3 4 5
// 5 ┌───────────────────┐
// │ │
// 4 │ ┌───────┐ │
// │ │ ░ ░ │ │ Two L-shaped holes share vertices at (2,2) and (3,3)
// 3 │ │ ┌───●───┐ │
// │ │ ░ │ │ ░ │ │ ░ = holes
// 2 │ └───●───┘ │ │
// │ │ ░ ░ │ │ The shared edge disconnects the interior
// 1 │ └───────┘ │
// │ │
// 0 └───────────────────┘
//
// OGC Simple Feature Specification (ISO 19125-1) states:
// "The interior of every Surface is a connected point set."
let subj = vec![vec![[0.0, 0.0], [5.0, 0.0], [5.0, 5.0], [0.0, 5.0]]];
let clip = vec![
vec![[1.0, 2.0], [1.0, 4.0], [3.0, 4.0], [3.0, 3.0], [2.0, 3.0], [2.0, 2.0]],
vec![[2.0, 1.0], [2.0, 2.0], [3.0, 2.0], [3.0, 3.0], [4.0, 3.0], [4.0, 1.0]],
];
let options = OverlayOptions::<f64>::ogc();
let mut overlay = FloatOverlay::with_subj_and_clip_custom(&subj, &clip, options, Default::default());
let result = overlay.overlay(OverlayRule::Difference, FillRule::EvenOdd);
assert_eq!(result.len(), 2);Licensed under either of:
- MIT license (LICENSE-MIT)
- Apache License, Version 2.0 (LICENSE-APACHE)