1use crate::vector::Color;
2
3#[cfg(feature = "tracing")]
4use tracing::instrument;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
8pub enum TextAlignment {
9 #[default]
11 Left,
12 Center,
14 Right,
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
20pub enum TextRenderingMode {
21 #[default]
23 Fill,
24 Stroke,
26 FillStroke,
28 Invisible,
30 FillClip,
32 StrokeClip,
34 FillStrokeClip,
36 Clip,
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
42pub enum FontWeight {
43 Thin,
45 ExtraLight,
47 Light,
49 #[default]
51 Normal,
52 Medium,
54 SemiBold,
56 Bold,
58 ExtraBold,
60 Black,
62}
63
64#[derive(Clone, Debug)]
66pub struct TextBuilder {
67 commands: Vec<String>,
68}
69
70impl TextBuilder {
71 pub fn new() -> Self {
73 Self {
74 commands: vec!["BT".to_string()],
75 }
76 }
77
78 pub fn font(mut self, name: &str, size: f64) -> Self {
80 self.commands.push(format!("/{} {} Tf", name, size));
81 self
82 }
83
84 pub fn next_line(mut self, offset_x: f64, offset_y: f64) -> Self {
86 self.commands.push(format!("{} {} Td", offset_x, offset_y));
87 self
88 }
89
90 pub fn position(mut self, x: f64, y: f64) -> Self {
92 self.commands.push(format!("{} {} Td", x, y));
93 self
94 }
95
96 pub fn text_matrix(mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
98 self.commands
99 .push(format!("{} {} {} {} {} {} Tm", a, b, c, d, e, f));
100 self
101 }
102
103 pub fn text(mut self, text: &str) -> Self {
105 let escaped = text
106 .chars()
107 .map(|c| match c {
108 '(' => "\\(".to_string(),
109 ')' => "\\)".to_string(),
110 '\\' => "\\\\".to_string(),
111 '\n' => "\\n".to_string(),
112 '\r' => "\\r".to_string(),
113 '\t' => "\\t".to_string(),
114 '\x08' => "\\b".to_string(),
115 '\x0c' => "\\f".to_string(),
116 _ => c.to_string(),
117 })
118 .collect::<String>();
119 self.commands.push(format!("({}) Tj", escaped));
120 self
121 }
122
123 pub fn text_line(mut self, text: &str) -> Self {
125 let escaped = text
126 .chars()
127 .map(|c| match c {
128 '(' => "\\(".to_string(),
129 ')' => "\\)".to_string(),
130 '\\' => "\\\\".to_string(),
131 '\n' => "\\n".to_string(),
132 '\r' => "\\r".to_string(),
133 '\t' => "\\t".to_string(),
134 '\x08' => "\\b".to_string(),
135 '\x0c' => "\\f".to_string(),
136 _ => c.to_string(),
137 })
138 .collect::<String>();
139 self.commands.push(format!("({}) '", escaped));
140 self
141 }
142
143 pub fn text_line_spacing(mut self, text: &str, word_spacing: f64, char_spacing: f64) -> Self {
145 let escaped = text
146 .chars()
147 .map(|c| match c {
148 '(' => "\\(".to_string(),
149 ')' => "\\)".to_string(),
150 '\\' => "\\\\".to_string(),
151 '\n' => "\\n".to_string(),
152 '\r' => "\\r".to_string(),
153 '\t' => "\\t".to_string(),
154 '\x08' => "\\b".to_string(),
155 '\x0c' => "\\f".to_string(),
156 _ => c.to_string(),
157 })
158 .collect::<String>();
159 self.commands.push(format!(
160 "{} {} ({}) \"",
161 word_spacing, char_spacing, escaped
162 ));
163 self
164 }
165
166 pub fn char_spacing(mut self, spacing: f64) -> Self {
168 self.commands.push(format!("{} Tc", spacing));
169 self
170 }
171
172 pub fn word_spacing(mut self, spacing: f64) -> Self {
174 self.commands.push(format!("{} Tw", spacing));
175 self
176 }
177
178 pub fn horizontal_scaling(mut self, scale: f64) -> Self {
180 self.commands.push(format!("{} Tz", scale * 100.0));
181 self
182 }
183
184 pub fn leading(mut self, leading: f64) -> Self {
186 self.commands.push(format!("{} TL", leading));
187 self
188 }
189
190 pub fn rendering_mode(mut self, mode: TextRenderingMode) -> Self {
192 let code = match mode {
193 TextRenderingMode::Fill => 0,
194 TextRenderingMode::Stroke => 1,
195 TextRenderingMode::FillStroke => 2,
196 TextRenderingMode::Invisible => 3,
197 TextRenderingMode::FillClip => 4,
198 TextRenderingMode::StrokeClip => 5,
199 TextRenderingMode::FillStrokeClip => 6,
200 TextRenderingMode::Clip => 7,
201 };
202 self.commands.push(format!("{} Tr", code));
203 self
204 }
205
206 pub fn text_rise(mut self, rise: f64) -> Self {
208 self.commands.push(format!("{} Ts", rise));
209 self
210 }
211
212 pub fn set_color(mut self, color: Color) -> Self {
214 self.commands
215 .push(format!("{} {} {} rg", color.r, color.g, color.b));
216 self
217 }
218
219 pub fn translate(mut self, tx: f64, ty: f64) -> Self {
221 self.commands.push(format!("1 0 0 1 {} {} Tm", tx, ty));
222 self
223 }
224
225 pub fn rotate(mut self, angle: f64) -> Self {
227 let cos_a = angle.cos();
228 let sin_a = angle.sin();
229 self.commands
230 .push(format!("{} {} {} {} 0 0 Tm", cos_a, sin_a, -sin_a, cos_a));
231 self
232 }
233
234 pub fn scale(mut self, sx: f64, sy: f64) -> Self {
236 self.commands.push(format!("{} 0 0 {} 0 0 Tm", sx, sy));
237 self
238 }
239
240 pub fn skew(mut self, ax: f64, ay: f64) -> Self {
242 let tan_x = ax.tan();
243 let tan_y = ay.tan();
244 self.commands
245 .push(format!("1 {} {} 1 0 0 Tm", tan_x, tan_y));
246 self
247 }
248
249 #[cfg_attr(feature = "tracing", instrument)]
251 pub fn finish(self) -> Vec<u8> {
252 let mut cmds = self.commands;
253 cmds.push("ET".to_string());
254 let mut content = cmds.join("\n");
255 content.push('\n');
256 content.into_bytes()
257 }
258
259 pub fn extend(mut self, other: TextBuilder) -> Self {
261 self.commands.extend(other.commands);
262 self
263 }
264}
265
266impl Default for TextBuilder {
267 fn default() -> Self {
268 Self::new()
269 }
270}