1use crate::error::Result;
2use crate::graphics::Image;
3use crate::layout::image_utils::fit_image_dimensions;
4use crate::layout::RichText;
5use crate::page::Margins;
6use crate::page_tables::PageTables;
7use crate::text::text_block::measure_text_block;
8use crate::text::{Font, Table, TextAlign, TextFlowContext};
9use crate::{Document, Page};
10use std::sync::Arc;
11
12#[derive(Debug, Clone)]
14pub struct PageConfig {
15 pub width: f64,
16 pub height: f64,
17 pub margin_left: f64,
18 pub margin_right: f64,
19 pub margin_top: f64,
20 pub margin_bottom: f64,
21}
22
23impl PageConfig {
24 pub fn new(
26 width: f64,
27 height: f64,
28 margin_left: f64,
29 margin_right: f64,
30 margin_top: f64,
31 margin_bottom: f64,
32 ) -> Self {
33 let config = Self {
34 width,
35 height,
36 margin_left,
37 margin_right,
38 margin_top,
39 margin_bottom,
40 };
41 debug_assert!(
42 config.content_width() > 0.0,
43 "margins ({} + {}) exceed page width ({})",
44 margin_left,
45 margin_right,
46 width
47 );
48 debug_assert!(
49 config.usable_height() > 0.0,
50 "margins ({} + {}) exceed page height ({})",
51 margin_top,
52 margin_bottom,
53 height
54 );
55 config
56 }
57
58 pub fn a4() -> Self {
60 Self::new(595.0, 842.0, 72.0, 72.0, 72.0, 72.0)
61 }
62
63 pub fn a4_with_margins(left: f64, right: f64, top: f64, bottom: f64) -> Self {
65 Self::new(595.0, 842.0, left, right, top, bottom)
66 }
67
68 pub fn content_width(&self) -> f64 {
70 self.width - self.margin_left - self.margin_right
71 }
72
73 pub fn usable_height(&self) -> f64 {
75 self.height - self.margin_top - self.margin_bottom
76 }
77
78 fn start_y(&self) -> f64 {
79 self.height - self.margin_top
80 }
81
82 fn create_page(&self) -> Page {
83 let mut page = Page::new(self.width, self.height);
84 page.set_margins(
85 self.margin_left,
86 self.margin_right,
87 self.margin_top,
88 self.margin_bottom,
89 );
90 page
91 }
92
93 fn to_margins(&self) -> Margins {
94 Margins {
95 left: self.margin_left,
96 right: self.margin_right,
97 top: self.margin_top,
98 bottom: self.margin_bottom,
99 }
100 }
101}
102
103#[derive(Debug)]
105pub enum FlowElement {
106 Text {
108 text: String,
109 font: Font,
110 font_size: f64,
111 line_height: f64,
112 },
113 Spacer(f64),
115 Table(Table),
117 RichText { rich: RichText, line_height: f64 },
119 Image {
122 name: String,
123 image: Arc<Image>,
124 max_width: f64,
125 max_height: f64,
126 center: bool,
127 },
128}
129
130impl FlowElement {
131 fn measure_height(&self, content_width: f64) -> f64 {
133 match self {
134 FlowElement::Text {
135 text,
136 font,
137 font_size,
138 line_height,
139 } => {
140 let metrics =
141 measure_text_block(text, font, *font_size, *line_height, content_width);
142 metrics.height
143 }
144 FlowElement::Spacer(h) => *h,
145 FlowElement::Table(table) => table.get_height(),
146 FlowElement::RichText { rich, line_height } => rich.max_font_size() * line_height,
147 FlowElement::Image {
148 image,
149 max_width,
150 max_height,
151 ..
152 } => {
153 let (_, h) =
154 fit_image_dimensions(image.width(), image.height(), *max_width, *max_height);
155 h
156 }
157 }
158 }
159}
160
161pub struct FlowLayout {
183 config: PageConfig,
184 elements: Vec<FlowElement>,
185}
186
187impl FlowLayout {
188 pub fn new(config: PageConfig) -> Self {
190 Self {
191 config,
192 elements: Vec::new(),
193 }
194 }
195
196 pub fn add_text(&mut self, text: &str, font: Font, font_size: f64) -> &mut Self {
198 self.elements.push(FlowElement::Text {
199 text: text.to_string(),
200 font,
201 font_size,
202 line_height: 1.2,
203 });
204 self
205 }
206
207 pub fn add_text_with_line_height(
209 &mut self,
210 text: &str,
211 font: Font,
212 font_size: f64,
213 line_height: f64,
214 ) -> &mut Self {
215 self.elements.push(FlowElement::Text {
216 text: text.to_string(),
217 font,
218 font_size,
219 line_height,
220 });
221 self
222 }
223
224 pub fn add_spacer(&mut self, points: f64) -> &mut Self {
226 self.elements.push(FlowElement::Spacer(points));
227 self
228 }
229
230 pub fn add_table(&mut self, table: Table) -> &mut Self {
232 self.elements.push(FlowElement::Table(table));
233 self
234 }
235
236 pub fn add_image(
239 &mut self,
240 name: &str,
241 image: Arc<Image>,
242 max_width: f64,
243 max_height: f64,
244 ) -> &mut Self {
245 self.elements.push(FlowElement::Image {
246 name: name.to_string(),
247 image,
248 max_width,
249 max_height,
250 center: false,
251 });
252 self
253 }
254
255 pub fn add_image_centered(
258 &mut self,
259 name: &str,
260 image: Arc<Image>,
261 max_width: f64,
262 max_height: f64,
263 ) -> &mut Self {
264 self.elements.push(FlowElement::Image {
265 name: name.to_string(),
266 image,
267 max_width,
268 max_height,
269 center: true,
270 });
271 self
272 }
273
274 pub fn add_rich_text(&mut self, rich: RichText) -> &mut Self {
276 self.elements.push(FlowElement::RichText {
277 rich,
278 line_height: 1.2,
279 });
280 self
281 }
282
283 pub fn build_into(&self, doc: &mut Document) -> Result<()> {
289 let content_width = self.config.content_width();
290 let mut current_page = self.config.create_page();
291 let mut cursor_y = self.config.start_y();
292
293 for element in &self.elements {
294 let needed_height = element.measure_height(content_width);
295
296 if cursor_y - needed_height < self.config.margin_bottom
298 && cursor_y < self.config.start_y()
299 {
300 doc.add_page(current_page);
301 current_page = self.config.create_page();
302 cursor_y = self.config.start_y();
303 }
304
305 match element {
306 FlowElement::Text {
307 text,
308 font,
309 font_size,
310 line_height,
311 } => {
312 let mut text_flow = TextFlowContext::new(
313 self.config.width,
314 self.config.height,
315 self.config.to_margins(),
316 );
317 text_flow
318 .set_font(font.clone(), *font_size)
319 .set_line_height(*line_height)
320 .set_alignment(TextAlign::Left)
321 .at(self.config.margin_left, cursor_y - font_size * line_height);
322 text_flow.write_wrapped(text)?;
323 current_page.add_text_flow(&text_flow);
324 }
325 FlowElement::Spacer(_) => {
326 }
328 FlowElement::Table(table) => {
329 current_page.add_simple_table(
330 table,
331 self.config.margin_left,
332 cursor_y - needed_height,
333 )?;
334 }
335 FlowElement::RichText { rich, line_height } => {
336 let ops = rich.render_operations(
337 self.config.margin_left,
338 cursor_y - rich.max_font_size() * line_height,
339 );
340 current_page.append_raw_content(ops.as_bytes());
341 }
342 FlowElement::Image {
343 name,
344 image,
345 max_width,
346 max_height,
347 center,
348 } => {
349 let (w, h) = fit_image_dimensions(
350 image.width(),
351 image.height(),
352 *max_width,
353 *max_height,
354 );
355 let x = if *center {
356 crate::layout::image_utils::centered_image_x(
357 self.config.margin_left,
358 content_width,
359 w,
360 )
361 } else {
362 self.config.margin_left
363 };
364 current_page.add_image(name.clone(), Image::clone(image));
365 current_page.draw_image(name, x, cursor_y - h, w, h)?;
366 }
367 }
368
369 cursor_y -= needed_height;
370 }
371
372 doc.add_page(current_page);
373 Ok(())
374 }
375}