Use shared writer for IDML style groups

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 07:07:00 -05:00
1 parent 75d1b844f3
commit 511187946b
4 files changed
+33 -83

No files matched your search

Generated
+10 -10
View File
@@ -1242,10 +1242,10 @@ dependencies = [
[[package]]
name = "craft-idml"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=8345921af#8345921aff971504d55cc202b93d2ca6aa5932de"
source = "git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c#3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c"
dependencies = [
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"thiserror 2.0.21",
]
@@ -1647,7 +1647,7 @@ dependencies = [
[[package]]
name = "craft-xml"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=40525913633942f84b06d4837a8d90534a55d63f#40525913633942f84b06d4837a8d90534a55d63f"
source = "git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c#3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c"
dependencies = [
"quick-xml 0.41.0",
"thiserror 2.0.21",
@@ -1656,7 +1656,7 @@ dependencies = [
[[package]]
name = "craft-xml"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=8345921af#8345921aff971504d55cc202b93d2ca6aa5932de"
source = "git+https://github.com/storytold/craft-libs?rev=40525913633942f84b06d4837a8d90534a55d63f#40525913633942f84b06d4837a8d90534a55d63f"
dependencies = [
"quick-xml 0.41.0",
"thiserror 2.0.21",
@@ -1730,7 +1730,7 @@ dependencies = [
[[package]]
name = "craft-zip"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=8345921af#8345921aff971504d55cc202b93d2ca6aa5932de"
source = "git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c#3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c"
dependencies = [
"flate2",
"thiserror 2.0.21",
@@ -2021,7 +2021,7 @@ dependencies = [
"craft-prepress",
"craft-print",
"craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=f23b0f33f0d00b157476533a43cbbcfd901aa1f1)",
"designcraft-color",
"designcraft-compose",
@@ -2103,9 +2103,9 @@ name = "designcraft-idml"
version = "0.20.1"
dependencies = [
"craft-file-formats",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"craft-svg 0.1.0 (git+https://github.com/storytold/craft-libs?rev=cc23ffd4d26f3074808e2187d332564817624f8f)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"designcraft-color",
"designcraft-compose",
"designcraft-doc",
@@ -2222,7 +2222,7 @@ version = "0.20.1"
dependencies = [
"craft-docx",
"craft-file-formats",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=8345921af)",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c)",
"craft-rtf",
"craft-wordmodel",
"craft-xlsx",
+2 -2
View File
@@ -16,10 +16,10 @@ craft-pdf = { git = "https://github.com/storytold/craft-libs", rev = "4052591363
craft-designcraft-format = { git = "https://github.com/storytold/craft-libs", rev = "19692b6190465671e41967c75d0cd4c439d0c84c", version = "=0.1.0" }
craft-eps = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
craft-epub = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "8345921af", version = "=0.1.0" }
craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c", version = "=0.1.0" }
craft-indd = { git = "https://github.com/storytold/craft-libs", rev = "47bf5650b0d503b97bfb5e841746c4957897d11f", version = "=0.1.0" }
craft-svg = { git = "https://github.com/storytold/craft-libs", rev = "cc23ffd4d26f3074808e2187d332564817624f8f", version = "=0.1.0", features = ["tree-render", "tree-text"] }
craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "8345921af", version = "=0.1.0" }
craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "3d82c75d5d0ffcbb7bb39aff687f69ce8c4a713c", version = "=0.1.0" }
craft-xml-dtd = { package = "craft-xml", git = "https://github.com/storytold/craft-libs", rev = "f23b0f33f0d00b157476533a43cbbcfd901aa1f1", version = "=0.1.0" }
craft-rtf = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
craft-xlsx = { git = "https://github.com/storytold/craft-libs", rev = "ba24fe2435adb3c81ad6057faf36365fbba736cf", version = "=0.1.0" }
+11 -55
View File
@@ -1,7 +1,6 @@
use super::common::{affine_s, bool_s, p, pct, uid, with_props};
use super::{
Affine, ColumnsKind, Content, El, Ex, HashSet, Item, Node, OBJECT_BUILTINS, PathData, Shape, TextFrameOptions, base64_encode, escape_id, names,
num, pt,
Affine, ColumnsKind, Content, El, Ex, HashSet, Item, Node, OBJECT_BUILTINS, PathData, Shape, TextFrameOptions, base64_encode, names, num, pt,
};
impl<'a> Ex<'a> {
@@ -324,49 +323,6 @@ impl<'a> Ex<'a> {
}
}
/// Most nested style folders one style name can create.
const MAX_STYLE_GROUP_DEPTH: usize = 32;
/// Style folder tree (names `A/B/C` → groups A, B).
#[derive(Default)]
pub(super) struct Group {
styles: Vec<El>,
groups: Vec<(String, Group)>,
}
impl Group {
pub(super) fn insert(&mut self, name: &str, el: El) {
let builtin = name.starts_with('[');
let mut segs: Vec<&str> = if builtin { vec![name] } else { name.split('/').collect() };
let _leaf = segs.pop();
let mut g = self;
// Deeper folders than this are not made (the style lands in the deepest one), so the recursive `emit` is bounded.
for s in segs.into_iter().take(MAX_STYLE_GROUP_DEPTH) {
let i = match g.groups.iter().position(|(n, _)| n == s) {
Some(i) => i,
None => {
g.groups.push((s.to_string(), Group::default()));
g.groups.len() - 1
}
};
g = &mut g.groups[i].1;
}
g.styles.push(el);
}
pub(super) fn emit(self, parent: &mut El, kind: &str, path: &str) {
for s in self.styles {
parent.push(s);
}
for (name, g) in self.groups {
let full = if path.is_empty() { name.clone() } else { format!("{path}:{name}") };
let mut ge = El::new(kind).attr("Self", format!("{kind}/{}", escape_id(&full))).attr("Name", &name);
g.emit(&mut ge, kind, &full);
parent.push(ge);
}
}
}
/// `rule_color` is the IDML reference of the column rule's swatch.
/// `TextWrapPreference` of a page item or a placed graphic.
fn text_wrap_pref(w: &designcraft_doc::TextWrap) -> El {
@@ -522,20 +478,20 @@ pub(crate) fn path_to_uri(path: &str) -> String {
#[cfg(test)]
mod group_depth_tests {
use super::*;
use craft_idml::model::{StyleGroupWriter, StyleKind};
#[test]
fn a_style_name_with_many_separators_makes_bounded_folders() {
let mut g = Group::default();
g.insert(&"a/".repeat(10_000), El::new("S"));
let (mut depth, mut cur) = (0, &g);
while let Some((_, next)) = cur.groups.first() {
let mut group = StyleGroupWriter::new(StyleKind::Character, "root");
let style = craft_idml::El::new("CharacterStyle");
group.insert(&"a/".repeat(10_000), style);
let root = group.to_el();
let mut depth = 0;
let mut current = root.elements().next();
while let Some(el) = current.filter(|el| el.local() == "CharacterStyleGroup") {
depth += 1;
cur = next;
current = el.elements().next();
}
assert_eq!(depth, MAX_STYLE_GROUP_DEPTH);
assert_eq!(cur.styles.len(), 1);
let mut root = El::new("Root");
g.emit(&mut root, "K", "");
assert_eq!(depth, 32);
}
}
+10 -16
View File
@@ -1,5 +1,5 @@
use super::common::{bool_s, p, pct, pkg_root, with_props};
use super::item::{Group, text_frame_pref};
use super::item::text_frame_pref;
use super::story::with_list_tab;
use super::{
CHAR_BUILTINS, El, Ex, NO_PARA_ID, OBJECT_BUILTINS, PARA_BUILTINS, Rule, TOC_STYLE_ID, TOC_STYLE_NAME, document, escape_id, names, num, st,
@@ -14,7 +14,7 @@ impl<'a> Ex<'a> {
let mut root = pkg_root("Styles");
// Character styles.
let mut cg = Group::default();
let mut cg = craft_idml::model::StyleGroupWriter::new(craft_idml::model::StyleKind::Character, self.fresh());
for cs in &styles.character {
let id = names::style_self("CharacterStyle", CHAR_BUILTINS, &cs.name);
let mut el =
@@ -32,14 +32,12 @@ impl<'a> Ex<'a> {
if let Some(t) = styles.export_tag(&cs.name, true) {
el.push(self.export_tag_map(t));
}
cg.insert(&cs.name, el);
cg.insert(&cs.name, crate::xml::to_craft(&el));
}
let mut rc = El::new("RootCharacterStyleGroup").attr("Self", self.fresh());
cg.emit(&mut rc, "CharacterStyleGroup", "");
root.push(rc);
root.push(crate::xml::from_craft(cg.to_el()));
// Paragraph styles.
let mut pg = Group::default();
let mut pg = craft_idml::model::StyleGroupWriter::new(craft_idml::model::StyleKind::Paragraph, self.fresh());
for ps in &styles.paragraph {
let id = names::style_self("ParagraphStyle", PARA_BUILTINS, &ps.name);
let mut el =
@@ -77,11 +75,9 @@ impl<'a> Ex<'a> {
if let Some(t) = tag {
el.push(self.export_tag_map(t));
}
pg.insert(&ps.name, el);
pg.insert(&ps.name, crate::xml::to_craft(&el));
}
let mut rp = El::new("RootParagraphStyleGroup").attr("Self", self.fresh());
pg.emit(&mut rp, "ParagraphStyleGroup", "");
root.push(rp);
root.push(crate::xml::from_craft(pg.to_el()));
self.append_cell_styles(&mut root, &styles);
self.append_table_styles(&mut root, &styles);
@@ -97,7 +93,7 @@ impl<'a> Ex<'a> {
}
// Object styles.
let mut og = Group::default();
let mut og = craft_idml::model::StyleGroupWriter::new(craft_idml::model::StyleKind::Object, self.fresh());
let mut have: Vec<&str> = styles.object.iter().map(|o| o.name.as_str()).collect();
let defaults = designcraft_doc::Styles::default();
let mut all: Vec<designcraft_doc::ObjectStyle> = styles.object.clone();
@@ -157,11 +153,9 @@ impl<'a> Ex<'a> {
.attr("EnableDirectionalFeather", "false")
.attr("EnableGradientFeather", "false"),
);
og.insert(&os.name, el);
og.insert(&os.name, crate::xml::to_craft(&el));
}
let mut ro = El::new("RootObjectStyleGroup").attr("Self", self.fresh());
og.emit(&mut ro, "ObjectStyleGroup", "");
root.push(ro);
root.push(crate::xml::from_craft(og.to_el()));
if let Some(toc) = d.toc.as_ref().filter(|t| d.story(t.story).is_some()) {
root.push(self.toc_style_el(toc));
}