Skip to content

fit_to_bezpath_opt panics on a sliver #601

Description

@mlwilkerson

Claude found kurbo panics on some curves I'm processing. This is Claude's summary of the issue:

fit_to_bezpath_opt panics on some well-formed, finite inputs. Reproduced on 0.13.0 and 0.13.1.

I hit it through simplify_bezpath with SimplifyOptLevel::Optimize, feeding it flattened polygon-offset contours.

Reproduction

use kurbo::simplify::SimplifyBezPath;
use kurbo::{BezPath, Point};

fn main() {
    // A closed quadrilateral whose four vertices are nearly collinear.
    let pts = [(1.0, 0.7), (4.1, 2.0), (4.4, 2.1), (0.0, 0.0)];

    let mut path = BezPath::new();
    path.move_to(Point::new(pts[0].0, pts[0].1));
    for (x, y) in &pts[1..] {
        path.line_to(Point::new(*x, *y));
    }
    path.close_path();

    let _ = kurbo::fit_to_bezpath_opt(&SimplifyBezPath::new(path), 2.0); // panics
}
thread 'main' panicked at kurbo-0.13.1/src/fit.rs:646:61:
called `Option::unwrap()` on a `None` value

(fit.rs:650:61 on 0.13.0.) Expected: a BezPath, or a graceful fallback — not a panic.

Cause

The panic is the last statement of fit_to_bezpath_opt_inner:

let (c, _) = fit_to_cubic(source, t0..t1, accuracy).unwrap();

fit_to_cubic returns Option and is treated as fallible at every other call site in fit.rs. Here it's unwrapped,
assuming a segment that fit_opt_segment accepted at the smaller per-segment budget x must also fit at the larger
accuracy.

It need not, because fit_to_cubic changes strategy rather than just relaxing as accuracy grows:

if chord2 <= acc2 {
    // Special case very short chords; try to fit a line.
    return try_fit_line(source, accuracy, range, start.p, end.p);
}

A bigger accuracy makes this branch more likely, and try_fit_line returns None when the curve strays further
than accuracy from the chord. So the trigger is a sub-range with arc length ≫ accuracy but chord ≪ accuracy
geometry that doubles back on itself, which is what a nearly-degenerate polyline is full of.

Notes

  • It's an accuracy window, not a threshold: this input panics at accuracy = 2.0 but succeeds at 0.5, 1.0, 4.0 and
    8.0.
  • fit_to_bezpath handles the same input at every accuracy above, so it's specific to the _opt path.
  • The input is minimal — no three-vertex subset reproduces, the unclosed form doesn't, and only this starting vertex
    does.

Possible fix

Stop unwrapping and give the loop somewhere to go when a sub-range can't be fit — fall back to the chord, or propagate
the failure — matching how the other fit_to_cubic call sites already treat it.

Repro

kurbo_fit_opt_panic.rs.txt

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions