Skip to main content

rdocx_layout/
input.rs

1//! Input types for the layout engine.
2
3use std::collections::HashMap;
4
5use oxml_chart::CT_ChartSpace;
6use oxml_drawing::color::ColorMap;
7use oxml_drawing::theme::CT_OfficeStyleSheet;
8pub use oxml_layout::FontFile;
9use oxml_layout::MediaId;
10use rdocx_oxml::core_properties::CoreProperties;
11use rdocx_oxml::document::CT_Document;
12use rdocx_oxml::footnotes::CT_Footnotes;
13use rdocx_oxml::header_footer::CT_HdrFtr;
14use rdocx_oxml::numbering::CT_Numbering;
15use rdocx_oxml::styles::CT_Styles;
16use rdocx_oxml::theme::Theme;
17
18/// The tracked-revision projection used for Word layout.
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
20pub enum RevisionView {
21    /// Render the document as though all modeled revisions were accepted.
22    #[default]
23    Accepted,
24    /// Render both sides of modeled revisions with tracked decorations.
25    Tracked,
26}
27
28/// Image data keyed by relationship/embed ID.
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct ImageData {
31    /// Raw image bytes (PNG, JPEG, etc.).
32    pub data: Vec<u8>,
33    /// MIME content type (e.g., "image/png").
34    pub content_type: String,
35}
36
37/// Collision-safe media lookup shared by layout and pagination.
38#[derive(Debug, Clone)]
39pub struct MediaRegistry {
40    relationship_ids: HashMap<String, MediaId>,
41    media: HashMap<MediaId, ImageData>,
42    missing_id: MediaId,
43}
44
45impl MediaRegistry {
46    /// Resolve relationship IDs and image bytes once for a layout operation.
47    pub fn new(images: &HashMap<String, ImageData>) -> Self {
48        Self::with_hasher(images, MediaId::from_bytes)
49    }
50
51    /// Resolve the renderer-local ID for one package relationship.
52    pub fn id_for_relationship(&self, relationship_id: &str) -> MediaId {
53        self.relationship_ids
54            .get(relationship_id)
55            .copied()
56            .unwrap_or(self.missing_id)
57    }
58
59    /// Return the image bytes and content types keyed by resolved media ID.
60    pub fn media(&self) -> &HashMap<MediaId, ImageData> {
61        &self.media
62    }
63
64    pub(crate) fn with_hasher<F>(images: &HashMap<String, ImageData>, media_id_for_bytes: F) -> Self
65    where
66        F: Fn(&[u8]) -> MediaId,
67    {
68        let missing_id = media_id_for_bytes(&[]);
69        let mut media = HashMap::from([(
70            missing_id,
71            ImageData {
72                data: Vec::new(),
73                content_type: String::new(),
74            },
75        )]);
76        let mut relationship_ids = HashMap::new();
77        let mut images = images.iter().collect::<Vec<_>>();
78        images.sort_unstable_by(|(left_id, left), (right_id, right)| {
79            left.data
80                .cmp(&right.data)
81                .then_with(|| left.content_type.cmp(&right.content_type))
82                .then_with(|| left_id.cmp(right_id))
83        });
84
85        for (relationship_id, image) in images {
86            let mut media_id = media_id_for_bytes(&image.data);
87            loop {
88                match media.get(&media_id) {
89                    Some(existing) if existing.data == image.data => break,
90                    Some(_) => media_id.0 = media_id.0.wrapping_add(1),
91                    None => {
92                        media.insert(media_id, image.clone());
93                        break;
94                    }
95                }
96            }
97            relationship_ids.insert(relationship_id.clone(), media_id);
98        }
99
100        Self {
101            relationship_ids,
102            media,
103            missing_id,
104        }
105    }
106}
107
108/// All inputs needed to lay out a DOCX document.
109#[derive(Debug, Clone)]
110pub struct LayoutInput {
111    /// The parsed document content.
112    pub document: CT_Document,
113    /// Whether document settings enable automatic hyphenation.
114    pub automatic_hyphenation: bool,
115    /// The tracked-revision projection to lay out.
116    pub revision_view: RevisionView,
117    /// Style definitions.
118    pub styles: CT_Styles,
119    /// Numbering definitions (optional).
120    pub numbering: Option<CT_Numbering>,
121    /// Header parts keyed by relationship ID.
122    pub headers: HashMap<String, CT_HdrFtr>,
123    /// Footer parts keyed by relationship ID.
124    pub footers: HashMap<String, CT_HdrFtr>,
125    /// Images keyed by embed ID.
126    pub images: HashMap<String, ImageData>,
127    /// Parsed chart parts, or contextual relationship failures, keyed by ID.
128    pub charts: HashMap<String, std::result::Result<Box<CT_ChartSpace>, String>>,
129    /// DrawingML theme used by the shared chart renderer.
130    pub chart_theme: CT_OfficeStyleSheet,
131    /// Standard Word chart colour mapping.
132    pub chart_color_map: ColorMap,
133    /// Document core properties (metadata).
134    pub core_properties: Option<CoreProperties>,
135    /// Hyperlink URLs keyed by relationship ID.
136    pub hyperlink_urls: HashMap<String, String>,
137    /// Footnote definitions.
138    pub footnotes: Option<CT_Footnotes>,
139    /// Endnote definitions.
140    pub endnotes: Option<CT_Footnotes>,
141    /// Document theme (colors + fonts).
142    pub theme: Option<Theme>,
143    /// User-provided or DOCX-embedded font files.
144    /// These are loaded before system fonts, so they take priority.
145    pub fonts: Vec<FontFile>,
146}