Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn bezier<C: Ctx>(

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(())
Expand Down
12 changes: 9 additions & 3 deletions src/spiro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl TryFrom<u8> for CpTy {
}
}

/// A spiro [`Seg`]ment between two control points.
#[derive(Default, Debug, Clone, Copy)]
pub struct Seg {
cp: Cp,
Expand Down Expand Up @@ -119,6 +120,7 @@ impl Seg {
}
}

/// An ordered collection of spiro [`Seg`]ments.
#[derive(Debug)]
pub struct Segs(pub Vec<Seg>);

Expand Down Expand Up @@ -326,7 +328,7 @@ impl Segs {
}
}

pub(crate) fn to_bez_path<C: Ctx>(&self, ctx: &mut C, n_seg: usize, delta: f64) {
pub(crate) fn render_bez<C: Ctx>(&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);

Expand All @@ -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
///
Expand Down
48 changes: 20 additions & 28 deletions src/spiro/arc.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -108,43 +112,31 @@ impl Arc {
)
}

fn uniform_subdivide(&self, stops: usize, sink: &mut Vec<Self>) {
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<Self> {
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<Self> {
fn go(this: Arc, delta: f64, depth: usize, sink: &mut Vec<Arc>) {
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<C: Ctx>(&self, ctx: &mut C, delta: f64) {
pub(crate) fn render_bez<C: Ctx>(&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);
Expand Down
19 changes: 9 additions & 10 deletions tests/snapshots/to_bez__bezier.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down