Use shared codec for placed graphic options

This commit is contained in:
Dylan M. Taylor committed 2026-11-13 07:32:50 -05:00
1 parent e61f7baf54
commit 3d8ddc5978
5 files changed
+27 -100

No files matched your search

Generated
+9 -9
View File
@@ -1242,10 +1242,10 @@ dependencies = [
[[package]]
name = "craft-idml"
version = "0.1.0"
source = "git+https://github.com/storytold/craft-libs?rev=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a#7435724d60a321fdee6d87b1aea6e0c7cc3dea2a"
source = "git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71#76d9d83769d2a53cf801b659e361c5d35d928e71"
dependencies = [
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"craft-zip 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a#7435724d60a321fdee6d87b1aea6e0c7cc3dea2a"
source = "git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71#76d9d83769d2a53cf801b659e361c5d35d928e71"
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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a#7435724d60a321fdee6d87b1aea6e0c7cc3dea2a"
source = "git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71#76d9d83769d2a53cf801b659e361c5d35d928e71"
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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-xml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"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=7435724d60a321fdee6d87b1aea6e0c7cc3dea2a)",
"craft-idml 0.1.0 (git+https://github.com/storytold/craft-libs?rev=76d9d83769d2a53cf801b659e361c5d35d928e71)",
"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 = "7435724d60a321fdee6d87b1aea6e0c7cc3dea2a", version = "=0.1.0" }
craft-idml = { git = "https://github.com/storytold/craft-libs", rev = "76d9d83769d2a53cf801b659e361c5d35d928e71", 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 = "7435724d60a321fdee6d87b1aea6e0c7cc3dea2a", version = "=0.1.0" }
craft-xml = { git = "https://github.com/storytold/craft-libs", rev = "76d9d83769d2a53cf801b659e361c5d35d928e71", 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" }
-9
View File
@@ -5,15 +5,6 @@ use craft_idml::model::{
StrokeDefinition, TintDefinition,
};
// Retained IDML attributes have unprefixed ASCII names. Native JSON can be
// edited independently: never let its map keys become XML syntax or Self IDs.
pub(super) fn graphic_layer_attribute_name(key: &str) -> bool {
!key.is_empty()
&& key != "Self"
&& key != "xmlns"
&& key.bytes().enumerate().all(|(i, b)| b.is_ascii_alphabetic() || b == b'_' || (i > 0 && (b.is_ascii_digit() || matches!(b, b'.' | b'-'))))
}
impl<'a> Ex<'a> {
/// A `Gradient` element; stop colours missing from the swatches become anonymous colours.
fn gradient_el(
+8 -47
View File
@@ -1,58 +1,19 @@
use super::common::{anchor_self, bool_s, change_type, topic_self, uid, with_props, xref_format_self};
use super::graphics::graphic_layer_attribute_name;
use super::{
BTreeMap, CHAR_BUILTINS, CharFormat, El, Ex, ListType, NO_CHAR_ID, Node, PARA_BUILTINS, ParaAttrs, ParaFormat, Story, TOC_STYLE_ID, names, num,
st,
};
impl<'a> Ex<'a> {
/// `<GraphicLayerOption>` of a placed graphic: its layer attributes and the nested layer tree.
/// `<GraphicLayerOption>` of a placed graphic: its layer attributes and nested layer tree.
pub(super) fn layer_option_el(&mut self, options: &designcraft_doc::GraphicLayerOptions) -> El {
let mut option = El::new("GraphicLayerOption");
for (key, value) in &options.attributes {
if graphic_layer_attribute_name(key) {
option.set(key, value);
}
}
// Only an ancestor whose subtree is still open can parent the next layer.
// Reopening a closed subtree would reorder native-edited lists on roundtrip.
let mut open = Vec::new();
let mut parents = Vec::with_capacity(options.layers.len());
for i in 0..options.layers.len() {
let parent = options.parents.get(i).copied().flatten();
if let Some(at) = parent.and_then(|p| open.iter().position(|id| *id == p)).filter(|at| *at < 239) {
open.truncate(at + 1);
parents.push(parent);
} else {
open.clear();
parents.push(None);
}
open.push(i);
}
let mut children: Vec<Vec<El>> = vec![Vec::new(); options.layers.len()];
let mut roots = Vec::new();
for (i, attributes) in options.layers.iter().enumerate().rev() {
let mut layer = El::new("GraphicLayer").attr("Self", self.fresh());
for (key, value) in attributes {
if graphic_layer_attribute_name(key) {
layer.set(key, value);
}
}
if let Some(nested) = children.get_mut(i) {
for child in std::mem::take(nested).into_iter().rev() {
layer.push(child);
}
}
if let Some(nested) = parents.get(i).copied().flatten().and_then(|parent| children.get_mut(parent)) {
nested.push(layer);
} else {
roots.push(layer);
}
}
for layer in roots.into_iter().rev() {
option.push(layer);
}
option
let model = craft_idml::model::GraphicLayerOptions {
attributes: options.attributes.iter().map(|(key, value)| (key.clone(), value.clone())).collect(),
layers: options.layers.iter().map(|layer| layer.iter().map(|(key, value)| (key.clone(), value.clone())).collect()).collect(),
parents: options.parents.clone(),
};
let self_id = self.fresh();
crate::xml::from_craft(model.to_el(&self_id, || self.fresh()))
}
/// `<Index>`: nested topics with their See / See also cross-references.
+8 -33
View File
@@ -243,22 +243,10 @@ impl<'r> Importer<'r> {
}
fn graphic(&mut self, g: &El, model: &craft_idml::model::Graphic) -> Content {
let layer_options = g.find("GraphicLayerOption").map(|options| {
let attrs = |e: &El| e.attrs.iter().filter(|(key, _)| key != "Self").cloned().collect();
let mut out = designcraft_doc::GraphicLayerOptions { attributes: attrs(options), ..Default::default() };
let mut pending: Vec<_> = options.find_all("GraphicLayer").map(|e| (e, None)).collect();
pending.reverse();
while let Some((layer, parent)) = pending.pop() {
let index = out.layers.len();
out.layers.push(attrs(layer));
out.parents.push(parent);
let children: Vec<_> = layer.find_all("GraphicLayer").collect();
pending.extend(children.into_iter().rev().map(|e| (e, Some(index))));
}
if out.parents.iter().all(Option::is_none) {
out.parents.clear();
}
out
let layer_options = model.layer_options().map(|options| designcraft_doc::GraphicLayerOptions {
attributes: options.attributes.into_iter().collect(),
layers: options.layers.into_iter().map(IntoIterator::into_iter).map(Iterator::collect).collect(),
parents: options.parents,
});
if layer_options.as_ref().is_some_and(designcraft_doc::GraphicLayerOptions::has_visibility_overrides) {
self.warnings.push(format!("graphic {}: {}", g.get("Self").unwrap_or("(unnamed)"), designcraft_doc::GRAPHIC_LAYER_RENDERING_WARNING));
@@ -294,9 +282,11 @@ impl<'r> Importer<'r> {
})
};
let mut data = model.contents().unwrap_or_default();
let page = pdf_page_in(g);
let pdf_attribute = model.pdf_attribute();
let page = pdf_attribute.as_ref().map(craft_idml::model::PdfAttribute::page_index).unwrap_or(0);
// Where the box sits on the page needs the PDF parsed: the engine finds it after import.
let pdf_crop = pdf_attribute(g).and_then(|a| a.get("PDFCrop")).map_or(designcraft_doc::PdfCrop::Crop, names::pdf_crop_in);
let pdf_crop =
pdf_attribute.as_ref().and_then(craft_idml::model::PdfAttribute::crop).map_or(designcraft_doc::PdfCrop::Crop, names::pdf_crop_in);
let mut read_from = None;
if data.is_empty()
&& let Some(p) = &uri
@@ -404,21 +394,6 @@ fn path_from_model(geometry: craft_idml::model::PathGeometry) -> PathData {
PathData::new(subpaths)
}
/// A placed PDF's `PDFAttribute` (Image Import Options), as a child or a property.
fn pdf_attribute(g: &El) -> Option<&El> {
g.find("PDFAttribute").or_else(|| g.prop_el("PDFAttribute"))
}
/// The page a placed PDF shows (0-based), from `PDFAttribute/@PageNumber` (1-based). A missing or
/// malformed value means page 1; a page the PDF lacks draws nothing and is left out of PDF export.
fn pdf_page_in(g: &El) -> u32 {
pdf_attribute(g)
.and_then(|a| a.get("PageNumber"))
.and_then(|v| v.trim().parse::<u64>().ok())
.and_then(|n| u32::try_from(n.saturating_sub(1)).ok())
.unwrap_or(0)
}
/// Anchors a custom text wrap boundary may have.
const MAX_WRAP_PATH_ANCHORS: usize = 4096;