diff --git a/Cargo.lock b/Cargo.lock index 095377a..9247b44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,6 +72,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "katexit" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccfb0b7ce7938f84a5ecbdca5d0a991e46bc9d6d078934ad5e92c5270fe547db" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "libc" version = "0.2.178" @@ -138,6 +149,7 @@ name = "sirop" version = "0.1.0" dependencies = [ "insta", + "katexit", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 0d3c838..e065fb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ repository = "https://github.com/rami3l/sirop" description = "A libspiro port in pure Rust." [dependencies] +katexit = "0.1.5" thiserror = "2.0.17" [dev-dependencies] diff --git a/src/lib.rs b/src/lib.rs index 5a0288a..31c2b2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub fn bezier( ctx.start(); let segs = Segs::from_cps(&mut cps, is_closed)?; - segs.to_bez_path(ctx, segs.0.len(), delta); + segs.render_bez(ctx, segs.0.len(), delta); ctx.end(); Ok(()) diff --git a/src/spiro.rs b/src/spiro.rs index cc865a9..180031d 100644 --- a/src/spiro.rs +++ b/src/spiro.rs @@ -69,6 +69,7 @@ impl TryFrom for CpTy { } } +/// A spiro [`Seg`]ment between two control points. #[derive(Default, Debug, Clone, Copy)] pub struct Seg { cp: Cp, @@ -119,6 +120,7 @@ impl Seg { } } +/// An ordered collection of spiro [`Seg`]ments. #[derive(Debug)] pub struct Segs(pub Vec); @@ -326,7 +328,7 @@ impl Segs { } } - pub(crate) fn to_bez_path(&self, ctx: &mut C, n_seg: usize, delta: f64) { + pub(crate) fn render_bez(&self, ctx: &mut C, n_seg: usize, delta: f64) { let s = &self.0; let n_seg = n_seg - usize::from(s[0].ty() == CpTy::Open); @@ -341,12 +343,16 @@ impl Segs { ctx.mark_knot(0); } - Arc::new(s[i].ks, p0, p1).to_bez_path(ctx, delta); + Arc::new(s[i].ks, p0, p1).render_bez(ctx, delta); ctx.mark_knot(i + 1); } } - /// Creates a [`Segs`] from control points. + /// Creates a [`Segs`] from a list of control points. + /// + /// If `is_closed` is `false`, the [`CpTy`]s of the first and the last + /// control points will be replaced with [`CpTy::Open`] and + /// [`CpTy::EndOpen`] respectively before calculating the segments. /// /// # Errors /// diff --git a/src/spiro/arc.rs b/src/spiro/arc.rs index efccd98..950d617 100644 --- a/src/spiro/arc.rs +++ b/src/spiro/arc.rs @@ -1,12 +1,16 @@ use super::K as SpiroK; use crate::{ Point, - bez::{self, Ctx}, + bez::{self, Ctx, Strand}, }; /// An [`Arc`] is defined as: -/// ∫[-1/2 .. 1/2] exp(i * (k0 s + (1/2) * k1 * s^2 + (1/6) * k2 * s^3 + (1/24) -/// * k3 * s^4)) d s +/// +/// $$ +/// \int_{-\frac{1}{2}}^{\frac{1}{2}} \exp\left(i \left(k\_0 s + \frac{ k\_1 +/// s^2}{2} + \frac{k\_2 s^3}{6} + \frac{k\_3 s^4}{24}\right)\right) ds +/// $$ +#[cfg_attr(doc, katexit::katexit)] #[derive(Debug, Clone, Copy)] pub struct Arc { pub p0: Point, @@ -108,43 +112,31 @@ impl Arc { ) } - fn uniform_subdivide(&self, stops: usize, sink: &mut Vec) { - let mut arc = *self; - for s in (2..=stops).rev() { - #[allow(clippy::cast_precision_loss)] - let (head, tail) = arc.subdivide((s as f64).recip()); - sink.push(head); - arc = tail; - } - sink.push(arc); - } - #[must_use] - pub fn subdivisions(&self, delta: f64) -> Vec { - const MAX_STOPS: usize = 32; - for stops in 1..MAX_STOPS { - let mut sink = Vec::with_capacity(stops); - self.uniform_subdivide(stops, &mut sink); - if sink.iter().all(|part| part.bend <= delta) { - return sink; + pub(crate) fn subdivisions(&self, delta: f64) -> Vec { + fn go(this: Arc, delta: f64, depth: usize, sink: &mut Vec) { + if depth > 5 || this.bend <= delta { + sink.push(this); + return; } + let (head, tail) = this.subdivide(0.5); + go(head, delta, depth + 1, sink); + go(tail, delta, depth + 1, sink); } - let mut sink = Vec::with_capacity(MAX_STOPS); - self.uniform_subdivide(MAX_STOPS, &mut sink); + let mut sink = Vec::with_capacity(32); + go(*self, delta, 0, &mut sink); sink } - pub fn to_bez_path(&self, ctx: &mut C, delta: f64) { + pub(crate) fn render_bez(&self, ctx: &mut C, delta: f64) { const ARC_STRAIGHT_EPSILON: f64 = 1e-8; if self.bend <= ARC_STRAIGHT_EPSILON { ctx.line_to(self.p1); return; } - let subdivision = self.subdivisions(delta); - for part in subdivision { - let strand = part.to_cubic(); - let crate::bez::Strand::Cubic { c1, c2, end, .. } = strand else { + for part in self.subdivisions(delta) { + let Strand::Cubic { c1, c2, end, .. } = part.to_cubic() else { unreachable!(); }; ctx.cubic_to(c1, c2, end); diff --git a/tests/snapshots/to_bez__bezier.snap b/tests/snapshots/to_bez__bezier.snap index 3243b30..57f5069 100644 --- a/tests/snapshots/to_bez__bezier.snap +++ b/tests/snapshots/to_bez__bezier.snap @@ -8,21 +8,20 @@ M 0.00000000 0.00000000 C 47.17044899 13.85038259, 86.14961741 52.82955101, 100.00000000 100.00000000 C 103.97701048 112.65648836, 106.33321379 125.77200651, 110.17201303 138.47109823 -C 114.01081226 151.17018994, 119.50210664 163.56071241, 128.01100900 173.73921380 -C 136.51991135 183.91771520, 147.98402725 191.56116004, 160.62139640 195.59851199 +C 114.01081226 151.17018994, 119.50210665 163.56071241, 128.01100900 173.73921381 +C 136.51991136 183.91771520, 147.98402725 191.56116004, 160.62139641 195.59851199 C 173.25876556 199.63586394, 186.73337615 200.00126024, 200.00000000 200.00000000 L 300.00000000 200.00000000 -C 312.91821760 198.16143569, 325.86767767 196.37633127, 338.56673679 193.37723771 -C 351.26579590 190.37814415, 363.80208311 186.09095608, 374.81974363 179.10011770 -C 385.83740416 172.10927933, 395.31703795 162.17910223, 400.00000000 150.00000000 +C 319.37732640 197.24215353, 339.00244973 194.70232493, 357.29640014 187.74402960 +C 366.44337534 184.26488193, 375.21328162 179.61651829, 382.73030959 173.35026628 +C 390.24733757 167.08401427, 396.48777846 159.13432667, 400.00000000 150.00000000 -C 401.39357678 141.55320919, 399.13380275 132.71120909, 394.55245491 125.47922573 -C 389.97110707 118.24724238, 383.18836541 112.55734121, 375.62361502 108.54923907 -C 368.05886462 104.54113693, 359.71533523 102.14585872, 351.26371291 100.78188971 -C 342.81209059 99.41792070, 334.22595321 99.05952603, 325.66508155 99.10204333 -C 317.10420989 99.14456064, 308.55076503 99.58197045, 300.00000000 100.00000000 +C 400.87098549 144.72075574, 400.33823794 139.24210865, 398.67184940 134.15760426 +C 397.00546087 129.07309987, 394.22259386 124.38002695, 390.72057313 120.33467326 +C 383.71653167 112.24396588, 373.98461985 106.84495197, 363.76523158 103.67001167 +C 343.32645503 97.32013106, 321.37691257 98.95492614, 300.00000000 100.00000000 L 200.00000000 100.00000000