1use crate::text::{Font, measure_text, split_into_words};
2use crate::page::Margins;
3use crate::error::Result;
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 page_height: f64,
24 margins: Margins,
25}
26
27impl TextFlowContext {
28 pub fn new(page_width: f64, page_height: f64, margins: Margins) -> Self {
29 Self {
30 operations: String::new(),
31 current_font: Font::Helvetica,
32 font_size: 12.0,
33 line_height: 1.2,
34 cursor_x: margins.left,
35 cursor_y: page_height - margins.top,
36 alignment: TextAlign::Left,
37 page_width,
38 page_height,
39 margins,
40 }
41 }
42
43 pub fn set_font(&mut self, font: Font, size: f64) -> &mut Self {
44 self.current_font = font;
45 self.font_size = size;
46 self
47 }
48
49 pub fn set_line_height(&mut self, multiplier: f64) -> &mut Self {
50 self.line_height = multiplier;
51 self
52 }
53
54 pub fn set_alignment(&mut self, alignment: TextAlign) -> &mut Self {
55 self.alignment = alignment;
56 self
57 }
58
59 pub fn at(&mut self, x: f64, y: f64) -> &mut Self {
60 self.cursor_x = x;
61 self.cursor_y = y;
62 self
63 }
64
65 pub fn content_width(&self) -> f64 {
66 self.page_width - self.margins.left - self.margins.right
67 }
68
69 pub fn write_wrapped(&mut self, text: &str) -> Result<&mut Self> {
70 let content_width = self.content_width();
71
72 let words = split_into_words(text);
74 let mut lines: Vec<Vec<&str>> = Vec::new();
75 let mut current_line: Vec<&str> = Vec::new();
76 let mut current_width = 0.0;
77
78 for word in words {
80 let word_width = measure_text(word, self.current_font, self.font_size);
81
82 if !current_line.is_empty() && current_width + word_width > content_width {
84 lines.push(current_line);
85 current_line = vec![word];
86 current_width = word_width;
87 } else {
88 current_line.push(word);
89 current_width += word_width;
90 }
91 }
92
93 if !current_line.is_empty() {
94 lines.push(current_line);
95 }
96
97 for (i, line) in lines.iter().enumerate() {
99 let line_text = line.join("");
100 let line_width = measure_text(&line_text, self.current_font, self.font_size);
101
102 let x = match self.alignment {
104 TextAlign::Left => self.margins.left,
105 TextAlign::Right => self.page_width - self.margins.right - line_width,
106 TextAlign::Center => self.margins.left + (content_width - line_width) / 2.0,
107 TextAlign::Justified => {
108 if i < lines.len() - 1 && line.len() > 1 {
109 self.margins.left
111 } else {
112 self.margins.left
113 }
114 }
115 };
116
117 self.operations.push_str("BT\n");
119
120 write!(&mut self.operations, "/{} {} Tf\n",
122 self.current_font.pdf_name(), self.font_size).unwrap();
123
124 write!(&mut self.operations, "{:.2} {:.2} Td\n",
126 x, self.cursor_y).unwrap();
127
128 if self.alignment == TextAlign::Justified && i < lines.len() - 1 && line.len() > 1 {
130 let spaces_count = line.iter().filter(|w| w.trim().is_empty()).count();
132 if spaces_count > 0 {
133 let extra_space = content_width - line_width;
134 let space_adjustment = extra_space / spaces_count as f64;
135
136 write!(&mut self.operations, "{:.2} Tw\n", space_adjustment).unwrap();
138 }
139 }
140
141 self.operations.push('(');
143 for ch in line_text.chars() {
144 match ch {
145 '(' => self.operations.push_str("\\("),
146 ')' => self.operations.push_str("\\)"),
147 '\\' => self.operations.push_str("\\\\"),
148 '\n' => self.operations.push_str("\\n"),
149 '\r' => self.operations.push_str("\\r"),
150 '\t' => self.operations.push_str("\\t"),
151 _ => self.operations.push(ch),
152 }
153 }
154 self.operations.push_str(") Tj\n");
155
156 if self.alignment == TextAlign::Justified && i < lines.len() - 1 {
158 self.operations.push_str("0 Tw\n");
159 }
160
161 self.operations.push_str("ET\n");
163
164 self.cursor_y -= self.font_size * self.line_height;
166 }
167
168 Ok(self)
169 }
170
171 pub fn write_paragraph(&mut self, text: &str) -> Result<&mut Self> {
172 self.write_wrapped(text)?;
173 self.cursor_y -= self.font_size * self.line_height * 0.5;
175 Ok(self)
176 }
177
178 pub fn newline(&mut self) -> &mut Self {
179 self.cursor_y -= self.font_size * self.line_height;
180 self.cursor_x = self.margins.left;
181 self
182 }
183
184 pub fn cursor_position(&self) -> (f64, f64) {
185 (self.cursor_x, self.cursor_y)
186 }
187
188 pub fn generate_operations(&self) -> Vec<u8> {
189 self.operations.as_bytes().to_vec()
190 }
191}