vectorcraft/crates/format/src/legacy.rs

50 lines
2.1 KiB
Rust

//! Writing for older readers: format v1's anchor form.
use serde_json::{Value, json};
/// Rewrite every anchor in a serialized document (`anchors` arrays of path data) the way format v1
/// wrote it: `{p: {x, y}, in: {x, y}, out: {x, y}, kind}`, every member present (v1 readers need
/// them all; v2 writes `{p: [x, y], in?, out?, kind?}`).
pub(crate) fn anchors_to_v1(v: &mut Value) {
match v {
Value::Object(o) => {
for (k, x) in o.iter_mut() {
match x {
Value::Array(anchors) if k == "anchors" => anchors.iter_mut().for_each(anchor_to_v1),
_ => anchors_to_v1(x),
}
}
}
Value::Array(a) => a.iter_mut().for_each(anchors_to_v1),
_ => {}
}
}
fn anchor_to_v1(a: &mut Value) {
let point = |v: &Value| match v.as_array().map(Vec::as_slice) {
Some([x, y]) => Some(json!({ "x": x, "y": y })),
_ => None,
};
let Some(o) = a.as_object() else { return };
// Not a v2 anchor (already a map, or something else called `anchors`): leave it alone.
let Some(p) = o.get("p").and_then(point) else { return };
let handle = |k: &str| o.get(k).and_then(point).unwrap_or_else(|| p.clone());
let kind = o.get("kind").cloned().unwrap_or_else(|| json!("Corner"));
*a = json!({ "p": p, "in": handle("in"), "out": handle("out"), "kind": kind });
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn anchors_are_written_the_v1_way() {
let mut v = json!({"layers": [{"path": {"subpaths": [{"anchors": [{"p": [1.0, 2.0]}, {"p": [3.0, 4.0], "out": [5.0, 4.0], "kind": "Smooth"}], "closed": true}]}}], "effect": {"anchors": true}});
anchors_to_v1(&mut v);
let a = &v["layers"][0]["path"]["subpaths"][0]["anchors"];
assert_eq!(a[0], json!({"p": {"x": 1.0, "y": 2.0}, "in": {"x": 1.0, "y": 2.0}, "out": {"x": 1.0, "y": 2.0}, "kind": "Corner"}));
assert_eq!(a[1], json!({"p": {"x": 3.0, "y": 4.0}, "in": {"x": 3.0, "y": 4.0}, "out": {"x": 5.0, "y": 4.0}, "kind": "Smooth"}));
assert_eq!(v["effect"]["anchors"], json!(true));
}
}