From 6c3ac0c55b71bb355e59d6c0eb9156048f2e1024 Mon Sep 17 00:00:00 2001 From: Trevor Brown Date: Sat, 7 Nov 2026 19:44:51 -0500 Subject: [PATCH] Export settings: node.set takes back what node.get returns (snake_case aliases) node.get reports an export setting's two-word fields in snake_case, which node.set refused, so a setting could not be read and written back unchanged. ExportSetting now accepts those names as serde aliases; the camelCase names stay the canonical form. Follow-up the maintainer suggested on #2008. Co-Authored-By: Claude Sonnet 5.5 Model-Effort: Opus 5.5 drove and briefed; Sonnet 5.5 wrote at medium --- crates/doc/src/props.rs | 9 +++++++ crates/engine/src/export_behaviour_tests.rs | 28 +++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/crates/doc/src/props.rs b/crates/doc/src/props.rs index 99b9d9c39..664fcf88d 100644 --- a/crates/doc/src/props.rs +++ b/crates/doc/src/props.rs @@ -1449,8 +1449,10 @@ pub struct PrintSettings { /// is printed this far out, to be trimmed off. pub bleed: f64, /// Crop marks outside the bleed, at the trim's corners. + #[serde(alias = "crop_marks")] pub crop_marks: bool, /// Bleed marks outside the bleed, at the bleed's corners. + #[serde(alias = "bleed_marks")] pub bleed_marks: bool, /// CMYK colour (converted from RGB) instead of RGB. pub cmyk: bool, @@ -1519,25 +1521,32 @@ pub struct ExportSetting { pub print: PrintSettings, /// Only the layer itself (on), or every layer that shows in its area (off). #[serde(skip_serializing_if = "is_true")] + #[serde(alias = "contents_only")] pub contents_only: bool, /// The layer's own box, cutting off what it paints outside it (shadows, outside strokes). #[serde(skip_serializing_if = "is_false")] + #[serde(alias = "include_bounding_box")] pub include_bounding_box: bool, /// SVG: text as outlines (on) or as `` elements (off). #[serde(skip_serializing_if = "is_true")] + #[serde(alias = "svg_outline_text")] pub svg_outline_text: bool, /// SVG: layer names as `id` attributes. #[serde(skip_serializing_if = "is_false")] + #[serde(alias = "svg_id_attribute")] pub svg_id_attribute: bool, /// SVG: strokes as `stroke` attributes where that's exact (on), else as outlines. #[serde(skip_serializing_if = "is_true")] + #[serde(alias = "svg_simplify_stroke")] pub svg_simplify_stroke: bool, /// Android Vector Drawable: what to do with what a drawable can't hold exactly. #[serde(skip_serializing_if = "VdCompat::is_default")] + #[serde(alias = "vd_compat")] pub vd_compat: VdCompat, /// PNG, JPG and WebP: the color profile the file is written in. SVG, PDF and XML are always /// sRGB, whatever this says. #[serde(skip_serializing_if = "ExportColorProfile::is_default")] + #[serde(alias = "color_profile")] pub color_profile: ExportColorProfile, /// JPG: the encoder's quality, 1 (smallest file) to 100 (see [`ExportSetting::quality`]). #[serde(skip_serializing_if = "is_default_quality")] diff --git a/crates/engine/src/export_behaviour_tests.rs b/crates/engine/src/export_behaviour_tests.rs index 1cd836784..020734dbe 100644 --- a/crates/engine/src/export_behaviour_tests.rs +++ b/crates/engine/src/export_behaviour_tests.rs @@ -384,3 +384,31 @@ fn copy_as_png_svg_and_code() { let css = crate::codegen::code_for(doc, a, crate::codegen::CodeLang::Css); assert!(css.contains("background: #FF0000;"), "Copy as CSS is the layer's CSS from Inspect"); } + +#[test] +fn node_set_takes_back_the_export_settings_node_get_returns() { + let mut s = Session::new(); + let a = rect(&mut s, 0.0, 0.0, 40.0, 20.0, "#FF0000", None); + let given = json!({ + "format": "jpg", + "suffix": "@3x", + "contentsOnly": false, + "includeBoundingBox": true, + "svgOutlineText": false, + "svgIdAttribute": true, + "svgSimplifyStroke": false, + "vdCompat": "exact", + "colorProfile": "displayP3", + "quality": 55, + "bleed": 9, + "cropMarks": true, + "bleedMarks": true, + }); + ex(&mut s, "export.add", json!({"ids": [a], "setting": given})); + let stored = s.doc().unwrap().node(a).unwrap().exports.clone(); + assert_eq!(stored.len(), 1); + let got = ex(&mut s, "node.get", json!({"id": a}))["exports"].clone(); + set(&mut s, &[a], json!({"exports": []})); + set(&mut s, &[a], json!({"exports": got})); + assert_eq!(s.doc().unwrap().node(a).unwrap().exports, stored, "node.set stores what node.get returned, unchanged"); +}