Skip to main content

normordis_pdf/elements/
mod.rs

1pub mod fixed;
2pub mod fixed_image;
3pub mod fixed_line;
4pub mod fixed_text;
5pub mod footnote;
6pub mod footer;
7pub mod form;
8pub mod header;
9pub mod image;
10pub mod list;
11pub mod page_break;
12pub mod paragraph;
13pub mod section;
14pub mod section_break;
15pub mod spacer;
16pub mod table;
17pub mod toc;
18
19use std::collections::HashMap;
20
21use crate::{
22    backend::{FontRef, PdfBackend},
23    compliance::ua::{AccessibilityConfig, StructTag, StructureTree},
24    fonts::FontRegistry,
25    layout::{FixedBox, GlyphUsageTracker, PageFlow, TextLayoutEngine},
26    page::PageLayout,
27    styles::{DocumentStyle, RgbColor},
28};
29
30/// Determines how an element interacts with the page flow.
31#[derive(Debug, Clone)]
32pub enum LayoutMode {
33    /// Element participates in vertical flow.
34    Flow,
35    /// Element is placed at absolute coordinates; does not affect the cursor.
36    Fixed(FixedBox),
37}
38
39/// Result of a single render call on an element.
40#[derive(Debug, Clone)]
41pub struct RenderResult {
42    pub has_more: bool,
43}
44
45impl RenderResult {
46    pub fn done() -> Self { Self { has_more: false } }
47    pub fn more() -> Self { Self { has_more: true } }
48}
49
50/// Context passed to every element during rendering.
51///
52/// Elements call `ctx.backend.draw_*()` to emit drawing operations and advance
53/// `ctx.flow.cursor_y_mm` via `ctx.flow.advance()`.
54pub struct RenderContext {
55    /// PDF backend — all drawing calls go through this.
56    pub backend: Box<dyn PdfBackend>,
57    /// Font map: `"family::variant"` → `FontRef`.
58    pub font_map: HashMap<String, FontRef>,
59    /// Vertical cursor and page-break state.
60    pub flow: PageFlow,
61    /// Calculated page layout for the current document style.
62    pub layout: PageLayout,
63    /// Text measurement and line-wrapping engine.
64    pub layout_engine: TextLayoutEngine,
65    /// Document-wide style settings.
66    pub style: DocumentStyle,
67    /// Loaded font registry.
68    pub fonts: FontRegistry,
69    /// Set to `true` by `PageBreakElement`.
70    pub force_page_break: bool,
71    /// Name of the default font family.
72    pub default_font_family: String,
73    /// Current page number (1-based).
74    pub page_number: u32,
75    /// Total pages (set after first-pass page count).
76    pub total_pages: u32,
77    /// Index of the first item to render on continuation pages.
78    pub resume_index: usize,
79    /// Glyph usage collector for Eixo B subsetting.
80    pub glyph_tracker: GlyphUsageTracker,
81    /// Vertical space (mm) reserved at the bottom for footnotes.
82    pub reserved_footnotes_mm: f64,
83    /// Accessibility configuration for PDF/UA-2.
84    pub ua_config: AccessibilityConfig,
85    /// Structure tree events accumulated during rendering.
86    pub ua_events: StructureTree,
87    /// Current marked content identifier (resets on each page).
88    pub mcid_counter: u32,
89    /// Last heading level rendered (for UA-2 level-skip detection).
90    pub last_heading_level: Option<u8>,
91}
92
93impl RenderContext {
94    pub fn reset_resume(&mut self) {
95        self.resume_index = 0;
96    }
97
98    /// Returns true if PDF/UA-2 accessibility mode is active.
99    pub fn ua_enabled(&self) -> bool {
100        self.ua_config.enabled
101    }
102
103    /// Allocates the next MCID for the current page and returns it.
104    pub fn next_mcid(&mut self) -> u32 {
105        let mcid = self.mcid_counter;
106        self.mcid_counter += 1;
107        mcid
108    }
109
110    /// Records a single-element structure event (BeginGroup + ContentRef + EndGroup).
111    pub fn ua_tag_element(&mut self, tag: StructTag, alt: Option<String>) -> u32 {
112        let mcid = self.next_mcid();
113        let page_idx = self.page_number as usize - 1;
114        self.ua_events.begin_group(tag, alt);
115        self.ua_events.add_content_ref(mcid, page_idx);
116        self.ua_events.end_group();
117        mcid
118    }
119
120    /// Opens a structure element group (must be closed with ua_end_group).
121    pub fn ua_begin_group(&mut self, tag: StructTag, alt: Option<String>) {
122        self.ua_events.begin_group(tag, alt);
123    }
124
125    /// Adds a content ref to the current structure element group.
126    pub fn ua_content_ref(&mut self, mcid: u32) {
127        let page_idx = self.page_number as usize - 1;
128        self.ua_events.add_content_ref(mcid, page_idx);
129    }
130
131    /// Closes the current structure element group.
132    pub fn ua_end_group(&mut self) {
133        self.ua_events.end_group();
134    }
135
136    /// Returns the `FontRef` for the default family with the given style.
137    pub fn get_font_ref(&self, bold: bool, italic: bool) -> Option<FontRef> {
138        self.get_font_ref_for(&self.default_font_family.clone(), bold, italic)
139    }
140
141    /// Returns the `FontRef` for a specific family with the given style.
142    pub fn get_font_ref_for(&self, family: &str, bold: bool, italic: bool) -> Option<FontRef> {
143        if bold && italic {
144            self.font_map.get(&format!("{family}::bold_italic"))
145                .or_else(|| self.font_map.get(&format!("{family}::bold")))
146                .or_else(|| self.font_map.get(&format!("{family}::italic")))
147                .or_else(|| self.font_map.get(&format!("{family}::regular")))
148                .copied()
149        } else if bold {
150            self.font_map.get(&format!("{family}::bold"))
151                .or_else(|| self.font_map.get(&format!("{family}::regular")))
152                .copied()
153        } else if italic {
154            self.font_map.get(&format!("{family}::italic"))
155                .or_else(|| self.font_map.get(&format!("{family}::regular")))
156                .copied()
157        } else {
158            self.font_map.get(&format!("{family}::regular")).copied()
159        }
160    }
161
162    /// Convenience: draw text, forwarding to the backend.
163    pub fn draw_text(
164        &mut self,
165        text: &str,
166        x_mm: f64,
167        y_mm: f64,
168        font_size_pt: f64,
169        font_ref: FontRef,
170        color: &RgbColor,
171    ) -> crate::Result<()> {
172        self.backend.draw_text(text, x_mm, y_mm, font_size_pt, font_ref, color, 0.0)
173    }
174
175    /// Convenience: draw text with letter spacing.
176    pub fn draw_text_spaced(
177        &mut self,
178        text: &str,
179        x_mm: f64,
180        y_mm: f64,
181        font_size_pt: f64,
182        font_ref: FontRef,
183        color: &RgbColor,
184        letter_spacing_pt: f32,
185    ) -> crate::Result<()> {
186        self.backend.draw_text(text, x_mm, y_mm, font_size_pt, font_ref, color, letter_spacing_pt)
187    }
188
189    /// Convenience: draw a horizontal line.
190    pub fn draw_hline(
191        &mut self,
192        x0_mm: f64,
193        x1_mm: f64,
194        y_mm: f64,
195        width_pt: f32,
196        color: &RgbColor,
197    ) -> crate::Result<()> {
198        self.backend.draw_line(x0_mm, y_mm, x1_mm, y_mm, width_pt, color)
199    }
200
201    /// Convenience: draw a vertical line.
202    pub fn draw_vline(
203        &mut self,
204        x_mm: f64,
205        y0_mm: f64,
206        y1_mm: f64,
207        width_pt: f32,
208        color: &RgbColor,
209    ) -> crate::Result<()> {
210        self.backend.draw_line(x_mm, y0_mm, x_mm, y1_mm, width_pt, color)
211    }
212}
213
214/// Trait implemented by every document element.
215pub trait Element {
216    fn layout_mode(&self) -> LayoutMode {
217        LayoutMode::Flow
218    }
219
220    fn estimated_height_mm(&self) -> f64 {
221        0.0
222    }
223
224    fn as_section_info(&self) -> Option<(u8, &str)> {
225        None
226    }
227
228    fn inject_toc_entries(&mut self, _entries: &[crate::elements::toc::TocEntry]) {}
229
230    fn render(&self, ctx: &mut RenderContext) -> crate::Result<RenderResult>;
231}