1use crate::layout::Color;
29use crate::object::Object;
30use std::collections::HashMap;
31
32fn key(s: &str) -> String {
34 s.to_string()
35}
36
37#[derive(Debug, Clone, Copy, Default)]
39pub enum PatternPaintType {
40 #[default]
42 Colored = 1,
43 Uncolored = 2,
45}
46
47#[derive(Debug, Clone, Copy, Default)]
49pub enum PatternTilingType {
50 #[default]
52 ConstantSpacing = 1,
53 NoDistortion = 2,
55 ConstantSpacingFaster = 3,
57}
58
59#[derive(Debug, Clone)]
64pub struct TilingPatternBuilder {
65 bbox: (f32, f32, f32, f32),
67 x_step: f32,
69 y_step: f32,
71 paint_type: PatternPaintType,
73 tiling_type: PatternTilingType,
75 content: Vec<u8>,
77 matrix: Option<[f32; 6]>,
79 resources: HashMap<Vec<u8>, Object>,
81}
82
83impl Default for TilingPatternBuilder {
84 fn default() -> Self {
85 Self {
86 bbox: (0.0, 0.0, 10.0, 10.0),
87 x_step: 10.0,
88 y_step: 10.0,
89 paint_type: PatternPaintType::Colored,
90 tiling_type: PatternTilingType::ConstantSpacing,
91 content: Vec::new(),
92 matrix: None,
93 resources: HashMap::new(),
94 }
95 }
96}
97
98impl TilingPatternBuilder {
99 pub fn new() -> Self {
101 Self::default()
102 }
103
104 pub fn bbox(mut self, x: f32, y: f32, width: f32, height: f32) -> Self {
106 self.bbox = (x, y, width, height);
107 self
108 }
109
110 pub fn x_step(mut self, step: f32) -> Self {
112 self.x_step = step;
113 self
114 }
115
116 pub fn y_step(mut self, step: f32) -> Self {
118 self.y_step = step;
119 self
120 }
121
122 pub fn step(self, x: f32, y: f32) -> Self {
124 self.x_step(x).y_step(y)
125 }
126
127 pub fn colored(mut self) -> Self {
129 self.paint_type = PatternPaintType::Colored;
130 self
131 }
132
133 pub fn uncolored(mut self) -> Self {
135 self.paint_type = PatternPaintType::Uncolored;
136 self
137 }
138
139 pub fn tiling_type(mut self, tiling: PatternTilingType) -> Self {
141 self.tiling_type = tiling;
142 self
143 }
144
145 pub fn matrix(mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
147 self.matrix = Some([a, b, c, d, e, f]);
148 self
149 }
150
151 pub fn content_bytes(mut self, content: Vec<u8>) -> Self {
153 self.content = content;
154 self
155 }
156
157 pub fn build(&self) -> (Object, Vec<u8>) {
161 let mut dict: HashMap<String, Object> = HashMap::new();
162
163 dict.insert(key("Type"), Object::Name("Pattern".to_string()));
165
166 dict.insert(key("PatternType"), Object::Integer(1));
168
169 dict.insert(key("PaintType"), Object::Integer(self.paint_type as i64));
171
172 dict.insert(key("TilingType"), Object::Integer(self.tiling_type as i64));
174
175 dict.insert(
177 key("BBox"),
178 Object::Array(vec![
179 Object::Real(self.bbox.0 as f64),
180 Object::Real(self.bbox.1 as f64),
181 Object::Real(self.bbox.2 as f64),
182 Object::Real(self.bbox.3 as f64),
183 ]),
184 );
185
186 dict.insert(key("XStep"), Object::Real(self.x_step as f64));
188 dict.insert(key("YStep"), Object::Real(self.y_step as f64));
189
190 if let Some(m) = &self.matrix {
192 dict.insert(
193 key("Matrix"),
194 Object::Array(m.iter().map(|&v| Object::Real(v as f64)).collect()),
195 );
196 }
197
198 if !self.resources.is_empty() {
200 let converted: HashMap<String, Object> = self
201 .resources
202 .iter()
203 .map(|(k, v)| (String::from_utf8_lossy(k).to_string(), v.clone()))
204 .collect();
205 dict.insert(key("Resources"), Object::Dictionary(converted));
206 }
207
208 (Object::Dictionary(dict), self.content.clone())
209 }
210}
211
212#[derive(Debug, Clone, Default)]
216pub struct ShadingPatternBuilder {
217 shading_id: Option<u32>,
219 matrix: Option<[f32; 6]>,
221 ext_gstate_id: Option<u32>,
223}
224
225impl ShadingPatternBuilder {
226 pub fn new() -> Self {
228 Self::default()
229 }
230
231 pub fn shading_id(mut self, id: u32) -> Self {
233 self.shading_id = Some(id);
234 self
235 }
236
237 pub fn matrix(mut self, a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
239 self.matrix = Some([a, b, c, d, e, f]);
240 self
241 }
242
243 pub fn ext_gstate_id(mut self, id: u32) -> Self {
245 self.ext_gstate_id = Some(id);
246 self
247 }
248
249 pub fn build(&self) -> Object {
251 let mut dict: HashMap<String, Object> = HashMap::new();
252
253 dict.insert(key("Type"), Object::Name("Pattern".to_string()));
255
256 dict.insert(key("PatternType"), Object::Integer(2));
258
259 if let Some(id) = self.shading_id {
262 dict.insert(key("Shading"), Object::Reference(crate::object::ObjectRef::new(id, 0)));
263 }
264
265 if let Some(m) = &self.matrix {
267 dict.insert(
268 key("Matrix"),
269 Object::Array(m.iter().map(|&v| Object::Real(v as f64)).collect()),
270 );
271 }
272
273 if let Some(id) = self.ext_gstate_id {
275 dict.insert(key("ExtGState"), Object::Reference(crate::object::ObjectRef::new(id, 0)));
276 }
277
278 Object::Dictionary(dict)
279 }
280}
281
282pub struct PatternPresets;
284
285impl PatternPresets {
286 pub fn horizontal_stripes(
288 width: f32,
289 _height: f32,
290 stripe_height: f32,
291 color: Color,
292 ) -> Vec<u8> {
293 format!(
294 "{} {} {} rg\n0 0 {} {} re\nf\n",
295 color.r, color.g, color.b, width, stripe_height
296 )
297 .into_bytes()
298 }
299
300 pub fn vertical_stripes(_width: f32, height: f32, stripe_width: f32, color: Color) -> Vec<u8> {
302 format!(
303 "{} {} {} rg\n0 0 {} {} re\nf\n",
304 color.r, color.g, color.b, stripe_width, height
305 )
306 .into_bytes()
307 }
308
309 pub fn checkerboard(size: f32, color1: Color, color2: Color) -> Vec<u8> {
311 format!(
312 "{} {} {} rg\n0 0 {} {} re\nf\n{} {} {} rg\n{} 0 {} {} re\n0 {} {} {} re\nf\n",
313 color1.r,
314 color1.g,
315 color1.b,
316 size * 2.0,
317 size * 2.0,
318 color2.r,
319 color2.g,
320 color2.b,
321 size,
322 size,
323 size,
324 size,
325 size,
326 size
327 )
328 .into_bytes()
329 }
330
331 pub fn dots(spacing: f32, radius: f32, color: Color) -> Vec<u8> {
333 let k = radius * 0.552_284_8;
335 let cx = spacing / 2.0;
336 let cy = spacing / 2.0;
337
338 format!(
339 "{} {} {} rg\n\
340 {} {} m\n\
341 {} {} {} {} {} {} c\n\
342 {} {} {} {} {} {} c\n\
343 {} {} {} {} {} {} c\n\
344 {} {} {} {} {} {} c\n\
345 f\n",
346 color.r,
347 color.g,
348 color.b,
349 cx + radius,
350 cy,
351 cx + radius,
352 cy + k,
353 cx + k,
354 cy + radius,
355 cx,
356 cy + radius,
357 cx - k,
358 cy + radius,
359 cx - radius,
360 cy + k,
361 cx - radius,
362 cy,
363 cx - radius,
364 cy - k,
365 cx - k,
366 cy - radius,
367 cx,
368 cy - radius,
369 cx + k,
370 cy - radius,
371 cx + radius,
372 cy - k,
373 cx + radius,
374 cy
375 )
376 .into_bytes()
377 }
378
379 pub fn diagonal_lines(size: f32, line_width: f32, color: Color) -> Vec<u8> {
381 format!(
382 "{} {} {} RG\n{} w\n0 0 m\n{} {} l\nS\n",
383 color.r, color.g, color.b, line_width, size, size
384 )
385 .into_bytes()
386 }
387
388 pub fn crosshatch(size: f32, line_width: f32, color: Color) -> Vec<u8> {
390 format!(
391 "{} {} {} RG\n{} w\n\
392 0 0 m\n{} {} l\nS\n\
393 {} 0 m\n0 {} l\nS\n",
394 color.r, color.g, color.b, line_width, size, size, size, size
395 )
396 .into_bytes()
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_tiling_pattern_builder() {
406 let content =
407 PatternPresets::horizontal_stripes(10.0, 10.0, 5.0, Color::new(1.0, 0.0, 0.0));
408 let (dict, _content) = TilingPatternBuilder::new()
409 .bbox(0.0, 0.0, 10.0, 10.0)
410 .step(10.0, 10.0)
411 .colored()
412 .content_bytes(content)
413 .build();
414
415 if let Object::Dictionary(d) = dict {
416 assert!(d.contains_key("PatternType"));
417 if let Some(Object::Integer(pt)) = d.get("PatternType") {
418 assert_eq!(*pt, 1);
419 }
420 } else {
421 panic!("Expected dictionary");
422 }
423 }
424
425 #[test]
426 fn test_shading_pattern_builder() {
427 let dict = ShadingPatternBuilder::new()
428 .shading_id(5)
429 .matrix(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
430 .build();
431
432 if let Object::Dictionary(d) = dict {
433 assert!(d.contains_key("PatternType"));
434 if let Some(Object::Integer(pt)) = d.get("PatternType") {
435 assert_eq!(*pt, 2);
436 }
437 } else {
438 panic!("Expected dictionary");
439 }
440 }
441
442 #[test]
443 fn test_pattern_presets() {
444 let _ = PatternPresets::horizontal_stripes(10.0, 10.0, 5.0, Color::new(0.0, 0.0, 1.0));
445 let _ = PatternPresets::checkerboard(5.0, Color::white(), Color::black());
446 let _ = PatternPresets::dots(10.0, 2.0, Color::new(1.0, 0.0, 0.0));
447 let _ = PatternPresets::diagonal_lines(10.0, 0.5, Color::black());
448 }
449}