.fig import: the round-trip test asserts the scaled size; text on a path takes the instance's size (#2100 review)

The scaled-instance round-trip test now asserts the scaled width and height instead of printing them. text_path read the layer's raw size, so a text on a path inside a scaled instance kept the main's path length; it now uses the scaled or fitted size.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
This commit is contained in:
Trevor BrownandClaude Sonnet 5.5 committed 2026-11-07 22:58:07 -05:00
1 parent c7c5a4f87d
commit 9303b73991
3 files changed
+60 -8

No files matched your search

+4 -1
View File
@@ -1048,6 +1048,9 @@ fn a_scaled_instance_keeps_its_radius_through_a_round_trip() {
let (back, _) = import(&bytes, "x").unwrap();
let inst = back.nodes().find(|n| n.name == "Scaled").unwrap();
let b = inst.children.iter().map(|c| back.node(*c).unwrap()).find(|n| n.name == "Box").unwrap();
eprintln!("scaled box back: {}x{} radii {:?}", b.width, b.height, b.corners.radii);
// The 40 px box at the instance's 1.5 scale. The exporter writes every scaled value of a layer
// in a derived row, so there is no main-layer value left unwritten here to round-trip; the
// scaling of values with no row stays covered by `scale_factor_tests`.
assert_eq!((b.width, b.height), (60.0, 60.0));
assert_eq!(b.corners.radii, [15.0; 4]);
}
+1 -5
View File
@@ -186,11 +186,7 @@ impl<'a> Mapper<'a> {
fail(self, "has invalid normalized size");
return;
};
let Some(size) = v.get("size") else {
fail(self, "has invalid dimensions");
return;
};
let (Some(nx), Some(ny), Some(w), Some(h)) = (num(norm, "x"), num(norm, "y"), num(size, "x"), num(size, "y")) else {
let (Some(nx), Some(ny), Some((w, h))) = (num(norm, "x"), num(norm, "y"), v.size()) else {
fail(self, "has invalid dimensions");
return;
};
+55 -2
View File
@@ -3,7 +3,7 @@
use super::kiwi::Kv;
const SCHEMA: &str = "
enum NodeType DOCUMENT CANVAS TEXT TEXT_PATH
enum NodeType DOCUMENT CANVAS TEXT TEXT_PATH SYMBOL INSTANCE
enum StyleType NONE TEXT
enum TextDecoration NONE UNDERLINE STRIKETHROUGH
struct GUID uint sessionID, uint localID
@@ -17,7 +17,8 @@ message TextData string characters, uint[] characterStyleIDs, TextOverride[] sty
message TextPathStart float tValue, bool forward
message VectorData uint vectorNetworkBlob, Vector normalizedSize
message Blob byte[] bytes
message NodeChange GUID guid, ParentIndex parentIndex, NodeType type, string name, Vector size, Matrix transform, TextData textData, float fontSize, FontName fontName, TextPathStart textPathStart, VectorData vectorData, StyleType styleType, StyleId styleIdForText, TextDecoration textDecoration
message SymbolData GUID symbolID, float uniformScaleFactor
message NodeChange GUID guid, ParentIndex parentIndex, NodeType type, string name, Vector size, Matrix transform, TextData textData, float fontSize, FontName fontName, TextPathStart textPathStart, VectorData vectorData, StyleType styleType, StyleId styleIdForText, TextDecoration textDecoration, SymbolData symbolData
message Message NodeChange[] nodeChanges, Blob[] blobs
";
use figcraft_doc::{NodeKind, TextPath};
@@ -209,3 +210,55 @@ fn linked_text_style_decoration_warns_from_effective_mapped_style() {
assert_eq!(json["document"]["children"][0]["children"][0]["style"]["textDecoration"], "UNDERLINE");
assert!(warnings.iter().any(|w| w == "text path underline or strikethrough decoration is not rendered"), "{warnings:?}");
}
/// A component holding a text on a path (path 100 long) and a half-scale instance of it.
fn scaled_instance_message() -> Kv<'static> {
let identity = || o(vec![("m00", f(1.0)), ("m01", f(0.0)), ("m02", f(0.0)), ("m10", f(0.0)), ("m11", f(1.0)), ("m12", f(0.0))]);
let nodes = vec![
node(0, None, "DOCUMENT", vec![]),
node(1, Some(0), "CANVAS", vec![]),
node(5, Some(1), "SYMBOL", vec![("name", s("Main")), ("size", point(100.0, 100.0)), ("transform", identity())]),
node(
6,
Some(5),
"TEXT_PATH",
vec![
("name", s("arc")),
("size", point(100.0, 0.0)),
("transform", identity()),
("textData", o(vec![("characters", s("alpha"))])),
("fontSize", f(24.0)),
("fontName", o(vec![("family", s("Inter")), ("style", s("Regular"))])),
("textPathStart", o(vec![("tValue", f(0.5)), ("forward", Kv::Bool(true))])),
("vectorData", o(vec![("vectorNetworkBlob", u(0)), ("normalizedSize", point(100.0, 0.0))])),
],
),
node(
9,
Some(1),
"INSTANCE",
vec![
("name", s("Half")),
("size", point(50.0, 50.0)),
("transform", identity()),
("symbolData", o(vec![("symbolID", guid(5)), ("uniformScaleFactor", f(0.5))])),
],
),
];
o(vec![("nodeChanges", a(nodes)), ("blobs", a(vec![o(vec![("bytes", Kv::Bytes(blob()))])]))])
}
#[test]
fn a_text_on_a_path_in_a_scaled_instance_has_its_path_scaled() {
let (json, warnings, _) = super::map::to_rest(&scaled_instance_message(), "Scaled path", crate::rest::MAX_NODES);
assert!(!warnings.iter().any(|w| w.contains("text path")), "{warnings:?}");
let (doc, _) = crate::rest::import_fig(&json, &|_| None).unwrap();
let path_of = |parent: &str| {
let parent = doc.nodes().find(|n| n.name == parent).unwrap();
let node = doc.node(parent.children[0]).unwrap();
let NodeKind::Text(text) = &node.kind else { panic!("expected text") };
text.path.as_deref().unwrap().d.clone()
};
assert_eq!(path_of("Main"), "M0,0 L100,0");
assert_eq!(path_of("Half"), "M0,0 L50,0");
}