mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 05:42:38 +00:00
Merge pull request #2115 from GiovanniBot/fix/vector-node-corner-radius
M6: a vector point without a corner radius of its own takes the layer's on .fig import (#2114)
This commit is contained in:
6 files changed
+123
-5
No files matched your search
@@ -115,3 +115,6 @@ display_name = "Hamza0101"
|
||||
|
||||
[people.ashokram-si]
|
||||
real_name = "Ashok Ram"
|
||||
|
||||
[people.GiovanniBot]
|
||||
sync_github_name = true
|
||||
@@ -207,7 +207,9 @@ impl<'d> Exporter<'d> {
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
let mut st: Fields = Vec::new();
|
||||
if v.radius > 0.0 {
|
||||
// A point without a radius of its own is drawn with the layer's, so a sharp
|
||||
// point on a rounded layer says so.
|
||||
if v.radius > 0.0 || n.corners.radii[0] > 0.0 {
|
||||
st.push(("cornerRadius", fl(v.radius)));
|
||||
}
|
||||
if let Some(m) = v.mirror {
|
||||
|
||||
@@ -955,6 +955,25 @@ fn closed_vector_geometry_is_written_with_its_corner_smoothing() {
|
||||
assert_eq!(radii, vec![20.0, 0.0, 10.0, 0.0], "each point keeps its own radius");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sharp_points_on_a_rounded_vector_stay_sharp() {
|
||||
// A point without a radius takes the layer's when read, so a sharp point on a vector with a
|
||||
// layer radius is written with its own zero.
|
||||
let mut doc = Document::new("sharp point");
|
||||
let page = doc.first_page().unwrap();
|
||||
let mut network = VectorNetwork::from_points(&[(0.0, 0.0), (100.0, 0.0), (100.0, 100.0), (0.0, 100.0)].map(|(x, y)| Point::new(x, y)), true);
|
||||
for (v, r) in network.vertices.iter_mut().zip([8.0, 0.0, 8.0, 0.0]) {
|
||||
v.radius = r;
|
||||
}
|
||||
let id = doc.create(page, NodeKind::Vector { network }, "V", 0.0, 0.0, 100.0, 100.0).unwrap();
|
||||
doc.node_mut(id).unwrap().corners.radii = [8.0; 4];
|
||||
let (back, _) = round_trip(&doc, "sharp point");
|
||||
let v = back.nodes().find(|n| matches!(n.kind, NodeKind::Vector { .. })).unwrap();
|
||||
let NodeKind::Vector { network } = &v.kind else { panic!("vector") };
|
||||
let radii: Vec<f64> = network.vertices.iter().map(|x| x.radius).collect();
|
||||
assert_eq!(radii, vec![8.0, 0.0, 8.0, 0.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_written_schema_parses_and_round_trips() {
|
||||
let schema = Schema::parse(export::SCHEMA).unwrap();
|
||||
|
||||
@@ -356,8 +356,9 @@ struct Mapper<'a> {
|
||||
/// holding it) name it by `vectorNetworkRef`. Kept out of the JSON, where a large file's
|
||||
/// networks took gigabytes.
|
||||
networks: Vec<Option<VectorNetwork>>,
|
||||
/// Index in `networks` by its blob, position scale, style table and point-radius scale.
|
||||
network_ids: HashMap<(usize, u64, u64, usize, u64), usize>,
|
||||
/// Index in `networks` by its blob, position scale, style table, point-radius scale and the
|
||||
/// layer's corner radius (which points without one of their own take).
|
||||
network_ids: HashMap<(usize, u64, u64, usize, u64, u64), usize>,
|
||||
/// Region paint elements (paints and gradient stops) read so far: one budget for the whole
|
||||
/// file, since layers with region paints don't share their network.
|
||||
region_paint_elements: usize,
|
||||
|
||||
@@ -668,6 +668,8 @@ impl<'a> Mapper<'a> {
|
||||
size.zip(norm).filter(|(s, n)| s.is_finite() && *n > 1e-9).map_or(1.0, |(s, n)| s / n)
|
||||
};
|
||||
let (sx, sy) = (scale("x"), scale("y"));
|
||||
// A point whose style doesn't set a radius takes the layer's, as the editor draws it.
|
||||
let layer_radius = v.len("cornerRadius");
|
||||
let vertices: Vec<Value> = net
|
||||
.vertices
|
||||
.iter()
|
||||
@@ -676,7 +678,7 @@ impl<'a> Mapper<'a> {
|
||||
let st = style(p.style);
|
||||
// Point radii are lengths in this row's space, unlike the normalized positions.
|
||||
// An override/derived row already carries the instance's scale.
|
||||
if let Some(r) = st.and_then(|s| num(s, "cornerRadius")).and_then(|r| scaled(r, radius_scale)).filter(|r| *r > 0.0) {
|
||||
if let Some(r) = st.and_then(|s| num(s, "cornerRadius")).and_then(|r| scaled(r, radius_scale)).or(layer_radius).filter(|r| *r > 0.0) {
|
||||
out["radius"] = json!(r);
|
||||
}
|
||||
match st.and_then(|s| s.get("handleMirroring")).and_then(Kv::str) {
|
||||
@@ -793,7 +795,8 @@ impl<'a> Mapper<'a> {
|
||||
return true;
|
||||
}
|
||||
// A resized instance and a K-scaled one can have the same box but different radii.
|
||||
let key = (blob_id, sx.to_bits(), sy.to_bits(), std::ptr::from_ref(vd) as usize, radius_scale.to_bits());
|
||||
let key =
|
||||
(blob_id, sx.to_bits(), sy.to_bits(), std::ptr::from_ref(vd) as usize, radius_scale.to_bits(), layer_radius.unwrap_or(0.0).to_bits());
|
||||
let id = match self.network_ids.get(&key) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
|
||||
@@ -521,6 +521,96 @@ fn network_of<'d>(doc: &'d figcraft_doc::Document, name: &str) -> &'d figcraft_d
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vector_points_without_a_radius_of_their_own_take_the_layers() {
|
||||
// A vector rounded from the layer's corner radius: the editor rounds every point whose
|
||||
// style doesn't set a radius (no style, or one without `cornerRadius`) with the layer's,
|
||||
// while a point's own radius, zero included, wins.
|
||||
let mut message = tail_message();
|
||||
let Kv::Array(nodes) = field_mut(&mut message, "nodeChanges").unwrap() else { panic!() };
|
||||
let wave = nodes.iter_mut().find(|node| node.get("name").and_then(Kv::str) == Some("Wave")).unwrap();
|
||||
let Kv::Object(fields) = wave else { panic!() };
|
||||
fields.push(("cornerRadius", f(6.0)));
|
||||
let vector_data = field_mut(wave, "vectorData").unwrap();
|
||||
let Kv::Array(styles) = field_mut(vector_data, "styleOverrideTable").unwrap() else { panic!() };
|
||||
let Kv::Object(sharp) = styles.get_mut(2).unwrap() else { panic!() };
|
||||
sharp.push(("cornerRadius", f(0.0)));
|
||||
let (doc, _) = import(&canvas(&schema(), &message), "Rounded").unwrap();
|
||||
let radii: Vec<f64> = network_of(&doc, "Wave").vertices.iter().map(|v| v.radius).collect();
|
||||
assert_eq!(radii, [6.0, 4.0, 6.0, 0.0], "no style, own radius, style without one, own zero");
|
||||
// Without a layer radius, the points without one of their own stay sharp.
|
||||
let (doc, _) = import(&tail_canvas(), "Tail").unwrap();
|
||||
let radii: Vec<f64> = network_of(&doc, "Wave").vertices.iter().map(|v| v.radius).collect();
|
||||
assert_eq!(radii, [0.0, 4.0, 0.0, 0.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_instance_that_rounds_a_vector_keeps_its_own_points() {
|
||||
// An instance's copy shares its main's vector data; overriding the layer's radius must not
|
||||
// reuse the main's network, nor hand the instance's to the main.
|
||||
let mut message = tail_message();
|
||||
let Kv::Array(nodes) = field_mut(&mut message, "nodeChanges").unwrap() else { panic!() };
|
||||
let wave = nodes.iter_mut().find(|node| node.get("name").and_then(Kv::str) == Some("Wave")).unwrap();
|
||||
*field_mut(wave, "parentIndex").unwrap() = o(vec![("guid", g(50)), ("position", s("!"))]);
|
||||
*field_mut(wave, "transform").unwrap() = at(0.0, 0.0);
|
||||
let rounded = o(vec![("guidPath", o(vec![("guids", arr(vec![g(30)]))])), ("cornerRadius", f(6.0))]);
|
||||
nodes.push(node(50, Some((1, "~")), "SYMBOL", "Mark", vec![("size", xy(200.0, 100.0)), ("transform", at(700.0, 0.0))]));
|
||||
nodes.push(node(
|
||||
51,
|
||||
Some((1, "~!")),
|
||||
"INSTANCE",
|
||||
"Rounded mark",
|
||||
vec![
|
||||
("size", xy(200.0, 100.0)),
|
||||
("transform", at(700.0, 400.0)),
|
||||
("symbolData", o(vec![("symbolID", g(50)), ("symbolOverrides", arr(vec![rounded]))])),
|
||||
],
|
||||
));
|
||||
let (doc, _) = import(&canvas(&schema(), &message), "Instance").unwrap();
|
||||
let radii_under = |parent: &str| -> Vec<f64> {
|
||||
let p = named(&doc, parent);
|
||||
let wave = p.children.iter().map(|c| doc.node(*c).unwrap()).find(|n| n.name == "Wave").unwrap();
|
||||
let NodeKind::Vector { network } = &wave.kind else { panic!() };
|
||||
network.vertices.iter().map(|v| v.radius).collect()
|
||||
};
|
||||
assert_eq!(radii_under("Mark"), [0.0, 4.0, 0.0, 0.0]);
|
||||
assert_eq!(radii_under("Rounded mark"), [6.0, 4.0, 6.0, 6.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scaled_instance_scales_its_vectors_point_radii() {
|
||||
// Point radii are lengths: an instance scaled with the Scale tool draws its main's, from a
|
||||
// point's own style or from the layer, times the factor (#2091).
|
||||
let mut message = tail_message();
|
||||
let Kv::Array(nodes) = field_mut(&mut message, "nodeChanges").unwrap() else { panic!() };
|
||||
let wave = nodes.iter_mut().find(|node| node.get("name").and_then(Kv::str) == Some("Wave")).unwrap();
|
||||
*field_mut(wave, "parentIndex").unwrap() = o(vec![("guid", g(50)), ("position", s("!"))]);
|
||||
*field_mut(wave, "transform").unwrap() = at(0.0, 0.0);
|
||||
let Kv::Object(fields) = wave else { panic!() };
|
||||
fields.push(("cornerRadius", f(3.0)));
|
||||
nodes.push(node(50, Some((1, "~")), "SYMBOL", "Mark", vec![("size", xy(200.0, 100.0)), ("transform", at(700.0, 0.0))]));
|
||||
nodes.push(node(
|
||||
51,
|
||||
Some((1, "~!")),
|
||||
"INSTANCE",
|
||||
"Big mark",
|
||||
vec![
|
||||
("size", xy(400.0, 200.0)),
|
||||
("transform", at(700.0, 400.0)),
|
||||
("symbolData", o(vec![("symbolID", g(50)), ("uniformScaleFactor", f(2.0))])),
|
||||
],
|
||||
));
|
||||
let (doc, _) = import(&canvas(&schema(), &message), "Scaled").unwrap();
|
||||
let radii_under = |parent: &str| -> Vec<f64> {
|
||||
let p = named(&doc, parent);
|
||||
let wave = p.children.iter().map(|c| doc.node(*c).unwrap()).find(|n| n.name == "Wave").unwrap();
|
||||
let NodeKind::Vector { network } = &wave.kind else { panic!() };
|
||||
network.vertices.iter().map(|v| v.radius).collect()
|
||||
};
|
||||
assert_eq!(radii_under("Mark"), [3.0, 4.0, 3.0, 3.0]);
|
||||
assert_eq!(radii_under("Big mark"), [6.0, 8.0, 6.0, 6.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vectors_import_as_editable_networks() {
|
||||
let (doc, warnings) = import(&tail_canvas(), "Tail").unwrap();
|
||||
|
||||
Reference in new issue
Block a user