Merge main into fix/fitted-gradient-turns-with-transform

This commit is contained in:
dotCooCoo
2026-11-08 11:16:42 -08:00
564 changed files with 63631 additions and 2464 deletions
+4
View File
@@ -27,6 +27,10 @@ pub struct ObjectAttributes {
/// the element's event attributes; nothing in VectorCraft runs them.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub events: Vec<(String, String)>,
/// The CSS classes the object had in an imported SVG, written back to `class` on export
/// (beside the generated `cls-N` classes).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub classes: Vec<String>,
/// Attributes in other applications' XML namespaces, in order, kept from SVG import to SVG
/// export: what another tool notes on an object (a plotter's pen, a cutter's passes) survives
/// a round trip through VectorCraft. Nothing in VectorCraft reads them. They are the object's,
+4
View File
@@ -69,6 +69,9 @@ pub struct NodeExtras {
/// As an envelope with margins in Edit Contents, the margins its strokes gave when Edit Contents
/// began ([`crate::live::EnvelopeOptions::bounds_margin`]).
pub envelope_edit_margin: Option<[f32; 4]>,
/// Transform panel › Align to Pixel Grid: the path's or compound path's horizontal and vertical
/// segments stay on the pixel grid when it is transformed.
pub pixel_aligned: bool,
}
/// No extras: what [`Node::extras`] gives for a node without any.
@@ -95,6 +98,7 @@ pub(super) static NO_EXTRAS: NodeExtras = NodeExtras {
shape_mode: crate::ShapeMode::Add,
symbol_overrides: Vec::new(),
envelope_edit_margin: None,
pixel_aligned: false,
};
/// A node's [`NodeExtras`], if it has any ([`Node::extras`]). Extras that are all unset equal
+4 -1
View File
@@ -448,6 +448,9 @@ impl Node {
if self.appearance.gradients().next().is_some() {
self.appearance.transform_gradients(a);
}
if self.appearance.has_svg_filter() {
self.appearance.transform_svg_filters(a);
}
if sc.patterns {
crate::pattern::transform_pattern_paints(self, a);
}
@@ -508,7 +511,7 @@ impl Node {
*h = (lin * h.to_point()).to_vec2();
}
}
EnvelopeKind::TopObject { path } => path.transform(a),
EnvelopeKind::TopObject { path, .. } => path.transform(a),
EnvelopeKind::Warp { .. } => {}
}
}
+3
View File
@@ -22,6 +22,7 @@ fn extras_are_written_flat_in_order() {
e.compound_shape = true;
e.shape_mode = crate::ShapeMode::Exclude;
e.envelope_edit_margin = Some([0.5, 0.5, 0.25, 0.25]);
e.pixel_aligned = true;
}
/// The top-level keys of a JSON object, in the order written.
struct Keys(Vec<String>);
@@ -67,9 +68,11 @@ fn extras_are_written_flat_in_order() {
"compound_shape",
"shape_mode",
"envelope_edit_margin",
"pixel_aligned",
];
assert_eq!(keys, order);
assert_eq!(v["shape_mode"], "exclude");
assert_eq!(v["pixel_aligned"], true);
assert_eq!(serde_json::from_value::<Node>(v).unwrap(), n);
let plain = Node::path(NodeId(9), shapes::rectangle(Rect::new(0.0, 0.0, 4.0, 2.0)), Appearance::default_art());
let back: Node = serde_json::from_value(serde_json::to_value(&plain).unwrap()).unwrap();
+5
View File
@@ -54,6 +54,7 @@ impl Serialize for Node {
is_default(&e.shape_mode),
e.symbol_overrides.is_empty(),
e.envelope_edit_margin.is_none(),
is_default(&e.pixel_aligned),
];
let mut st = s.serialize_struct("Node", skips.iter().filter(|s| !**s).count())?;
let mut skip = skips.into_iter();
@@ -96,6 +97,7 @@ impl Serialize for Node {
field!("shape_mode", &e.shape_mode);
field!("symbol_overrides", &e.symbol_overrides);
field!("envelope_edit_margin", &e.envelope_edit_margin);
field!("pixel_aligned", &e.pixel_aligned);
st.end()
}
}
@@ -236,6 +238,8 @@ pub(super) struct NodeWire {
symbol_overrides: Vec<crate::SymbolOverride>,
#[serde(default)]
envelope_edit_margin: Option<[f32; 4]>,
#[serde(default)]
pixel_aligned: bool,
}
impl From<NodeWire> for Node {
@@ -263,6 +267,7 @@ impl From<NodeWire> for Node {
shape_mode: w.shape_mode,
symbol_overrides: w.symbol_overrides,
envelope_edit_margin: w.envelope_edit_margin,
pixel_aligned: w.pixel_aligned,
};
Node {
id: w.id,