mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-14 19:26:56 -04:00
M14.4: no panics in the shipped code main gained since the no-panics branch
The M3/M4 work added ten panic sites the new workspace lints deny: - doc: the lens/taper width profiles fall back to a uniform profile instead of `expect` (the presets are a constant table, covered by tests). - engine: `FlattenOptions::default` builds the medium preset directly (`builtin`), `apply` skips an empty group instead of `expect`, the writable-format fallback reports an error instead of `expect`, and the new-art template's stroke comes back as an `Option` (it always has one) instead of `expect`. - tools: the freeform gradient drag returns no actions for a grab it never starts (was `unreachable!`), and the gradient tool's release takes the gesture with `if let` instead of `expect`. Behaviour on the success paths is unchanged. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
3e323bfa2a
commit
1a5f7f6441
8 files changed
+42
-25
No files matched your search
@@ -270,13 +270,13 @@ impl WidthProfile {
|
||||
}
|
||||
/// The lens profile (thin ends, full width in the middle).
|
||||
pub fn lens() -> Self {
|
||||
Self::preset("lens").expect("built-in")
|
||||
Self::preset("lens").unwrap_or_default()
|
||||
}
|
||||
pub fn taper_end() -> Self {
|
||||
Self::preset("taperEnd").expect("built-in")
|
||||
Self::preset("taperEnd").unwrap_or_default()
|
||||
}
|
||||
pub fn taper_start() -> Self {
|
||||
Self::preset("taperStart").expect("built-in")
|
||||
Self::preset("taperStart").unwrap_or_default()
|
||||
}
|
||||
/// The (left, right) factors just before and just after `t`: they differ only at a
|
||||
/// discontinuous point (two points at `t`, within `1e-7`).
|
||||
@@ -1023,7 +1023,7 @@ mod tests {
|
||||
a.rebase_gradients(Rect::new(0.0, 0.0, 100.0, 100.0), Rect::new(100.0, 0.0, 300.0, 100.0));
|
||||
let start = |p: Paint| match p {
|
||||
Paint::Gradient(g) => g.geom.unwrap().start,
|
||||
_ => unreachable!(),
|
||||
_ => panic!("a gradient"),
|
||||
};
|
||||
// The fill's start stays on the left edge; the stroke's on its inflated box's (−5 → 95).
|
||||
assert_eq!((start(a.fill_paint()), start(a.stroke_paint())), (Point::new(100.0, 0.0), Point::new(95.0, 0.0)));
|
||||
|
||||
@@ -272,7 +272,7 @@ pub fn writable_format(format_param: Option<&str>, path: Option<&str>) -> std::r
|
||||
let f = match (format_param, path.map(extension).filter(|e| !e.is_empty())) {
|
||||
(Some(f), _) => format(f).ok_or_else(|| format!("unknown format `{f}` (see document.formats)"))?,
|
||||
(None, Some(e)) => format(&e).ok_or_else(|| format!("unknown extension `.{e}`: pass `format` (see document.formats)"))?,
|
||||
(None, None) => format("png").expect("png is in FORMATS"),
|
||||
(None, None) => format("png").ok_or("no PNG encoder")?,
|
||||
};
|
||||
if f.write { Ok(f) } else { Err(format!("{} files can be opened but not written (see document.formats)", f.label)) }
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ pub struct FlattenOptions {
|
||||
|
||||
impl Default for FlattenOptions {
|
||||
fn default() -> Self {
|
||||
Self::preset("medium").expect("built-in preset")
|
||||
Self::builtin("medium")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,12 +155,17 @@ impl FlattenOptions {
|
||||
pub fn preset(name: &str) -> Option<Self> {
|
||||
let key = name.trim().to_ascii_lowercase();
|
||||
let id = Self::PRESETS.into_iter().find(|id| key == *id || Self::preset_label(id).is_some_and(|l| l.eq_ignore_ascii_case(&key)))?;
|
||||
Some(Self::builtin(id))
|
||||
}
|
||||
|
||||
/// The built-in preset `id` (one of [`Self::PRESETS`]; anything else is low).
|
||||
fn builtin(id: &str) -> Self {
|
||||
let (balance, line_art_ppi, gradient_ppi, clip_complex_regions, anti_alias) = match id {
|
||||
"high" => (100.0, 1200.0, 300.0, true, false),
|
||||
"medium" => (75.0, 300.0, 300.0, false, true),
|
||||
_ => (75.0, 300.0, 150.0, false, true),
|
||||
};
|
||||
Some(Self {
|
||||
Self {
|
||||
balance,
|
||||
line_art_ppi,
|
||||
gradient_ppi,
|
||||
@@ -170,7 +175,7 @@ impl FlattenOptions {
|
||||
anti_alias,
|
||||
preserve_alpha: false,
|
||||
preserve_overprints: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// The built-in presets, finest first.
|
||||
@@ -984,6 +989,8 @@ fn apply(d: &mut Document, sel: &mut vectorcraft_doc::Selection, plan: Plan, o:
|
||||
let (mut vector, mut rasterized) = (0, 0);
|
||||
let mut ids = vec![];
|
||||
for f in plan.flat {
|
||||
// A group always has objects.
|
||||
let Some(&top) = f.roots.last() else { continue };
|
||||
let mut children = vec![];
|
||||
// The image goes under the regions, reaching a pixel past its own: their edges then fall on
|
||||
// matching pixels instead of on what is behind the group.
|
||||
@@ -1001,7 +1008,6 @@ fn apply(d: &mut Document, sel: &mut vectorcraft_doc::Selection, plan: Plan, o:
|
||||
children.push(Arc::new(n));
|
||||
vector += 1;
|
||||
}
|
||||
let top = *f.roots.last().expect("a group has objects");
|
||||
let (par, idx, _) = d.position(top).ok_or(EngineError::NoNode(top))?;
|
||||
let g = Node::group(d.alloc_id(), children);
|
||||
ids.push(d.insert(par, idx + 1, g)?);
|
||||
|
||||
@@ -381,8 +381,9 @@ fn edit_gradient(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(m) = mode
|
||||
&& ids.is_empty()
|
||||
&& p.get("ids").is_none()
|
||||
&& let Some(st) = s.new_art_stroke_mut()
|
||||
{
|
||||
s.new_art_stroke_mut().gradient_mode = m;
|
||||
st.gradient_mode = m;
|
||||
}
|
||||
// The last gradient is the one the first object now shows (the defaults' without objects).
|
||||
let shown = match ids.first() {
|
||||
|
||||
@@ -155,9 +155,9 @@ impl Session {
|
||||
st
|
||||
}
|
||||
|
||||
/// The template's top stroke for an edit (`stroke.set`): the template becomes the user's own
|
||||
/// (an inherited one as new art takes it now), with a stroke to edit.
|
||||
pub(crate) fn new_art_stroke_mut(&mut self) -> &mut StrokeLayer {
|
||||
/// The template for an edit (`stroke.set`): it becomes the user's own (an inherited one as new
|
||||
/// art takes it now), with a stroke to edit.
|
||||
fn new_art_template_mut(&mut self) -> &mut Appearance {
|
||||
let own = self.template().map(Cow::into_owned);
|
||||
if self.basic_only() {
|
||||
(self.paint.opacity, self.paint.blend, self.paint.style) = (1.0, BlendMode::Normal, None);
|
||||
@@ -167,7 +167,13 @@ impl Session {
|
||||
if t.stroke().is_none() {
|
||||
t.set_stroke(Paint::None);
|
||||
}
|
||||
t.stroke_mut().expect("a stroke was just made")
|
||||
t
|
||||
}
|
||||
|
||||
/// The template's top stroke for an edit (see [`Session::new_art_template_mut`], which always
|
||||
/// gives it one).
|
||||
pub(crate) fn new_art_stroke_mut(&mut self) -> Option<&mut StrokeLayer> {
|
||||
self.new_art_template_mut().stroke_mut()
|
||||
}
|
||||
|
||||
/// A graphic style clicked with nothing selected: new art takes it (`add`: its fills, strokes
|
||||
@@ -175,8 +181,7 @@ impl Session {
|
||||
pub(crate) fn new_art_style(&mut self, g: &GraphicStyle, add: bool) {
|
||||
let ap = if g.unit_box { g.appearance.clone() } else { unplace_all(g.appearance.clone()) };
|
||||
if add {
|
||||
let _ = self.new_art_stroke_mut();
|
||||
let t = self.paint.appearance.as_mut().expect("made by new_art_stroke_mut");
|
||||
let t = self.new_art_template_mut();
|
||||
t.items.extend(ap.items);
|
||||
t.effects.extend(ap.effects);
|
||||
self.paint.style = None;
|
||||
|
||||
@@ -217,8 +217,8 @@ fn stroke_set(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
};
|
||||
// With nothing selected the Stroke panel sets up the next object drawn.
|
||||
if new_art {
|
||||
set(s.new_art_stroke_mut());
|
||||
if new_art && let Some(st) = s.new_art_stroke_mut() {
|
||||
set(st);
|
||||
}
|
||||
edit_items(s, &ids, item, C, "Stroke", false, |n, index| {
|
||||
if index.is_none()
|
||||
@@ -360,8 +360,11 @@ fn stroke_advanced(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
};
|
||||
// As stroke.set: with nothing selected, for the next object drawn.
|
||||
if no_targets(&ids, p) && (scale.is_some() || swap || flip.is_some()) {
|
||||
arrows_and_profile(s.new_art_stroke_mut());
|
||||
if no_targets(&ids, p)
|
||||
&& (scale.is_some() || swap || flip.is_some())
|
||||
&& let Some(st) = s.new_art_stroke_mut()
|
||||
{
|
||||
arrows_and_profile(st);
|
||||
}
|
||||
edit_items(s, &ids, item, C, "Stroke", false, |n, index| {
|
||||
let Some(st) = n.appearance.stroke_at_mut(index) else { return Ok(()) };
|
||||
|
||||
@@ -230,7 +230,8 @@ pub(super) fn drag(cx: &ToolContext, g: &mut Gesture, p: Point) -> Vec<Action> {
|
||||
let spread = if scale > 0.0 { ((r + at.distance(p) - d) / scale).clamp(0.0, 1.0) } else { 0.0 };
|
||||
("paint.freeform.setPoint", json!({ "index": index, "spread": spread }))
|
||||
}
|
||||
_ => unreachable!("checked above"),
|
||||
// Checked above.
|
||||
_ => return out,
|
||||
};
|
||||
out.push(Action::Preview(cmd.into(), on_proxy(cx, params)));
|
||||
out
|
||||
@@ -501,7 +502,7 @@ mod tests {
|
||||
// The handle of a point without spread drags one out.
|
||||
let mut d0 = d.clone();
|
||||
let n = d0.node_mut(s.objects[0]).unwrap();
|
||||
let Paint::Gradient(mut g) = n.appearance.fill_paint() else { unreachable!() };
|
||||
let Paint::Gradient(mut g) = n.appearance.fill_paint() else { panic!("a gradient fill") };
|
||||
g.freeform.as_mut().unwrap().points[2].spread = 0.0;
|
||||
n.appearance.set_fill(Paint::Gradient(g));
|
||||
let mut cx = crate::testutil::cx(&d0, &s, &p);
|
||||
|
||||
@@ -475,8 +475,9 @@ impl Tool for GradientTool {
|
||||
}
|
||||
(PointerKind::Drag, Some(g)) => return freeform::drag(cx, g, ev.pos),
|
||||
(PointerKind::Up, Some(_)) => {
|
||||
let g = self.free.take().expect("checked");
|
||||
return freeform::release(cx, FreeformAnnotator::of(cx).as_ref(), g, &mut self.lines);
|
||||
if let Some(g) = self.free.take() {
|
||||
return freeform::release(cx, FreeformAnnotator::of(cx).as_ref(), g, &mut self.lines);
|
||||
}
|
||||
}
|
||||
(PointerKind::Move, None) => freeform::hover(cx, &mut self.lines, ev.pos),
|
||||
(PointerKind::DoubleClick, _) => {
|
||||
@@ -903,7 +904,7 @@ mod tests {
|
||||
fn a_radial_shows_its_extent_ellipse_aspect_handle_and_focal_dot() {
|
||||
let (mut d, s) = graded(two());
|
||||
let n = d.node_mut(s.objects[0]).unwrap();
|
||||
let Paint::Gradient(mut g) = n.appearance.fill_paint() else { unreachable!() };
|
||||
let Paint::Gradient(mut g) = n.appearance.fill_paint() else { panic!("a gradient fill") };
|
||||
g.gradient.kind = GradientKind::Radial;
|
||||
let geom = g.geom.as_mut().unwrap();
|
||||
geom.aspect = 0.5;
|
||||
|
||||
Reference in new issue
Block a user