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
197 pub fn alignment(&self) -> TextAlign {
199 self.alignment
200 }
201
202 pub fn page_dimensions(&self) -> (f64, f64) {
204 (self.page_width, self.page_height)
205 }
206
207 pub fn margins(&self) -> &Margins {
209 &self.margins
210 }
211
212 pub fn line_height(&self) -> f64 {
214 self.line_height
215 }
216
217 pub fn operations(&self) -> &str {
219 &self.operations
220 }
221
222 pub fn clear(&mut self) {
224 self.operations.clear();
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231 use crate::page::Margins;
232
233 fn create_test_margins() -> Margins {
234 Margins {
235 left: 50.0,
236 right: 50.0,
237 top: 50.0,
238 bottom: 50.0,
239 }
240 }
241
242 #[test]
243 fn test_text_flow_context_new() {
244 let margins = create_test_margins();
245 let context = TextFlowContext::new(400.0, 600.0, margins.clone());
246
247 assert_eq!(context.current_font, Font::Helvetica);
248 assert_eq!(context.font_size, 12.0);
249 assert_eq!(context.line_height, 1.2);
250 assert_eq!(context.alignment, TextAlign::Left);
251 assert_eq!(context.page_width, 400.0);
252 assert_eq!(context.page_height, 600.0);
253 assert_eq!(context.cursor_x, 50.0); assert_eq!(context.cursor_y, 550.0); }
256
257 #[test]
258 fn test_set_font() {
259 let margins = create_test_margins();
260 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
261
262 context.set_font(Font::TimesBold, 16.0);
263 assert_eq!(context.current_font, Font::TimesBold);
264 assert_eq!(context.font_size, 16.0);
265 }
266
267 #[test]
268 fn test_set_line_height() {
269 let margins = create_test_margins();
270 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
271
272 context.set_line_height(1.5);
273 assert_eq!(context.line_height(), 1.5);
274 }
275
276 #[test]
277 fn test_set_alignment() {
278 let margins = create_test_margins();
279 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
280
281 context.set_alignment(TextAlign::Center);
282 assert_eq!(context.alignment(), TextAlign::Center);
283 }
284
285 #[test]
286 fn test_at_position() {
287 let margins = create_test_margins();
288 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
289
290 context.at(100.0, 200.0);
291 let (x, y) = context.cursor_position();
292 assert_eq!(x, 100.0);
293 assert_eq!(y, 200.0);
294 }
295
296 #[test]
297 fn test_content_width() {
298 let margins = create_test_margins();
299 let context = TextFlowContext::new(400.0, 600.0, margins.clone());
300
301 let content_width = context.content_width();
302 assert_eq!(content_width, 300.0); }
304
305 #[test]
306 fn test_text_align_variants() {
307 assert_eq!(TextAlign::Left, TextAlign::Left);
308 assert_eq!(TextAlign::Right, TextAlign::Right);
309 assert_eq!(TextAlign::Center, TextAlign::Center);
310 assert_eq!(TextAlign::Justified, TextAlign::Justified);
311
312 assert_ne!(TextAlign::Left, TextAlign::Right);
313 }
314
315 #[test]
316 fn test_write_wrapped_simple() {
317 let margins = create_test_margins();
318 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
319
320 context.write_wrapped("Hello World").unwrap();
321
322 let ops = context.operations();
323 assert!(ops.contains("BT\n"));
324 assert!(ops.contains("ET\n"));
325 assert!(ops.contains("/Helvetica 12 Tf"));
326 assert!(ops.contains("(Hello World) Tj"));
327 }
328
329 #[test]
330 fn test_write_paragraph() {
331 let margins = create_test_margins();
332 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
333
334 let initial_y = context.cursor_y;
335 context.write_paragraph("Test paragraph").unwrap();
336
337 assert!(context.cursor_y < initial_y);
339 }
340
341 #[test]
342 fn test_newline() {
343 let margins = create_test_margins();
344 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
345
346 let initial_y = context.cursor_y;
347 context.newline();
348
349 assert_eq!(context.cursor_x, margins.left);
350 assert!(context.cursor_y < initial_y);
351 assert_eq!(context.cursor_y, initial_y - context.font_size * context.line_height);
352 }
353
354 #[test]
355 fn test_cursor_position() {
356 let margins = create_test_margins();
357 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
358
359 context.at(75.0, 125.0);
360 let (x, y) = context.cursor_position();
361 assert_eq!(x, 75.0);
362 assert_eq!(y, 125.0);
363 }
364
365 #[test]
366 fn test_generate_operations() {
367 let margins = create_test_margins();
368 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
369
370 context.write_wrapped("Test").unwrap();
371 let ops_bytes = context.generate_operations();
372 let ops_string = String::from_utf8(ops_bytes).unwrap();
373
374 assert_eq!(ops_string, context.operations());
375 }
376
377 #[test]
378 fn test_clear_operations() {
379 let margins = create_test_margins();
380 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
381
382 context.write_wrapped("Test").unwrap();
383 assert!(!context.operations().is_empty());
384
385 context.clear();
386 assert!(context.operations().is_empty());
387 }
388
389 #[test]
390 fn test_page_dimensions() {
391 let margins = create_test_margins();
392 let context = TextFlowContext::new(400.0, 600.0, margins.clone());
393
394 let (width, height) = context.page_dimensions();
395 assert_eq!(width, 400.0);
396 assert_eq!(height, 600.0);
397 }
398
399 #[test]
400 fn test_margins_access() {
401 let margins = create_test_margins();
402 let context = TextFlowContext::new(400.0, 600.0, margins.clone());
403
404 let ctx_margins = context.margins();
405 assert_eq!(ctx_margins.left, 50.0);
406 assert_eq!(ctx_margins.right, 50.0);
407 assert_eq!(ctx_margins.top, 50.0);
408 assert_eq!(ctx_margins.bottom, 50.0);
409 }
410
411 #[test]
412 fn test_method_chaining() {
413 let margins = create_test_margins();
414 let mut context = TextFlowContext::new(400.0, 600.0, margins.clone());
415
416 context
417 .set_font(Font::Courier, 10.0)
418 .set_line_height(1.5)
419 .set_alignment(TextAlign::Center)
420 .at(100.0, 200.0);
421
422 assert_eq!(context.current_font, Font::Courier);
423 assert_eq!(context.font_size, 10.0);
424 assert_eq!(context.line_height(), 1.5);
425 assert_eq!(context.alignment(), TextAlign::Center);
426 let (x, y) = context.cursor_position();
427 assert_eq!(x, 100.0);
428 assert_eq!(y, 200.0);
429 }
430
431 #[test]
432 fn test_text_align_debug() {
433 let align = TextAlign::Center;
434 let debug_str = format!("{:?}", align);
435 assert_eq!(debug_str, "Center");
436 }
437
438 #[test]
439 fn test_text_align_clone() {
440 let align1 = TextAlign::Justified;
441 let align2 = align1;
442 assert_eq!(align1, align2);
443 }
444
445 #[test]
446 fn test_text_align_copy() {
447 let align1 = TextAlign::Right;
448 let align2 = align1; assert_eq!(align1, align2);
450
451 assert_eq!(align1, TextAlign::Right);
453 assert_eq!(align2, TextAlign::Right);
454 }
455}