easypdf_core/layout/
flow_layout.rs1use std::path::Path;
4
5use crate::{Orientation, PageSize, PdfFont, PdfText, Result};
6
7use crate::{Direction, LayoutSink};
8
9pub struct FlowLayout<S> {
11 direction: Direction,
12 margins: f64,
13 spacing: f64,
14 cursor: f64,
15 page_width: f64,
16 page_height: f64,
17 sink: S,
18}
19
20impl<S: LayoutSink> FlowLayout<S> {
21 #[must_use]
23 pub fn vertical(sink: S, page_size: PageSize) -> Self {
24 let (width, height) = page_size.dimensions();
25 Self {
26 direction: Direction::Vertical,
27 margins: 72.0,
28 spacing: 12.0,
29 cursor: height - 72.0,
30 page_width: width,
31 page_height: height,
32 sink,
33 }
34 }
35
36 #[must_use]
38 pub fn margins(mut self, margins: f64) -> Self {
39 self.margins = margins;
40 self.cursor = self.page_height - margins;
41 self
42 }
43
44 #[must_use]
46 pub fn spacing(mut self, spacing: f64) -> Self {
47 self.spacing = spacing;
48 self
49 }
50
51 #[must_use]
53 pub const fn direction(&self) -> Direction {
54 self.direction
55 }
56
57 pub fn add_text(&mut self, content: &str, font: &PdfFont, estimated_height: f64) -> Result<()> {
63 let y = self.cursor - estimated_height;
64 self.sink
65 .write_text(&PdfText::new(content).font(font.clone()), self.margins, y)?;
66 self.cursor = y - self.spacing;
67 Ok(())
68 }
69
70 #[must_use]
72 pub fn remaining_space(&self) -> f64 {
73 self.cursor - self.margins
74 }
75
76 pub fn new_page(&mut self) -> Result<()> {
82 self.sink.add_page(
83 PageSize::Custom(self.page_width, self.page_height),
84 Orientation::Portrait,
85 )?;
86 self.cursor = self.page_height - self.margins;
87 Ok(())
88 }
89
90 pub fn finish(self, path: impl AsRef<Path>) -> Result<()> {
96 self.sink.finish(path.as_ref())
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use std::path::Path;
103
104 use crate::{Orientation, PageSize, PdfFont, PdfText, Result};
105
106 use super::FlowLayout;
107 use crate::LayoutSink;
108
109 #[derive(Default)]
110 struct RecordingSink {
111 pages: usize,
112 texts: Vec<String>,
113 }
114
115 impl LayoutSink for RecordingSink {
116 fn add_page(&mut self, _size: PageSize, _orientation: Orientation) -> Result<usize> {
117 self.pages += 1;
118 Ok(self.pages)
119 }
120
121 fn write_text(&mut self, text: &PdfText, _x: f64, _y: f64) -> Result<()> {
122 self.texts.push(text.content.clone());
123 Ok(())
124 }
125
126 fn finish(self, _path: &Path) -> Result<()> {
127 Ok(())
128 }
129 }
130
131 #[test]
132 fn lays_out_text_and_new_pages_without_writer_dependency() {
133 let mut layout = FlowLayout::vertical(RecordingSink::default(), PageSize::A4)
134 .margins(50.0)
135 .spacing(10.0);
136 layout
137 .add_text("Hello", &PdfFont::helvetica(12.0), 20.0)
138 .expect("add text");
139 assert!(layout.remaining_space() > 0.0);
140 layout.new_page().expect("new page");
141 layout.finish("unused.pdf").expect("finish");
142 }
143}