1#![cfg_attr(not(feature = "std"), no_std)]
16
17#[cfg(not(feature = "std"))]
18extern crate alloc;
19
20#[cfg(not(feature = "std"))]
21#[allow(unused_imports)]
22use alloc::{
23 borrow::ToOwned,
24 format,
25 string::{String, ToString},
26 vec,
27 vec::Vec,
28};
29
30use core::fmt::Write;
31
32use qrcode_core::Color as ModuleColor;
33use qrcode_render::colors::{CmykColor as SharedCmykColor, ColorSpace, RgbColor};
34use qrcode_render::{Canvas as RenderCanvas, Pixel, StyledPixel};
35
36#[derive(Copy, Clone, Default, PartialEq, PartialOrd)]
40pub struct Color(pub [f64; 3]);
41
42#[derive(Copy, Clone, Default, PartialEq, PartialOrd)]
47pub struct CmykColor(pub [f64; 4]);
48
49impl Pixel for Color {
50 type Canvas = Canvas;
51 type Image = Vec<u8>;
52
53 fn default_color(color: ModuleColor) -> Self {
54 Self(color.select(Default::default(), [1.0; 3]))
55 }
56}
57
58impl StyledPixel for Color {
59 fn from_hex(hex: &str) -> Self {
60 let (r, g, b) = qrcode_render::colors::hex_to_rgb(hex).unwrap_or((0, 0, 0));
61 Self([r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0])
62 }
63}
64
65impl From<RgbColor> for Color {
66 fn from(color: RgbColor) -> Self {
67 Self(color.to_array())
68 }
69}
70
71impl Pixel for CmykColor {
72 type Canvas = CmykCanvas;
73 type Image = Vec<u8>;
74
75 fn default_color(color: ModuleColor) -> Self {
76 Self(color.select([0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 0.0, 0.0]))
77 }
78}
79
80impl StyledPixel for CmykColor {
81 fn from_hex(hex: &str) -> Self {
82 let (r, g, b) = qrcode_render::colors::hex_to_rgb(hex).unwrap_or((0, 0, 0));
83 SharedCmykColor::from_rgb(RgbColor::new(r, g, b)).into()
84 }
85}
86
87impl From<SharedCmykColor> for CmykColor {
88 fn from(color: SharedCmykColor) -> Self {
89 Self(color.to_array())
90 }
91}
92
93#[doc(hidden)]
94pub struct Canvas {
95 stream: String,
96 width: u32,
97 height: u32,
98 fg_r: f64,
99 fg_g: f64,
100 fg_b: f64,
101 pending_left: u32,
102 pending_bottom: u32,
103 pending_width: u32,
104 pending_height: u32,
105 has_pending: bool,
106}
107
108#[doc(hidden)]
109pub struct CmykCanvas {
110 stream: String,
111 width: u32,
112 height: u32,
113 fg_c: f64,
114 fg_m: f64,
115 fg_y: f64,
116 fg_k: f64,
117 pending_left: u32,
118 pending_bottom: u32,
119 pending_width: u32,
120 pending_height: u32,
121 has_pending: bool,
122}
123
124impl Canvas {
125 fn flush_pending(&mut self) {
126 if self.has_pending {
127 writeln!(
128 self.stream,
129 "{} {} {} rg {} {} {} {} re f",
130 self.fg_r,
131 self.fg_g,
132 self.fg_b,
133 self.pending_left,
134 self.pending_bottom,
135 self.pending_width,
136 self.pending_height
137 )
138 .unwrap();
139 self.has_pending = false;
140 }
141 }
142}
143
144impl RenderCanvas for Canvas {
145 type Pixel = Color;
146 type Image = Vec<u8>;
147
148 fn new(width: u32, height: u32, dark_pixel: Color, _light_pixel: Color) -> Self {
149 Canvas {
150 stream: String::new(),
151 width,
152 height,
153 fg_r: dark_pixel.0[0],
154 fg_g: dark_pixel.0[1],
155 fg_b: dark_pixel.0[2],
156 pending_left: 0,
157 pending_bottom: 0,
158 pending_width: 0,
159 pending_height: 0,
160 has_pending: false,
161 }
162 }
163
164 fn draw_dark_pixel(&mut self, x: u32, y: u32) {
165 self.draw_dark_rect(x, y, 1, 1);
166 }
167
168 fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
169 let bottom = self.height - top - height;
170 if self.has_pending
171 && bottom == self.pending_bottom
172 && height == self.pending_height
173 && left == self.pending_left + self.pending_width
174 {
175 self.pending_width += width;
176 } else {
177 self.flush_pending();
178 self.pending_left = left;
179 self.pending_bottom = bottom;
180 self.pending_width = width;
181 self.pending_height = height;
182 self.has_pending = true;
183 }
184 }
185
186 fn into_image(mut self) -> Vec<u8> {
187 self.flush_pending();
188
189 let w = self.width;
190 let h = self.height;
191 let stream_content = &self.stream;
192 let stream_len = stream_content.len();
193
194 let mut obj_offsets = Vec::with_capacity(5);
195 let mut pos: usize = 0;
196
197 let header = "%PDF-1.4\n";
199 pos += header.len();
200
201 obj_offsets.push(pos);
203 let obj1 = "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
204 pos += obj1.len();
205
206 obj_offsets.push(pos);
208 let obj2 = "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";
209 pos += obj2.len();
210
211 obj_offsets.push(pos);
213 let mut obj3 = String::new();
214 write!(
215 &mut obj3,
216 "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Contents 4 0 R /Resources << >> >>\nendobj\n",
217 )
218 .unwrap();
219 pos += obj3.len();
220
221 obj_offsets.push(pos);
223 let mut obj4_head = String::new();
224 write!(&mut obj4_head, "4 0 obj\n<< /Length {stream_len} >>\nstream\n").unwrap();
225 pos += obj4_head.len();
226 pos += stream_len;
227 let obj4_tail = "\nendstream\nendobj\n";
228 pos += obj4_tail.len();
229
230 let xref_pos = pos;
232 let mut xref = String::new();
233 xref.push_str("xref\n0 5\n");
234 xref.push_str("0000000000 65535 f \n");
235 for &off in &obj_offsets {
236 writeln!(&mut xref, "{off:010} 00000 n ").unwrap();
237 }
238
239 let mut trailer = String::new();
241 write!(&mut trailer, "trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{xref_pos}\n%%EOF\n",).unwrap();
242
243 let total = pos + xref.len() + trailer.len();
245 let mut pdf = Vec::with_capacity(total);
246 pdf.extend_from_slice(header.as_bytes());
247 pdf.extend_from_slice(obj1.as_bytes());
248 pdf.extend_from_slice(obj2.as_bytes());
249 pdf.extend_from_slice(obj3.as_bytes());
250 pdf.extend_from_slice(obj4_head.as_bytes());
251 pdf.extend_from_slice(stream_content.as_bytes());
252 pdf.extend_from_slice(obj4_tail.as_bytes());
253 pdf.extend_from_slice(xref.as_bytes());
254 pdf.extend_from_slice(trailer.as_bytes());
255 pdf
256 }
257}
258
259impl CmykCanvas {
260 fn flush_pending(&mut self) {
261 if self.has_pending {
262 writeln!(
263 self.stream,
264 "{} {} {} {} k {} {} {} {} re f",
265 self.fg_c,
266 self.fg_m,
267 self.fg_y,
268 self.fg_k,
269 self.pending_left,
270 self.pending_bottom,
271 self.pending_width,
272 self.pending_height
273 )
274 .unwrap();
275 self.has_pending = false;
276 }
277 }
278}
279
280impl RenderCanvas for CmykCanvas {
281 type Pixel = CmykColor;
282 type Image = Vec<u8>;
283
284 fn new(width: u32, height: u32, dark_pixel: CmykColor, _light_pixel: CmykColor) -> Self {
285 CmykCanvas {
286 stream: String::new(),
287 width,
288 height,
289 fg_c: dark_pixel.0[0],
290 fg_m: dark_pixel.0[1],
291 fg_y: dark_pixel.0[2],
292 fg_k: dark_pixel.0[3],
293 pending_left: 0,
294 pending_bottom: 0,
295 pending_width: 0,
296 pending_height: 0,
297 has_pending: false,
298 }
299 }
300
301 fn draw_dark_pixel(&mut self, x: u32, y: u32) {
302 self.draw_dark_rect(x, y, 1, 1);
303 }
304
305 fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
306 let bottom = self.height - top - height;
307 if self.has_pending
308 && bottom == self.pending_bottom
309 && height == self.pending_height
310 && left == self.pending_left + self.pending_width
311 {
312 self.pending_width += width;
313 } else {
314 self.flush_pending();
315 self.pending_left = left;
316 self.pending_bottom = bottom;
317 self.pending_width = width;
318 self.pending_height = height;
319 self.has_pending = true;
320 }
321 }
322
323 fn into_image(mut self) -> Vec<u8> {
324 self.flush_pending();
325
326 let w = self.width;
327 let h = self.height;
328 let stream_content = &self.stream;
329 let stream_len = stream_content.len();
330
331 let mut obj_offsets = Vec::with_capacity(5);
332 let mut pos: usize = 0;
333
334 let header = "%PDF-1.4\n";
335 pos += header.len();
336
337 obj_offsets.push(pos);
338 let obj1 = "1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n";
339 pos += obj1.len();
340
341 obj_offsets.push(pos);
342 let obj2 = "2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n";
343 pos += obj2.len();
344
345 obj_offsets.push(pos);
346 let mut obj3 = String::new();
347 write!(
348 &mut obj3,
349 "3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Contents 4 0 R /Resources << >> >>\nendobj\n",
350 )
351 .unwrap();
352 pos += obj3.len();
353
354 obj_offsets.push(pos);
355 let mut obj4_head = String::new();
356 write!(&mut obj4_head, "4 0 obj\n<< /Length {stream_len} >>\nstream\n").unwrap();
357 pos += obj4_head.len();
358 pos += stream_len;
359 let obj4_tail = "\nendstream\nendobj\n";
360 pos += obj4_tail.len();
361
362 let xref_pos = pos;
363 let mut xref = String::new();
364 xref.push_str("xref\n0 5\n");
365 xref.push_str("0000000000 65535 f \n");
366 for &off in &obj_offsets {
367 writeln!(&mut xref, "{off:010} 00000 n ").unwrap();
368 }
369
370 let mut trailer = String::new();
371 write!(&mut trailer, "trailer\n<< /Size 5 /Root 1 0 R >>\nstartxref\n{xref_pos}\n%%EOF\n",).unwrap();
372
373 let total = pos + xref.len() + trailer.len();
374 let mut pdf = Vec::with_capacity(total);
375 pdf.extend_from_slice(header.as_bytes());
376 pdf.extend_from_slice(obj1.as_bytes());
377 pdf.extend_from_slice(obj2.as_bytes());
378 pdf.extend_from_slice(obj3.as_bytes());
379 pdf.extend_from_slice(obj4_head.as_bytes());
380 pdf.extend_from_slice(stream_content.as_bytes());
381 pdf.extend_from_slice(obj4_tail.as_bytes());
382 pdf.extend_from_slice(xref.as_bytes());
383 pdf.extend_from_slice(trailer.as_bytes());
384 pdf
385 }
386}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391 use qrcode_core::Color as ModuleColor;
392 use qrcode_render::{Renderer, StyledPixel};
393
394 #[test]
395 fn test_pdf_header() {
396 let colors = vec![ModuleColor::Dark; 4];
397 let pdf: Vec<u8> = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
398 assert!(pdf.starts_with(b"%PDF-1.4\n"));
399 assert!(pdf.ends_with(b"%%EOF\n"));
400 }
401
402 #[test]
403 fn test_pdf_contains_rect() {
404 let colors = vec![ModuleColor::Dark; 4];
405 let pdf: Vec<u8> = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
406 let content = String::from_utf8_lossy(&pdf);
407 assert!(content.contains(" re f"));
408 assert!(content.contains(" rg"));
409 }
410
411 #[test]
412 fn test_pdf_xref() {
413 let colors = vec![ModuleColor::Light, ModuleColor::Dark, ModuleColor::Dark, ModuleColor::Light];
414 let pdf: Vec<u8> = Renderer::<Color>::new(&colors, 2, 1).module_dimensions(1, 1).build();
415 let content = String::from_utf8_lossy(&pdf);
416 assert!(content.contains("xref"));
417 assert!(content.contains("trailer"));
418 assert!(content.contains("startxref"));
419 }
420
421 #[test]
422 fn test_pdf_empty_rects() {
423 let colors = vec![ModuleColor::Light; 4];
424 let pdf: Vec<u8> = Renderer::<Color>::new(&colors, 2, 0).module_dimensions(1, 1).build();
425 assert!(pdf.starts_with(b"%PDF-1.4"));
427 assert!(pdf.ends_with(b"%%EOF\n"));
428 }
429
430 #[test]
431 fn test_pdf_color_parses_hex_for_templates() {
432 let color = Color::from_hex("#336699");
433
434 assert!((color.0[0] - 0.2).abs() < f64::EPSILON);
435 assert!((color.0[1] - 0.4).abs() < f64::EPSILON);
436 assert!((color.0[2] - 0.6).abs() < f64::EPSILON);
437 }
438
439 #[test]
440 fn test_pdf_cmyk_renderer_outputs_native_cmyk_operator() {
441 let colors = vec![ModuleColor::Dark, ModuleColor::Light, ModuleColor::Light, ModuleColor::Dark];
442 let pdf: Vec<u8> = Renderer::<CmykColor>::new(&colors, 2, 0)
443 .dark_color(CmykColor([1.0, 0.0, 0.0, 0.25]))
444 .module_dimensions(1, 1)
445 .build();
446 let content = String::from_utf8_lossy(&pdf);
447
448 assert!(content.contains("1 0 0 0.25 k"));
449 assert!(!content.contains(" rg"));
450 }
451}