mirror of
https://github.com/storytold/figcraft.git
synced 2026-10-24 10:27:00 -06:00
Cap stroke width in the outliner so huge weights don't panic
Found while testing crates/engine/src/cmd/props.rs:361 (strokeWeight is only clamped with .max(0.0)); the panic is raised when the renderer draws the stroke (render crate / tiny-skia stroker) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
d1d8c3f652
commit
9aeb9fb752
@@ -0,0 +1,28 @@
|
||||
//! An absurd stroke weight must not take the renderer down with it: saving (thumbnail) and exporting still work.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::Session;
|
||||
use crate::params::id_of;
|
||||
use figcraft_doc::NodeId;
|
||||
|
||||
fn id(v: Value) -> NodeId {
|
||||
id_of(&v["id"]).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn huge_stroke_weight_with_round_caps_still_saves_and_exports() {
|
||||
for weight in [1e6, 1e7, 1e10, 1e30] {
|
||||
let mut s = Session::new();
|
||||
let f = id(s.execute("frame.create", &json!({"x": 0, "y": 0, "width": 100, "height": 100})).unwrap());
|
||||
let v = id(s.execute("vector.create", &json!({"parent": f, "path": "M0 0.8333 L7.5 6.25 L15 0.8333"})).unwrap());
|
||||
s.execute("node.set", &json!({"id": v, "props": {"stroke": "#ff0000", "strokeWeight": weight, "strokeCap": "round", "strokeJoin": "round"}}))
|
||||
.unwrap();
|
||||
let path = std::env::temp_dir().join(format!("figcraft-huge-stroke-{}-{weight}.figcraft", std::process::id()));
|
||||
let saved = s.execute("file.saveAs", &json!({"path": path.to_string_lossy()}));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
assert!(saved.is_ok(), "saveAs at weight {weight}: {:?}", saved.err());
|
||||
let exported = s.execute("file.export", &json!({"ids": [f], "format": "png"}));
|
||||
assert!(exported.is_ok(), "export at weight {weight}: {:?}", exported.err());
|
||||
}
|
||||
}
|
||||
@@ -1548,6 +1548,8 @@ mod history_behaviour_tests;
|
||||
#[cfg(test)]
|
||||
mod html_codegen_tests;
|
||||
#[cfg(test)]
|
||||
mod huge_stroke_tests;
|
||||
#[cfg(test)]
|
||||
mod import_export_tests;
|
||||
#[cfg(test)]
|
||||
mod inspect_behaviour_tests;
|
||||
|
||||
@@ -258,11 +258,16 @@ pub(crate) fn kjoin(j: StrokeJoin) -> Join {
|
||||
}
|
||||
}
|
||||
|
||||
/// Plain centred stroke of `path` at `width`.
|
||||
/// The widest stroke outlined: past this the rasteriser's fixed-point maths overflows and panics,
|
||||
/// and nothing visible changes (a stroke this wide covers any canvas already).
|
||||
pub const MAX_STROKE_WIDTH: f64 = 1e6;
|
||||
|
||||
/// Plain centred stroke of `path` at `width` (capped at [`MAX_STROKE_WIDTH`]).
|
||||
pub fn stroke_center(path: &BezPath, width: f64, props: &StrokeProps) -> BezPath {
|
||||
if !(width.is_finite() && width > 0.0 && finite(path)) {
|
||||
return BezPath::new();
|
||||
}
|
||||
let width = width.min(MAX_STROKE_WIDTH);
|
||||
let mut style = kurbo::Stroke::new(width)
|
||||
.with_join(kjoin(props.join))
|
||||
.with_miter_limit(props.miter_limit.clamp(1.0, 100.0))
|
||||
@@ -284,10 +289,17 @@ pub fn stroke_outline(path: &BezPath, props: &StrokeProps, closed_shape: bool) -
|
||||
/// [`stroke_outline`] of a shape whose stroke follows vector network `net` (`path` is its stroke
|
||||
/// path): points with a cap or join of their own draw them there.
|
||||
pub fn stroke_outline_of(path: &BezPath, props: &StrokeProps, closed_shape: bool, net: Option<&VectorNetwork>) -> BezPath {
|
||||
let w = props.weight;
|
||||
if !(w.is_finite() && w > 0.0) {
|
||||
if !(props.weight.is_finite() && props.weight > 0.0) {
|
||||
return BezPath::new();
|
||||
}
|
||||
let capped;
|
||||
let props = if props.weight > MAX_STROKE_WIDTH {
|
||||
capped = StrokeProps { weight: MAX_STROKE_WIDTH, ..props.clone() };
|
||||
&capped
|
||||
} else {
|
||||
props
|
||||
};
|
||||
let w = props.weight;
|
||||
let styled = net.and_then(|n| points::Styled::new(n, props));
|
||||
// Centred at `width` (twice the weight for Inside/Outside, which clip half of it away).
|
||||
let centred = |width: f64| match (&styled, &props.brush, props.profile()) {
|
||||
|
||||
Reference in New Issue
Block a user