Never crash: geom and color return or fall back instead of panicking
Replace the unwrap/expect calls in path closing, bounds, Pathfinder output and gradient sampling with let-else / if-let; the ASE reader uses checked offsets (a 32-bit length could overflow on wasm32). Both crates now deny unwrap/expect/panic/unimplemented/todo/unreachable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
192b72054d
commit
89891fdfbe
|
|
@ -0,0 +1,4 @@
|
|||
allow-unwrap-in-tests = true
|
||||
allow-expect-in-tests = true
|
||||
allow-panic-in-tests = true
|
||||
allow-indexing-slicing-in-tests = true
|
||||
|
|
@ -26,8 +26,9 @@ struct R<'a> {
|
|||
|
||||
impl R<'_> {
|
||||
fn take(&mut self, n: usize) -> Result<&[u8], AseError> {
|
||||
let s = self.b.get(self.i..self.i + n).ok_or(AseError::Truncated)?;
|
||||
self.i += n;
|
||||
let end = self.i.checked_add(n).ok_or(AseError::Truncated)?;
|
||||
let s = self.b.get(self.i..end).ok_or(AseError::Truncated)?;
|
||||
self.i = end;
|
||||
Ok(s)
|
||||
}
|
||||
fn u16(&mut self) -> Result<u16, AseError> {
|
||||
|
|
|
|||
|
|
@ -83,8 +83,10 @@ impl Gradient {
|
|||
return (a.color.lerp(&b.color, v), a.opacity + (b.opacity - a.opacity) * v);
|
||||
}
|
||||
}
|
||||
let l = stops.last().unwrap();
|
||||
(l.color, l.opacity)
|
||||
match stops.last() {
|
||||
Some(l) => (l.color, l.opacity),
|
||||
None => (Color::BLACK, 1.0),
|
||||
}
|
||||
}
|
||||
/// Stops expanded so that midpoints are represented as explicit stops (for renderers without midpoints).
|
||||
pub fn expanded_stops(&self) -> Vec<(f32, Color, f32)> {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
//!
|
||||
//! Colours keep the model the user picked them in (RGB, CMYK, Gray, HSB is a UI view of RGB), so
|
||||
//! documents don't drift when converting back and forth. Rendering asks for [`Color::to_rgba`].
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod ase;
|
||||
|
|
|
|||
|
|
@ -120,8 +120,10 @@ pub fn smooth(sp: &SubPath, a: At, b: At, tol: f64) -> SubPath {
|
|||
let mut out = tidy(head);
|
||||
if sp.closed {
|
||||
// The walk repeated the first anchor at the end.
|
||||
if out.len() > 2 && (out[0].p - out[out.len() - 1].p).hypot() < 1e-6 {
|
||||
let last = out.pop().expect("len");
|
||||
if out.len() > 2
|
||||
&& (out[0].p - out[out.len() - 1].p).hypot() < 1e-6
|
||||
&& let Some(last) = out.pop()
|
||||
{
|
||||
out[0].h_in = last.h_in;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
//! handle positions. Rendering and algorithms convert to [`kurbo::BezPath`].
|
||||
//!
|
||||
//! Coordinates are document points (1/72 in), y pointing down.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod corners;
|
||||
|
|
|
|||
|
|
@ -307,13 +307,12 @@ impl PathData {
|
|||
PathEl::ClosePath => {
|
||||
if let Some(mut sp) = cur.take() {
|
||||
// Merge a closing anchor that duplicates the start.
|
||||
if sp.anchors.len() > 1 {
|
||||
let first = sp.anchors[0].p;
|
||||
let lastp = sp.anchors.last().unwrap().p;
|
||||
if first.distance(lastp) < 1e-7 {
|
||||
let l = sp.anchors.pop().unwrap();
|
||||
sp.anchors[0].h_in = l.h_in;
|
||||
}
|
||||
if sp.anchors.len() > 1
|
||||
&& let (Some(first), Some(last)) = (sp.anchors.first(), sp.anchors.last())
|
||||
&& first.p.distance(last.p) < 1e-7
|
||||
&& let Some(l) = sp.anchors.pop()
|
||||
{
|
||||
sp.anchors[0].h_in = l.h_in;
|
||||
}
|
||||
sp.closed = true;
|
||||
for a in &mut sp.anchors {
|
||||
|
|
@ -341,9 +340,10 @@ impl PathData {
|
|||
return None;
|
||||
}
|
||||
let bp = self.to_bezpath();
|
||||
if self.anchor_count() == 1 {
|
||||
let p = self.subpaths.iter().find_map(|s| s.anchors.first()).unwrap().p;
|
||||
return Some(Rect::from_points(p, p));
|
||||
if self.anchor_count() == 1
|
||||
&& let Some(a) = self.subpaths.iter().find_map(|s| s.anchors.first())
|
||||
{
|
||||
return Some(Rect::from_points(a.p, a.p));
|
||||
}
|
||||
Some(bp.bounding_box())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,14 +67,18 @@ fn from_flo(paths: &[SimpleBezierPath]) -> PathData {
|
|||
}
|
||||
let mut anchors = vec![Anchor::corner(p(start))];
|
||||
for (c1, c2, end) in curves {
|
||||
anchors.last_mut().expect("non-empty").h_out = p(c1);
|
||||
if let Some(prev) = anchors.last_mut() {
|
||||
prev.h_out = p(c1);
|
||||
}
|
||||
let mut a = Anchor::corner(p(end));
|
||||
a.h_in = p(c2);
|
||||
anchors.push(a);
|
||||
}
|
||||
// The last curve comes back to the start: fold its end into the first anchor.
|
||||
if anchors.len() > 2 && anchors.last().expect("non-empty").p.distance(anchors[0].p) < 1e-6 {
|
||||
let last = anchors.pop().expect("non-empty");
|
||||
if anchors.len() > 2
|
||||
&& anchors.last().is_some_and(|l| l.p.distance(anchors[0].p) < 1e-6)
|
||||
&& let Some(last) = anchors.pop()
|
||||
{
|
||||
anchors[0].h_in = last.h_in;
|
||||
}
|
||||
for a in &mut anchors {
|
||||
|
|
|
|||
Loading…
Reference in New Issue