1use crate::error::Result;
2use crate::page::Margins;
3use crate::text::{measure_text, split_into_words, Font};
4use std::fmt::Write;
5
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum TextAlign {
8 Left,
9 Right,
10 Center,
11 Justified,
12}
13
14pub struct TextFlowContext {
15 operations: String,
16 current_font: Font,
17 font_size: f64,
18 line_height: f64,
19 cursor_x: f64,
20 cursor_y: f64,
21 alignment: TextAlign,
22 page_width: f64,
23 #[allow(dead_code)]
24 page_height: f64,
25 margins: Margins,
26}
27
28impl TextFlowContext {
29 pub fn new(page_width: f64, page_height: f64, margins: Margins) -> Self {
30 Self {
31 operations: String::new(),
32 current_font: Font::Helvetica,
33 font_size: 12.0,
34 line_height: 1.2,
35 cursor_x: margins.left,
36 cursor_y: page_height - margins.top,
37 alignment: TextAlign::Left,
38 page_width,
39 page_height,
40 margins,
41 }
42 }
43
44 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
45 self.current_font = font;
46 self.font_size = size;
47 self
48 }
49
50 pub fn set_line_height(&mut self, multiplier: f64) -> &mut Self {
51 self.line_height = multiplier;
52 self
53 }
54
55 pub fn set_alignment(&mut self, alignment: TextAlign) -> &mut Self {
56 self.alignment = alignment;
57 self
58 }
59
60 pub fn at(&mut self, x: f64, y: f64) -> &mut Self {
61 self.cursor_x = x;
62 self.cursor_y = y;
63 self
64 }
65
66 pub fn content_width(&self) -> f64 {
67 self.page_width - self.margins.left - self.margins.right
68 }
69
70 pub fn write_wrapped(&mut self, text: &str) -> Result<&mut Self> {
71 let content_width = self.content_width();
72
73 let words = split_into_words(text);
75 let mut lines: Vec<Vec<&str>> = Vec::new();
76 let mut current_line: Vec<&str> = Vec::new();
77 let mut current_width = 0.0;
78
79 for word in words {
81 let word_width = measure_text(word, self.current_font, self.font_size);
82
83 if !current_line.is_empty() && current_width + word_width > content_width {
85 lines.push(current_line);
86 current_line = vec![word];
87 current_width = word_width;
88 } else {
89 current_line.push(word);
90 current_width += word_width;
91 }
92 }
93
94 if !current_line.is_empty() {
95 lines.push(current_line);
96 }
97
98 for (i, line) in lines.iter().enumerate() {
100 let line_text = line.join("");
101 let line_width = measure_text(&line_text, self.current_font, self.font_size);
102
103 let x = match self.alignment {
105 TextAlign::Left => self.margins.left,
106 TextAlign::Right => self.page_width - self.margins.right - line_width,
107 TextAlign::Center => self.margins.left + (content_width - line_width) / 2.0,
108 TextAlign::Justified => {
109 if i < lines.len() - 1 && line.len() > 1 {
110 self.margins.left
112 } else {
113 self.margins.left
114 }
115 }
116 };
117
118 self.operations.push_str("BT\n");
120
121 writeln!(
123 &mut self.operations,
124 "/{} {} Tf",
125 self.current_font.pdf_name(),
126 self.font_size
127 )
128 .unwrap();
129
130 writeln!(&mut self.operations, "{:.2} {:.2} Td", x, self.cursor_y).unwrap();
132
133 if self.alignment == TextAlign::Justified && i < lines.len() - 1 && line.len() > 1 {
135 let spaces_count = line.iter().filter(|w| w.trim().is_empty()).count();
137 if spaces_count > 0 {
138 let extra_space = content_width - line_width;
139 let space_adjustment = extra_space / spaces_count as f64;
140
141 writeln!(&mut self.operations, "{space_adjustment:.2} Tw").unwrap();
143 }
144 }
145
146 self.operations.push('(');
148 for ch in line_text.chars() {
149 match ch {
150 '(' => self.operations.push_str("\\("),
151 ')' => self.operations.push_str("\\)"),
152 '\\' => self.operations.push_str("\\\\"),
153 '\n' => self.operations.push_str("\\n"),
154 '\r' => self.operations.push_str("\\r"),
155 '\t' => self.operations.push_str("\\t"),
156 _ => self.operations.push(ch),
157 }
158 }
159 self.operations.push_str(") Tj\n");
160
161 if self.alignment == TextAlign::Justified && i < lines.len() - 1 {
163 self.operations.push_str("0 Tw\n");
164 }
165
166 self.operations.push_str("ET\n");
168
169 self.cursor_y -= self.font_size * self.line_height;
171 }
172
173 Ok(self)
174 }
175
176 pub fn write_paragraph(&mut self, text: &str) -> Result<&mut Self> {
177 self.write_wrapped(text)?;
178 self.cursor_y -= self.font_size * self.line_height * 0.5;
180 Ok(self)
181 }
182
183 pub fn newline(&mut self) -> &mut Self {
184 self.cursor_y -= self.font_size * self.line_height;
185 self.cursor_x = self.margins.left;
186 self
187 }
188
189 pub fn cursor_position(&self) -> (f64, f64) {
190 (self.cursor_x, self.cursor_y)
191 }
192
193 pub fn generate_operations(&self) -> Vec<u8> {
194 self.operations.as_bytes().to_vec()
195 }
196}