Skip to main content

rdocx_layout/
input.rs

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