Skip to main content

clipper2c_sys/
lib.rs

1//! Raw Rust FFI bindings to the [Clipper2](https://github.com/AngusJohnson/Clipper2)
2//! C++ polygon clipping and offsetting library by Angus Johnson — the canonical
3//! implementation. The library's C++ source is vendored into this crate; see
4//! the repository [README](https://github.com/tirithen/clipper2c-sys#clipper2c-sys)
5//! for the rationale and credit details.
6//!
7//! # You probably want [`clipper2`](https://crates.io/crates/clipper2) instead
8//!
9//! Almost every item here is `unsafe`, requires manual memory management, and
10//! exposes raw C pointer types. The high-level [`clipper2`](https://crates.io/crates/clipper2)
11//! crate wraps these with a safe Rust API and is the recommended way to do
12//! polygon clipping from Rust. Reach for `clipper2c-sys` directly only when
13//! you need to interop with code that already operates on the C ABI.
14//!
15//! # Memory model
16//!
17//! All non-trivial types in this crate are *opaque handles*. The C++ side
18//! owns the storage; Rust code only sees `*mut ClipperX` pointers and never
19//! reads or writes struct fields directly. The lifecycle of every opaque
20//! object is:
21//!
22//! 1. Ask `clipper_X_size()` how many bytes the C++ object needs.
23//! 2. Allocate that many bytes via [`clipper_allocate`].
24//! 3. Pass the buffer to a constructor like `clipper_X(mem)`. Internally
25//!    this is C++ placement-new — no extra allocation occurs.
26//! 4. Use the resulting `*mut ClipperX` as a handle to all `clipper_X_*`
27//!    operations.
28//! 5. Release with `clipper_delete_X(handle)` (runs the C++ destructor and
29//!    frees the [`clipper_allocate`] buffer) or `clipper_destruct_X(handle)`
30//!    (destructor only — caller frees the buffer themselves through the
31//!    matching allocator).
32//!
33//! Memory from [`clipper_allocate`] must be released through the matching
34//! `clipper_delete_X` (or destructed and freed via the same allocator).
35//! Mixing with `libc::free` or Rust's allocator is undefined behaviour.
36//!
37//! # `_64` (`i64`) vs `_D` (`f64`) variants
38//!
39//! Every clipping type comes in two flavours that look superficially
40//! similar but use **different Rust numeric types**:
41//!
42//! | Suffix | Rust coordinate type | Upstream C++ type | Example |
43//! |--------|----------------------|-------------------|---------|
44//! | `_64`  | `i64` (signed 64-bit integer) | `int64_t`         | [`ClipperPoint64`] (`x: i64, y: i64`) |
45//! | `_D`   | `f64` (64-bit IEEE-754 float) | `double`          | [`ClipperPointD`] (`x: f64, y: f64`)  |
46//!
47//! ## Why both exist
48//!
49//! The clipping engine is implemented **exclusively on `int64_t`**.
50//! Integer arithmetic and comparisons are exact, so the engine's
51//! handling of coincident edges and shared vertices is deterministic.
52//! Floating-point rounding around bit-different-but-mathematically-equal
53//! values would otherwise produce different topology depending on input
54//! order.
55//!
56//! - **`_64` types are the engine's native interface.** Your `i64`
57//!   coordinates pass through unchanged. No transformation, no rounding.
58//! - **`_D` types are a convenience wrapper for `f64` data.** On input,
59//!   each coordinate is multiplied by a scale factor
60//!   `s = 2^⌈log₂(10^precision)⌉` (default `precision = 2` → `s = 128`),
61//!   rounded to `i64`, fed to the engine. Outputs are divided by `s` and
62//!   returned as `f64`. The round-trip quantises results to a grid of
63//!   step `1/s ≈ 10^-precision`.
64//!
65//! ## Which preserves your data
66//!
67//! - **`_64` round-trips bit-exactly** (same `i64` values out as in).
68//! - **`_D` does not** — output values are rounded to the integer grid
69//!   defined by the `precision` setting. The round-trip is *not* the
70//!   identity. This is benign for typical CAD or vector-graphics use,
71//!   but matters if you depend on invariants like "feeding the same
72//!   shape back must give bit-identical coordinates".
73//!
74//! ## Other tradeoffs
75//!
76//! - **Precision floor on `_D`.** Quantisation step is `~10^-precision`
77//!   (default `~0.01`). Max supported `precision` is 8 decimal digits
78//!   (`CLIPPER2_MAX_DEC_PRECISION`).
79//! - **Range.** Integer coordinates must satisfy `|c| ≤ INT64_MAX/4`
80//!   (≈ 2.3 × 10¹⁸). For `_D`, the same bound applies *after* scaling,
81//!   so high-magnitude values at high precision can hit a range error
82//!   well before the raw `f64` runs out of bits.
83//! - **Performance.** `_D` adds a multiply on every input coordinate
84//!   and a divide on every output. For hot-path work, prefer `_64` and
85//!   pre-scale once if your data sits on a known integer grid.
86//!
87//! Reach for `_D` when your geometry is already `f64` and the
88//! quantisation is acceptable. Reach for `_64` when your data fits an
89//! integer grid or when bit-exact preservation matters.
90//!
91//! # Cargo features
92//!
93//! - `serde` (off by default) — derives `Serialize`/`Deserialize` on
94//!   [`ClipperPoint64`].
95//! - `generate-bindings` — regenerate the Rust FFI bindings from the C
96//!   headers at build time. Off by default; the pre-generated
97//!   `generated/bindings.rs` is used otherwise.
98//! - `update-bindings` — like `generate-bindings`, but writes the result
99//!   back into `generated/bindings.rs` so the regen output is committed.
100//!   Use `scripts/regenerate-bindings.sh` to invoke this with the right
101//!   `LIBCLANG_PATH` handling.
102
103#![allow(non_upper_case_globals)]
104#![allow(non_camel_case_types)]
105#![allow(non_snake_case)]
106#![allow(dead_code)]
107#![allow(clippy::unreadable_literal)]
108#![allow(deref_nullptr)]
109
110#[cfg(test)]
111mod test;
112
113#[cfg(all(not(feature = "update-bindings"), feature = "generate-bindings"))]
114include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
115
116#[cfg(any(feature = "update-bindings", not(feature = "generate-bindings")))]
117include!(concat!(
118    env!("CARGO_MANIFEST_DIR"),
119    "/generated/bindings.rs"
120));