From e234a310d96dbb790aa75330cd9faf558d69e73b Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Fri, 13 Nov 2026 06:49:41 -0500 Subject: [PATCH] Parse IDML transparency settings into a neutral model --- crates/idml/src/model/effects.rs | 124 +++++++++++++++++++++++++++++++ crates/idml/src/model/mod.rs | 2 + 2 files changed, 126 insertions(+) create mode 100644 crates/idml/src/model/effects.rs diff --git a/crates/idml/src/model/effects.rs b/crates/idml/src/model/effects.rs new file mode 100644 index 000000000..f2aec7442 --- /dev/null +++ b/crates/idml/src/model/effects.rs @@ -0,0 +1,124 @@ +//! The IDML transparency/effect settings found on page items and object styles. + +use craft_xml::{El, Local}; + +use super::Values; + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct DropShadowValues { + pub mode: Option, + pub x_offset: Option, + pub y_offset: Option, + pub color: Option, + pub opacity: Option, + pub size: Option, + pub spread: Option, + pub blend_mode: Option, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct FeatherValues { + pub mode: Option, + pub width: Option, + pub choke_amount: Option, + pub corner_type: Option, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct OpacityStopValues { + pub location: Option, + pub opacity: Option, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct GradientFeatherValues { + pub applied: bool, + pub radial: bool, + pub angle: Option, + pub length: Option, + pub start: Option<[f64; 2]>, + pub stops: Vec, +} + +#[derive(Clone, Debug, Default, PartialEq)] +pub struct DirectionalFeatherValues { + pub applied: bool, + pub widths: [Option; 4], +} + +/// A typed view of an IDML `TransparencySetting` and its effect elements. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct TransparencySetting { + pub blend_mode: Option, + pub opacity: Option, + pub drop_shadow: Option, + pub feather: Option, + pub gradient_feather: Option, + pub directional_feather: Option, +} + +impl TransparencySetting { + pub fn from_el(el: &El) -> Self { + let child = |name: &str| el.child(Local(name)); + let blending = child("BlendingSetting").map(Values::of); + let drop_shadow = child("DropShadowSetting").map(|el| { + let values = Values::of(el); + DropShadowValues { + mode: values.get("Mode").map(str::to_owned), + x_offset: values.num("XOffset"), + y_offset: values.num("YOffset"), + color: values.get("EffectColor").map(str::to_owned), + opacity: values.num("Opacity"), + size: values.num("Size"), + spread: values.num("Spread"), + blend_mode: values.get("BlendMode").map(str::to_owned), + } + }); + let feather = child("FeatherSetting").map(|el| { + let values = Values::of(el); + FeatherValues { + mode: values.get("Mode").map(str::to_owned), + width: values.num("Width"), + choke_amount: values.num("ChokeAmount"), + corner_type: values.get("CornerType").map(str::to_owned), + } + }); + let gradient_feather = child("GradientFeatherSetting").map(|el| { + let values = Values::of(el); + let start = values + .prop("GradientStart") + .and_then(|value| value.split_whitespace().map(|part| part.parse::().ok().filter(|n| n.is_finite())).collect::>>()) + .and_then(|parts| <[f64; 2]>::try_from(parts).ok()); + GradientFeatherValues { + applied: values.boolean("Applied") == Some(true), + radial: values.get("Type") == Some("Radial"), + angle: values.num("Angle"), + length: values.num("Length"), + start, + stops: el + .elements() + .filter(|stop| stop.local() == "OpacityGradientStop") + .map(|stop| { + let values = Values::of(stop); + OpacityStopValues { location: values.num("Location"), opacity: values.num("Opacity") } + }) + .collect(), + } + }); + let directional_feather = child("DirectionalFeatherSetting").map(|el| { + let values = Values::of(el); + DirectionalFeatherValues { + applied: values.boolean("Applied") == Some(true), + widths: [values.num("TopWidth"), values.num("LeftWidth"), values.num("BottomWidth"), values.num("RightWidth")], + } + }); + Self { + blend_mode: blending.as_ref().and_then(|values| values.get("BlendMode").map(str::to_owned)), + opacity: blending.as_ref().and_then(|values| values.num("Opacity")), + drop_shadow, + feather, + gradient_feather, + directional_feather, + } + } +} diff --git a/crates/idml/src/model/mod.rs b/crates/idml/src/model/mod.rs index 87b1e6275..4f6315bd6 100644 --- a/crates/idml/src/model/mod.rs +++ b/crates/idml/src/model/mod.rs @@ -13,6 +13,7 @@ //! [`spreads`]) and written back with `to_el`, then [`crate::part_xml`]. mod document; +mod effects; mod geometry; mod resources; mod spread; @@ -20,6 +21,7 @@ mod story; use craft_xml::{El, Local, Node}; pub use document::{ModelDocument, TopLevelKind, TopLevelNode}; +pub use effects::{DirectionalFeatherValues, DropShadowValues, FeatherValues, GradientFeatherValues, OpacityStopValues, TransparencySetting}; pub use geometry::{MAX_PATH_POINTS, PathGeometry, PathGeometryError, PathPoint, SubPath}; pub use resources::{ ColorDefinition, ColorGroup, ColorGroupDefinition, ColorGroupMember, ColorSpace, CompositeFontDefinition, CompositeFontEntryDefinition,