Text cuts: the 2D prism route takes a font's curves exactly (#1429)
The 2D prism route for text cuts takes a font's curves exactly instead of as polylines.
This commit is contained in:
1 parent
f0c5bfcdf2
commit
3047aa60cd
6 files changed
+647
-62
No files matched your search
@@ -276,6 +276,60 @@ pub(super) fn segment(e: &mt::Edge, plane: &Plane, tol: f64, work: &mut usize) -
|
||||
if a.dist(b) > tol && spans.iter().all(|s| line_span(s, a, b, tol, error, work).is_some()) {
|
||||
return Some(vec![Seg2::Line { a, b }]);
|
||||
}
|
||||
arc(&c, &spans, plane, a, b, tol, error, work).or_else(|| free_form(&spans, a, b, tol))
|
||||
}
|
||||
|
||||
/// One Bézier span that is neither a line nor an arc, as the exact free-form segment it is
|
||||
/// (a font's quadratic or cubic, a conic), its ends `a` and `b`. `None` for several spans (a
|
||||
/// curve that may wind about more than once), degrees past [`solidcraft_geom::MAX_BEZIER_DEGREE`],
|
||||
/// rational spans other than conics, and control polygons with no area (a curve doubling back
|
||||
/// along a line).
|
||||
fn free_form(spans: &[Vec<H>], a: Vec2, b: Vec2, tol: f64) -> Option<Vec<Seg2>> {
|
||||
let [span] = spans else { return None };
|
||||
let pts: Vec<Vec2> = span.iter().map(|p| point(*p)).collect();
|
||||
let (first, last) = (*pts.first()?, *pts.last()?);
|
||||
if first.dist(a) > tol || last.dist(b) > tol || first.dist(last) <= tol {
|
||||
return None;
|
||||
}
|
||||
// Twice the area the control polygon spans off the chord: none is a line, maybe doubling back.
|
||||
let chord = last - first;
|
||||
let off = pts.iter().map(|p| chord.cross(*p - first).abs()).fold(0., f64::max) / chord.len();
|
||||
if !(off > tol) {
|
||||
return None;
|
||||
}
|
||||
let rational = span.iter().any(|p| (p[2] - 1.).abs() > 1e-12);
|
||||
let seg = match (pts.len(), rational) {
|
||||
(_, false) => solidcraft_geom::bezier(&pts)?,
|
||||
(3, true) => {
|
||||
// Weights normalised to 1 at the ends: the middle one is the conic's.
|
||||
let (w0, w1, w2) = (span[0][2], span[1][2], span[2][2]);
|
||||
let w = w1 / (w0 * w2).sqrt();
|
||||
if !(w.is_finite() && w > 1e-9) {
|
||||
return None;
|
||||
}
|
||||
Seg2::Conic { a: first, apex: pts[1], b: last, w }
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
// The exact ends of the edge (the span's agree within the tolerance).
|
||||
let seg = match seg {
|
||||
Seg2::Cubic { p1, p2, .. } => Seg2::Cubic { p0: a, p1, p2, p3: b },
|
||||
Seg2::Conic { apex, w, .. } => Seg2::Conic { a, apex, b, w },
|
||||
Seg2::Bezier { n, mut p } => {
|
||||
*p.first_mut()? = a;
|
||||
*p.get_mut(n as usize)? = b;
|
||||
Seg2::Bezier { n, p }
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
Some(vec![seg])
|
||||
}
|
||||
|
||||
/// The spline `c` (its Bézier `spans` in the plane, from `a` to `b`) as a circular arc, when
|
||||
/// whole-span proofs show it is one.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn arc(c: &mt::Curve, spans: &[Vec<H>], plane: &Plane, a: Vec2, b: Vec2, tol: f64, error: f64, work: &mut usize) -> Option<Vec<Seg2>> {
|
||||
let (t0, t1) = c.range_tuple();
|
||||
// Samples are only a candidate proposal. Whole-span implicit and oriented
|
||||
// angular proofs below are required before converting any spline to an arc.
|
||||
let q = plane.to_local(from_p3(c.subs(t0 + (t1 - t0) * 5. / 16.))) - a;
|
||||
@@ -290,7 +344,7 @@ pub(super) fn segment(e: &mt::Edge, plane: &Plane, tol: f64, work: &mut usize) -
|
||||
return None;
|
||||
}
|
||||
let mut sweep: f64 = 0.;
|
||||
for span in &spans {
|
||||
for span in spans {
|
||||
let ds = circle_span(span, center, radius, tol, error, 0, work)?;
|
||||
if sweep != 0. && sweep.signum() != ds.signum() {
|
||||
return None;
|
||||
|
||||
Reference in new issue
Block a user