Klip 2.0.1153
dotnet add package Klip --version 2.0.1153
NuGet\Install-Package Klip -Version 2.0.1153
<PackageReference Include="Klip" Version="2.0.1153" />
<PackageVersion Include="Klip" Version="2.0.1153" />
<PackageReference Include="Klip" />
paket add Klip --version 2.0.1153
#r "nuget: Klip, 2.0.1153"
#:package Klip@2.0.1153
#addin nuget:?package=Klip&version=2.0.1153
#tool nuget:?package=Klip&version=2.0.1153

Klip
Fast and robust polygon clipping.
A partial F# port of Clipper2. This port is derived from the clipper2-ts TypeScript port rather than the original C#.
All original tests pass. There are also some new ones. And it's even a bit faster than clipper2-ts. See bench README.
Why another port?
In short, to use it in F# and be able to compile to JavaScript via Fable So that this code - and a lot of other code using it - runs on .NET as well as in the browser.
Scope
This is a partial port - it exposes polygon boolean operations on a single coordinate type:
Polygon boolean ops (intersection, union, difference, XOR) and PolyTree output
Coordinates are stored as 64-bit
floatvalues, so the output is Fable-friendly (no JSbigint) and fractional coordinates are preserved directly.No separate PathD-style API is needed for floating-point input. Klip's regular path types accept floats directly, and the engine does not scale them onto an integer grid.
No polygon offsetting, line clipping, rect clipping, Minkowski sums, triangulation, or arbitrary-precision decimal paths
The main convenience API is in Src/Klip.fs, in the Klip.Klipper module. It wraps Clipper64 for the common polygon boolean operations while keeping the lower-level engine available for specialized cases.
Coordinate precision (unrounded floats)
The clipping engine computes on unrounded float coordinates. Earlier versions snapped every
computed intersection point to the nearest integer (the old Geo.jsRound); that snapping has been
removed, so vertices created where edges cross are generally not integer-valued and keep full
floating-point precision.
What this means in practice:
- Point coincidence and collinearity are no longer tested with exact equality but with small
tolerances —
abs (a - b) < 1e-6for coordinates, and a relative tolerance for cross-product collinearity. These are sized to absorb floating-point noise without fusing genuinely distinct points (real coordinates can be as little as one unit apart). - Because there is no longer an integer grid, complex inputs can occasionally resolve into a few
more (or fewer) touching contours than an integer-snapped clipper would. Areas stay within the
usual tolerances, and a follow-up
unionsimplifies touching contours if needed. - You do not need to scale coordinates before clipping to preserve fractional precision. Use your source units directly unless your own application deliberately wants a different coordinate unit.
- If you need integer output, round the solution coordinates yourself after clipping (e.g. with
System.Math.Round).
Core Types
Original Documentation: https://www.angusj.com/clipper2
All types are still named like the original C# version, where the ..64 suffix indicated 64-bit integers. In Klip the XY coordinates are stored as float, and there is no separate ..D suffix API because the regular path types already accept and preserve floating-point coordinates.
New Generic 'Z Metadata
'Z is the generic type parameter for user-defined metadata that can be attached to vertices. It is optional and defaults to unit if not used.
In the original Clipper2 the optional Z value is always a int64 but in Klip it can be any type.
Path64<'Z>: A single contour. X and Y coordinates are stored in a flat interleavedResizeArray<float>asx0, y0, x1, y1, ....Paths64<'Z>: AResizeArray<Path64<'Z>>, representing multiple contours such as an outer polygon and its holes.PolyTree64<'Z>: A tree output structure that preserves parent-child contour relationships, such as holes inside outer contours.ZCallback64<'Z>: A callback for assigning user-defined'Zmetadata to new vertices created at edge intersections.'Zvalues are metadata, not 3D coordinates.
If you do not use 'Z metadata, use the default no-Z helpers such as Path64.createFrom and Paths64.createSingle; these create Path64<unit> and Paths64<unit> values.
Boolean Operations
Open vs closed paths
Clipper2 does not infer open/closed from coordinates — a trailing vertex equal to the first one is just stripped, it does not change how the path is treated. Instead, each path is tagged as open or closed when it is added to the engine.
Rules (inherited from Clipper2):
- Subject paths can be considered open or closed.
- Clip paths are always considered closed.
- For
Intersection,Difference, andXor: closed subject paths are ignored when computing the open-path solution, and vice versa — open and closed subjects are effectively processed independently. - For
Union: open subjects are clipped wherever they overlap any closed path (whether that closed path is a subject or a clip).
The Klipper.* wrappers below always treat input as closed polygons. To clip open
paths (polylines / line segments), drop down to Clipper64 directly and call
AddOpenSubject (or AddPaths(paths, PathType.Subject, isOpen = true)):
let c = Clipper64<unit>()
c.AddOpenSubject(openLines) // polylines — endpoints stay endpoints
c.AddSubject(closedPolygons) // optional, closed
c.AddClip(clipPolygons) // clip is always closed
let openSolution = Paths64<unit>()
let closedSolution = Paths64<unit>()
c.Execute(ClipType.Intersection, FillRule.EvenOdd, closedSolution, openSolution) |> ignore
Closed-polygon wrappers
Klipper.intersect clip subject: Returns the intersection of the subject and clip paths.Klipper.union clip subject: Returns the union of subject and clip paths.Klipper.unionSelf subject: Resolves self-intersections within a single subject path.Klipper.difference clip subject: Returns the regions of the subject that are not inside the clip region.Klipper.xor clip subject: Returns the regions of subject or clip that are not in both.
Each wrapper also has a Z variant that takes a ZCallback64<'Z> as the first argument:
Klipper.intersectZ zCallback clip subjectKlipper.unionZ zCallback clip subjectKlipper.unionSelfZ zCallback subjectKlipper.differenceZ zCallback clip subjectKlipper.xorZ zCallback clip subject
Use the general functions when you need a custom ClipType, FillRule, ZCallback64, or PolyTree64 output:
Klipper.booleanOp (clipType, subject, clip, fillRule, zCallback): Performs a boolean operation and returnsPaths64<'Z>.Klipper.booleanOpWithPolyTree (clipType, subject, clip, polyTree, fillRule, zCallback): Writes the result into aPolyTree64<'Z>so hierarchy is preserved.Klipper.polyTreeToPaths64 polyTree: Flattens aPolyTree64<'Z>back intoPaths64<'Z>.
Like the wrappers, these also treat subjects as closed. For open-subject clipping use
Clipper64 directly as shown above.
open Klip
let subject =
Paths64.createSingle [ 0.0; 0.0; 10.0; 0.0; 10.0; 10.0; 0.0; 10.0 ]
let clip =
Paths64.createSingle [ 5.0; 5.0; 15.0; 5.0; 15.0; 15.0; 5.0; 15.0 ]
let union = Klipper.union clip subject
let intersection = Klipper.intersect clip subject
let nonZeroDifference =
Klipper.booleanOp (ClipType.Difference, subject, clip, FillRule.NonZero, None)
Building
for .NET
dotnet build
test:
dotnet test Test/FSharp/Tests/Tests1/Tests1.fsproj
dotnet test Test/FSharp/Tests/Tests2/TestsZ.fsproj
for JS
cd Test
npm install
npm run build # F# → JavaScript via Fable, then vite build
npm run buildts # F# → TypeScript via Fable, then tsc and vite build
and then to test:
npm test # vitest --run
The compiled TypeScript ends up in _dist/Klip.mjs and is what the test
suite imports.
Performance
On .NET ist about the same as CLipper2 C#, but with more allocations.
In JavaScript it's about 15% faster than clipper2-ts, but 40% slower than clipper2-wasm.
See Test/bench/README.md
and Test/README.md.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- FSharp.Core (>= 6.0.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
### Changed
- MAJOR CHANGE: no more rounding to integers, no more scaling needed, API adapted