use super::component::{assigned, prop_guid}; use super::paint::color; use super::variables::variable_value; use super::{Affine, BTreeMap, BTreeSet, Kv, MAX_DEPTH, Mapper, Obj, Value, View, copy_str, guid, json, rows}; impl<'a> Mapper<'a> { /// The DOCUMENT with its pages. pub(super) fn document(&mut self) -> Value { let doc_id = self.nodes.iter().find(|(_, n)| n.get("type").and_then(Kv::str) == Some("DOCUMENT")).map(|(id, _)| id.clone()); let pages: Vec = match &doc_id { Some(d) => self.children(d), None => self.nodes.iter().filter(|(_, n)| n.get("type").and_then(Kv::str) == Some("CANVAS")).map(|(id, _)| id.clone()).collect(), }; let mut internal = Vec::new(); let mut visible = Vec::new(); for p in pages { let Some(&n) = self.nodes.get(&p) else { continue }; if n.get("type").and_then(Kv::str) != Some("CANVAS") { self.warn("document children other than pages were skipped"); continue; } // The hidden page that holds styles, variables and library copies. if n.get("internalOnly").and_then(Kv::bool) == Some(true) { internal.push((p, n)); } else { visible.push((p, n)); } } // The library copies the pages use come first: in a file past the layer cap, the pages' // last layers are skipped rather than the mains every instance of them needs (#981). let visible_pages: Vec = visible.iter().map(|(p, _)| p.clone()).collect(); let required_mains = self.referenced_component_mains(&visible_pages); // The pages and their top-level layers, up to half the cap, stay for the pages. let top: usize = visible_pages.iter().map(|p| 1 + self.children(p).len()).sum(); self.reserved = top.min(self.max_nodes / 2); for (id, canvas) in internal { if let Some(context) = self.library_context(&id, canvas, &required_mains) { self.library_contexts.push(context); } } self.reserved = 0; let out: Vec = visible.into_iter().map(|(p, n)| self.page(&p, n)).collect(); if out.is_empty() { self.warn("the file has no pages"); } let mut doc = json!({"id": doc_id.unwrap_or_else(|| "0:0".into()), "type": "DOCUMENT"}); doc["children"] = Value::Array(out); doc } /// The ids of the components the visible pages use, directly or through the components they /// use in turn (nested instances, swap targets, overrides naming another main). fn referenced_component_mains(&mut self, visible_pages: &[String]) -> BTreeSet { let mut mains = BTreeSet::new(); let mut visited = BTreeSet::new(); let mut pending: Vec<(String, usize)> = visible_pages.iter().flat_map(|page| self.children(page)).map(|id| (id, 1)).collect(); while let Some((id, depth)) = pending.pop() { if depth > MAX_DEPTH { self.warn(format!("component references below {MAX_DEPTH} layer levels were skipped")); continue; } if !visited.insert(id.clone()) { continue; } if visited.len() > self.max_nodes { let max = self.max_nodes; self.warn(format!("the component reference scan reached the {max}-layer limit")); break; } let Some(node) = self.nodes.get(&id) else { continue }; for target in self.component_main_targets(node) { if mains.insert(target.clone()) { pending.push((target, 1)); } } pending.extend(self.children(&id).into_iter().map(|child| (child, depth + 1))); } mains } /// The mains a layer names: an instance's own, the ones its overrides swap in, the instance-swap /// defaults and values of component properties. fn component_main_targets(&self, node: &Kv<'a>) -> Vec { let mut targets: Vec = node .get("componentPropDefs") .map(Kv::list) .unwrap_or_default() .iter() .filter_map(|definition| { self.is_instance_swap(definition.get("id")?)?; definition.get("initialValue")?.get("guidValue").and_then(guid) }) .collect(); if node.get("type").and_then(Kv::str) != Some("INSTANCE") { return targets; } // The instance and the override rows below it (rows carry the same fields, nested). let mut pending: Vec<(&Kv<'a>, usize)> = vec![(node, 0)]; let mut rows = 0usize; while let Some((row, depth)) = pending.pop() { rows += 1; if depth > MAX_DEPTH || rows > self.max_nodes { break; } let data = row.get("symbolData"); targets.extend(row.get("overriddenSymbolID").and_then(guid)); targets.extend(data.and_then(|d| d.get("symbolID")).and_then(guid)); for assignment in row.get("componentPropAssignments").map(Kv::list).unwrap_or_default() { if assignment.get("defID").is_some_and(|def| self.is_instance_swap(def).is_some()) { targets.extend(assigned(assignment).and_then(prop_guid)); } } let overrides = data.and_then(|d| d.get("symbolOverrides")).map(Kv::list).unwrap_or_default(); let derived = row.get("derivedSymbolData").map(Kv::list).unwrap_or_default(); pending.extend(overrides.iter().chain(derived).map(|row| (row, depth + 1))); } targets } /// The branches of the internal canvas `id` that hold the `required_mains` (with the frames /// they sit in), as a frame of its own: the cached definitions of components that come from a /// library. `None` when it holds none. fn library_context(&mut self, id: &str, canvas: &'a Kv<'a>, required_mains: &BTreeSet) -> Option { let mut included = BTreeSet::new(); let mut parents: BTreeMap = BTreeMap::new(); // The canvas itself is never a layer of the walk, whatever the parent links say. let mut visited = BTreeSet::from([id.to_string()]); // Each entry: layer, depth, inside a required definition. let mut pending: Vec<(String, usize, bool)> = self.children(id).into_iter().map(|child| (child, 1, false)).collect(); while let Some((node_id, depth, in_definition)) = pending.pop() { if depth > MAX_DEPTH { self.warn(format!("internal library branches deeper than {MAX_DEPTH} levels were skipped")); continue; } if !visited.insert(node_id.clone()) { continue; } self.library_scan_count += 1; if self.library_scan_count > self.max_nodes { let max = self.max_nodes; self.warn(format!("the internal library scan reached the {max}-layer limit; the remaining branches were skipped")); break; } let Some(node) = self.nodes.get(&node_id) else { continue }; let kind = node.get("type").and_then(Kv::str); let definition = (kind == Some("SYMBOL") || (kind == Some("FRAME") && node.get("isStateGroup").and_then(Kv::bool) == Some(true))) && required_mains.contains(&node_id); let in_definition = in_definition || definition; if in_definition { included.insert(node_id.clone()); } if definition { // The frames around it keep the path its layers are addressed by. let mut ancestor = parents.get(&node_id); for _ in 0..=MAX_DEPTH { let Some(parent) = ancestor else { break }; if !included.insert(parent.clone()) { break; } ancestor = parents.get(parent); } } for child in self.children(&node_id).into_iter().rev() { // A layer reached again keeps the parent it was first reached by, so the parents // lead back to the canvas. if visited.contains(&child) { continue; } parents.entry(child.clone()).or_insert_with(|| node_id.clone()); pending.push((child, depth + 1, in_definition)); } } if included.is_empty() { return None; } let mut context = Obj::new(); context.insert("id".into(), json!(id)); context.insert("type".into(), json!("FRAME")); copy_str(&mut context, &View::of(canvas), "name", "name"); context.insert("size".into(), json!({"x": 0.0, "y": 0.0})); context.insert("relativeTransform".into(), rows(Affine::IDENTITY)); context.insert(crate::rest::FIG_SOURCE_FIELD.into(), json!({"origin": id, "expanded": false, "wireFrame": true, "wireInstance": false})); self.variable_bindings(&mut context, &View::of(canvas)); let mut children = Vec::new(); for child in self.children(id) { children.extend(self.layer_filtered(&child, None, Affine::IDENTITY, None, None, 0, Some(&included))); } if !children.is_empty() { context.insert("children".into(), Value::Array(children)); } Some(Value::Object(context)) } fn page(&mut self, id: &str, n: &'a Kv<'a>) -> Value { let v = View::of(n); let mut o = Obj::new(); o.insert("id".into(), json!(id)); o.insert("type".into(), json!("CANVAS")); copy_str(&mut o, &v, "name", "name"); if let Some(c) = v.get("backgroundColor") { o.insert("backgroundColor".into(), color(c)); } if let Some(c) = v.get("prototypeBackgroundColor") { o.insert("prototypeBackgrounds".into(), json!([color(c)])); } self.variable_bindings(&mut o, &v); let mut flows: Vec<(&str, Value)> = Vec::new(); let mut kids = Vec::new(); for c in self.children(id) { if let Some(sp) = self.nodes.get(&c).and_then(|k| k.get("prototypeStartingPoint")) { let name = sp.get("name").and_then(Kv::str).filter(|s| !s.is_empty()).unwrap_or("Flow"); flows.push((sp.get("position").and_then(Kv::str).unwrap_or(""), json!({"nodeId": c, "name": name}))); } kids.extend(self.layer(&c, None, Affine::IDENTITY, None, None, 0)); } flows.sort_by(|a, b| a.0.cmp(b.0)); if !flows.is_empty() { o.insert("flowStartingPoints".into(), Value::Array(flows.into_iter().map(|f| f.1).collect())); } o.insert("children".into(), Value::Array(kids)); Value::Object(o) } /// The shared styles the file defines, in their order: each with the sample layer that holds /// its values (`styleDefinitions`, our extension of the file JSON; the REST importer reads it). pub(super) fn style_definitions(&mut self) -> Vec { let mut found: Vec<(&'a str, String, &'a Kv<'a>)> = Vec::new(); for (id, n) in &self.nodes { let live = n.get("isSoftDeleted").and_then(Kv::bool) != Some(true) && n.get("isSoftDeletedStyle").and_then(Kv::bool) != Some(true); if live && n.get("styleType").and_then(Kv::str).is_some_and(|t| matches!(t, "FILL" | "TEXT" | "EFFECT" | "GRID")) { found.push((n.get("sortPosition").and_then(Kv::str).unwrap_or(""), id.clone(), n)); } } found.sort_by(|a, b| (a.0, &a.1).cmp(&(b.0, &b.1))); let mut out = Vec::new(); for (_, id, n) in found { let Some(node) = self.layer(&id, None, Affine::IDENTITY, None, None, 0) else { break }; let name = n.get("name").and_then(Kv::str).unwrap_or("Style"); let desc = n.get("styleDescription").or_else(|| n.get("description")).and_then(Kv::str).unwrap_or(""); let ty = n.get("styleType").and_then(Kv::str).unwrap_or(""); out.push(json!({"id": id, "name": name, "description": desc, "styleType": ty, "node": node})); } out } /// Collections and variables, as the local variables response's `meta`, with their ids in the /// file's order (`collectionOrder` and `variableOrder`, our extensions). pub(super) fn variables(&self) -> Option { let of_type = |t: &'static str, key: &'static str| { let live = move |n: &Kv| n.get("type").and_then(Kv::str) == Some(t) && n.get("isSoftDeleted").and_then(Kv::bool) != Some(true); let mut list: Vec<(&str, &String, &'a Kv<'a>)> = self .nodes .iter() .filter(|(_, n)| live(n)) .map(|(id, n)| (n.get(key).and_then(|p| p.get("position").or(Some(p))).and_then(Kv::str).unwrap_or(""), id, *n)) .collect(); list.sort_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1))); list.into_iter().map(|(_, id, n)| (id, n)) }; let mut order = Vec::new(); let mut collections = Obj::new(); // Copies of one library collection (one per version the file met) become one: the newest // keeps its modes in its order, the others add the modes it no longer has. let mut extra: BTreeMap> = BTreeMap::new(); for (id, n) in of_type("VARIABLE_SET", "parentIndex") { let modes = sorted_modes(n); match self.canonical_collection(id).filter(|c| c != id) { Some(canonical) => extra.entry(canonical).or_default().extend(modes), None => { order.push(json!(id)); let default = modes.first().map_or(Value::Null, |m| m.1["modeId"].clone()); let name = n.get("name").and_then(Kv::str).unwrap_or("Collection"); let modes: Vec = modes.into_iter().map(|m| m.1).collect(); collections.insert(id.clone(), json!({"id": id, "name": name, "modes": modes, "defaultModeId": default})); } } } for (canonical, more) in extra { let Some(modes) = collections.get_mut(&canonical).and_then(|c| c.get_mut("modes")).and_then(Value::as_array_mut) else { continue }; for (_, m) in more { if !modes.iter().any(|x| x["modeId"] == m["modeId"]) { modes.push(m); } } } let mut variable_order = Vec::new(); let mut variables = Obj::new(); for (id, n) in of_type("VARIABLE", "sortPosition") { if super::VarCanon::superseded(id) { continue; } let Some(set) = n.get("variableSetID").and_then(|s| self.collection_id(s)).filter(|s| collections.contains_key(s)) else { continue }; let ty = n.get("variableResolvedType").and_then(Kv::str).unwrap_or(""); if !matches!(ty, "BOOLEAN" | "FLOAT" | "STRING" | "COLOR") { continue; } let mut values = Obj::new(); for e in n.get("variableDataValues").and_then(|m| m.get("entries")).map(Kv::list).unwrap_or_default() { if let (Some(mode), Some(val)) = (e.get("modeID").and_then(guid), e.get("variableData").and_then(variable_value)) { values.insert(mode, val); } } let name = n.get("name").and_then(Kv::str).unwrap_or("Variable"); let desc = n.get("description").and_then(Kv::str).unwrap_or(""); let mut var = json!({"id": id, "name": name, "variableCollectionId": set, "resolvedType": ty, "valuesByMode": values, "description": desc}); // The properties pickers offer the variable for, under the REST names. if let Some(scopes) = n.get("variableScopes") { var["scopes"] = scopes.list().iter().filter_map(Kv::str).map(|s| if s == "STROKE" { "STROKE_COLOR" } else { s }).collect(); } variable_order.push(json!(id)); variables.insert(id.clone(), var); } (!collections.is_empty()) .then(|| json!({"variableCollections": collections, "variables": variables, "collectionOrder": order, "variableOrder": variable_order})) } } /// A collection node's modes in their order (`sortPosition`), as `(position, {modeId, name})`. fn sorted_modes(n: &Kv) -> Vec<(String, Value)> { let mut modes: Vec<(String, Value)> = n .get("variableSetModes") .map(Kv::list) .unwrap_or_default() .iter() .filter_map(|m| { let mode = json!({"modeId": m.get("id").and_then(guid)?, "name": m.get("name").and_then(Kv::str).unwrap_or("Mode")}); Some((m.get("sortPosition").and_then(Kv::str).unwrap_or("").to_string(), mode)) }) .collect(); modes.sort_by(|a, b| a.0.cmp(&b.0)); modes }