1use alloc::string::String;
27use alloc::vec::Vec;
28use core::fmt::Write;
29use core::sync::atomic::{AtomicUsize, Ordering};
30
31use rt::fx::{cvtd64, maxsd, minsd};
32
33pub const ERR_NOMEM: i32 = -8;
35pub const ERR_UNSUPPORTED: i32 = -9;
36pub const ERR_DIMS: i32 = -12;
37pub const ERR_IO: i32 = -13;
38pub const ERR_CANCEL: i32 = -14;
39pub const ERR_LIMIT: i32 = -15;
40
41pub const PRINT_XRGB: u8 = 0; pub const PRINT_PARGB: u8 = 1; pub const PRINT_ARGB: u8 = 3; pub const PJF_LANDSCAPE: u32 = 1; pub const PJF_CENTER: u32 = 2; pub const PJF_SMASK: u32 = 4; pub const PJS_FIT: u32 = 0; pub const PJS_DPI: u32 = 1; pub const PJS_PERCENT: u32 = 2; pub const PAPER_A4: u32 = 0;
56pub const PAPER_LETTER: u32 = 1;
57pub const PAPER_A5: u32 = 2;
58pub const PAPER_LEGAL: u32 = 3;
59pub const PAPER_A3: u32 = 4;
60pub const PAPER_N: u32 = 5;
61pub const PRW_BGR: u32 = 1; pub const PRW_PREMUL: u32 = 2; pub const PRW_COPY: u32 = 4; pub const PRW_OPAQUE: u32 = 8; const TITLE_MAX: usize = 256; const PDF_LEVEL_DEF: i32 = 6;
69
70#[derive(Clone, Copy)]
72pub struct PrintSrc<'a> {
73 pub px: &'a [u8],
74 pub w: i32,
75 pub h: i32,
76 pub stride: i32,
77 pub fmt: u8, }
79
80#[derive(Clone, Copy)]
82pub struct PrintJob<'a> {
83 pub src: Option<PrintSrc<'a>>,
84 pub title: &'a str, pub dpi_x: f64, pub dpi_y: f64, pub flags: u32, pub scale: u32, pub percent: u32, pub margin: u32, pub paper: u32, pub copies: u32, pub page_w: f64, pub page_h: f64,
96}
97
98impl<'a> PrintJob<'a> {
99 pub fn new(src: Option<PrintSrc<'a>>, title: &'a str) -> PrintJob<'a> {
100 PrintJob {
101 src,
102 title,
103 dpi_x: 96.0,
104 dpi_y: 96.0,
105 flags: 0,
106 scale: PJS_FIT,
107 percent: 100,
108 margin: 0,
109 paper: PAPER_A4,
110 copies: 0,
111 page_w: 0.0,
112 page_h: 0.0,
113 }
114 }
115}
116
117#[derive(Clone, Copy, Default, Debug)]
119pub struct PPage {
120 pub w: f64, pub h: f64,
122 pub l: f64, pub t: f64,
124 pub r: f64,
125 pub b: f64,
126 pub token: u32, pub flags: u32, }
129
130#[derive(Clone, Copy, Default, Debug)]
132pub struct Place {
133 pub x: f64, pub y: f64,
135 pub w: f64,
136 pub h: f64,
137 pub ax: f64, pub ay: f64,
139 pub aw: f64,
140 pub ah: f64,
141}
142
143#[derive(Clone, Copy, Default)]
145pub struct PdfOpt<'a> {
146 pub page_w: f64, pub page_h: f64,
148 pub x: f64, pub y: f64,
150 pub w: f64,
151 pub h: f64,
152 pub cx: f64, pub cy: f64,
154 pub cw: f64,
155 pub ch: f64,
156 pub title: &'a str, pub flags: u32, pub level: i32, }
160
161static PAPER_PT: [[f64; 2]; 5] = [
163 [595.2755905511812, 841.8897637795276], [612.0, 792.0], [419.5275590551181, 595.2755905511812], [612.0, 1008.0], [841.8897637795276, 1190.551181102362], ];
169const MM100_PT: f64 = 0.028346456692913385; pub fn page_init(paper: u32, flags: u32) -> PPage {
173 let p = PAPER_PT[if paper >= PAPER_N { 0 } else { paper as usize }];
174 let (w, h) = if flags & PJF_LANDSCAPE != 0 { (p[1], p[0]) } else { (p[0], p[1]) };
175 PPage { w, h, l: 0.0, t: 0.0, r: 0.0, b: 0.0, token: 0, flags: flags & PJF_LANDSCAPE }
176}
177
178pub fn print_dpi(unit: u32, dpu: f64) -> f64 {
181 if unit < 1 || !(dpu > 0.0) {
182 return 96.0;
183 }
184 if unit == 2 { dpu * 2.54 } else { dpu }
185}
186
187pub fn layout(job: &PrintJob, page: &PPage) -> Place {
189 let dx = if job.dpi_x > 0.0 { job.dpi_x } else { 96.0 };
190 let dy = if job.dpi_y > 0.0 { job.dpi_y } else { dx };
191 let (pw, ph) = job.src.map_or((1, 1), |s| (s.w, s.h));
192 let mut iw = pw as f64 * 72.0 / dx; let mut ih = ph as f64 * 72.0 / dy;
194 let m = job.margin as f64 * MM100_PT;
195 let mut pl = Place::default();
196 pl.ax = maxsd(page.l, m);
197 pl.aw = maxsd(page.w - maxsd(page.r, m) - pl.ax, 1.0);
198 pl.ay = maxsd(page.t, m);
199 pl.ah = maxsd(page.h - maxsd(page.b, m) - pl.ay, 1.0);
200 if job.scale != PJS_DPI {
201 let s = if job.scale == PJS_PERCENT {
202 job.percent.clamp(1, 10000) as f64 / 100.0
203 } else {
204 minsd(pl.aw / iw, pl.ah / ih)
205 };
206 iw *= s;
207 ih *= s;
208 }
209 pl.w = iw;
210 pl.h = ih;
211 pl.x = pl.ax;
212 pl.y = pl.ay;
213 if job.flags & PJF_CENTER != 0 {
214 pl.x += (pl.aw - iw) * 0.5;
215 pl.y += (pl.ah - ih) * 0.5;
216 }
217 pl
218}
219
220pub fn row_white(dst: &mut [u8], src: &[u8], n: usize, mode: u32) {
224 for (d, s) in dst.chunks_exact_mut(3).zip(src.chunks_exact(4)).take(n) {
225 let (b, g, r) = (s[0] as u32, s[1] as u32, s[2] as u32);
226 let a = s[3] as u32;
227 let (b, g, r) = if mode & (PRW_OPAQUE | PRW_COPY) != 0 {
228 (b, g, r)
229 } else if mode & PRW_PREMUL != 0 {
230 let k = 255 - a;
231 ((b + k).min(255), (g + k).min(255), (r + k).min(255))
232 } else {
233 let w = (255 - a) * 255 + 128;
234 let st = |c: u32| {
235 let t = c * a + w;
236 (t + (t >> 8)) >> 8
237 };
238 (st(b), st(g), st(r))
239 };
240 if mode & PRW_BGR != 0 {
241 d[0] = b as u8;
242 d[1] = g as u8;
243 d[2] = r as u8;
244 } else {
245 d[0] = r as u8;
246 d[1] = g as u8;
247 d[2] = b as u8;
248 }
249 }
250}
251
252pub fn print_pdf(job: &mut PrintJob, page: &PPage) -> Result<Vec<u8>, i32> {
254 let src = job.src.ok_or(ERR_DIMS)?;
255 let pl = layout(job, page);
256 job.page_w = page.w;
257 job.page_h = page.h;
258 let opt = PdfOpt {
259 page_w: page.w,
260 page_h: page.h,
261 x: pl.x,
262 y: pl.y,
263 w: pl.w,
264 h: pl.h,
265 cx: pl.ax, cy: pl.ay,
267 cw: pl.aw,
268 ch: pl.ah,
269 title: job.title,
270 flags: job.flags & PJF_SMASK,
271 level: 0,
272 };
273 pdf_write_image(&src, &opt)
274}
275
276pub type DeflateFn = fn(&[u8], i32) -> Vec<u8>;
280
281static DEFLATE: AtomicUsize = AtomicUsize::new(0);
282
283pub fn set_print_deflate(f: Option<DeflateFn>) {
285 DEFLATE.store(f.map_or(0, |f| f as usize), Ordering::SeqCst);
286}
287
288fn deflate(data: &[u8], level: i32) -> Vec<u8> {
289 let p = DEFLATE.load(Ordering::SeqCst);
290 if p != 0 {
291 let f: DeflateFn = unsafe { core::mem::transmute::<usize, DeflateFn>(p) };
293 return f(data, level);
294 }
295 let mut out = Vec::with_capacity(data.len() + data.len() / 65535 * 5 + 16);
297 out.extend_from_slice(&[0x78, 0x01]);
298 let mut chunks = data.chunks(65535).peekable();
299 if chunks.peek().is_none() {
300 out.extend_from_slice(&[1, 0, 0, 0xFF, 0xFF]);
301 }
302 while let Some(c) = chunks.next() {
303 out.push(chunks.peek().is_none() as u8);
304 out.extend_from_slice(&(c.len() as u16).to_le_bytes());
305 out.extend_from_slice(&(!(c.len() as u16)).to_le_bytes());
306 out.extend_from_slice(c);
307 }
308 let (mut a, mut b) = (1u32, 0u32);
309 for &x in data {
310 a = (a + x as u32) % 65521;
311 b = (b + a) % 65521;
312 }
313 out.extend_from_slice(&(b << 16 | a).to_be_bytes());
314 out
315}
316
317fn pw_num(o: &mut String, v: f64) {
321 let m = cvtd64(v * 1000.0);
322 if m < 0 {
323 o.push('-');
324 }
325 let u = m.unsigned_abs();
326 let _ = write!(o, "{}.{:03}", u / 1000, u % 1000);
327}
328
329fn pw_nums(o: &mut String, a: f64, b: f64) {
330 o.push(' ');
331 pw_num(o, a);
332 o.push(' ');
333 pw_num(o, b);
334}
335
336fn pw_title(o: &mut String, s: &str) {
338 o.push_str(" /Title <FEFF");
339 for c in s.chars().take_while(|&c| c != '\0').take(TITLE_MAX) {
340 let mut buf = [0u16; 2];
341 for u in c.encode_utf16(&mut buf) {
342 let _ = write!(o, "{:04X}", u);
343 }
344 }
345 o.push('>');
346}
347
348pub fn pdf_write_image(src: &PrintSrc, opt: &PdfOpt) -> Result<Vec<u8>, i32> {
352 let (w, h) = (src.w, src.h);
353 if w <= 0 || h <= 0 || src.px.is_empty() {
354 return Err(ERR_DIMS);
355 }
356 let mut mode = match src.fmt {
357 PRINT_ARGB => 0,
358 PRINT_PARGB => PRW_PREMUL,
359 PRINT_XRGB => PRW_OPAQUE,
360 _ => return Err(ERR_UNSUPPORTED),
361 };
362 if src.fmt == PRINT_ARGB && opt.flags & PJF_SMASK != 0 {
363 mode = PRW_COPY;
364 }
365 let level = if opt.level <= 0 { PDF_LEVEL_DEF } else { opt.level };
366 let (wu, hu) = (w as usize, h as usize);
367 if src.stride < w.saturating_mul(4) {
369 return Err(ERR_DIMS);
370 }
371 let covered = wu.checked_mul(4).and_then(|rw| (hu - 1).checked_mul(src.stride as usize)?.checked_add(rw));
372 if covered.is_none_or(|n| n > src.px.len()) {
373 return Err(ERR_DIMS);
374 }
375 let stride = src.stride as isize;
376 let row_of = |y: usize| -> &[u8] {
377 let o = (y as isize * stride) as usize;
378 src.px.get(o..o + wu * 4).unwrap_or(&[])
379 };
380 let rb = wu * 3;
382 let mut raw = alloc::vec![0u8; rb * hu];
383 for y in 0..hu {
384 row_white(&mut raw[y * rb..(y + 1) * rb], row_of(y), wu, mode);
385 }
386 let rgb = deflate(&raw, level);
387 let alpha = if mode == PRW_COPY {
389 let mut a = Vec::with_capacity(wu * hu);
390 for y in 0..hu {
391 let r = row_of(y);
392 for x in 0..wu {
393 a.push(r.get(x * 4 + 3).copied().unwrap_or(0));
394 }
395 }
396 Some(deflate(&a, level))
397 } else {
398 None
399 };
400 drop(raw);
401 let nobj = if alpha.is_some() { 7 } else { 6 };
402 let mut off = [0usize; 8];
403 let mut f: Vec<u8> = Vec::with_capacity(rgb.len() + alpha.as_ref().map_or(0, |a| a.len()) + 4096);
404 let mut s = String::new();
405 f.extend_from_slice(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n");
406 off[1] = f.len();
407 f.extend_from_slice(b"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n");
408 off[2] = f.len();
409 f.extend_from_slice(b"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n");
410 off[3] = f.len();
412 s.push_str("3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0");
413 pw_nums(&mut s, opt.page_w, opt.page_h);
414 s.push_str("] /Resources << /XObject << /Im1 5 0 R >> /ProcSet [/PDF /ImageC] >> /Contents 4 0 R >>\nendobj\n");
415 f.extend_from_slice(s.as_bytes());
416 let mut c = String::from("q\n");
418 if opt.cw > 0.0 {
419 pw_nums(&mut c, opt.cx, opt.page_h - opt.cy - opt.ch); pw_nums(&mut c, opt.cw, opt.ch);
421 c.push_str(" re W n\n");
422 }
423 pw_nums(&mut c, opt.w, 0.0);
424 pw_nums(&mut c, 0.0, opt.h);
425 pw_nums(&mut c, opt.x, opt.page_h - opt.y - opt.h);
426 c.push_str(" cm\n/Im1 Do\nQ\n");
427 off[4] = f.len();
428 s.clear();
429 let _ = write!(s, "4 0 obj\n<< /Length {} >>\nstream\n", c.len());
430 f.extend_from_slice(s.as_bytes());
431 f.extend_from_slice(c.as_bytes());
432 f.extend_from_slice(b"\nendstream\nendobj\n");
433 off[5] = f.len();
435 s.clear();
436 let _ = write!(s, "5 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h}");
437 s.push_str(" /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /FlateDecode");
438 if nobj == 7 {
439 s.push_str(" /SMask 7 0 R");
440 }
441 let _ = write!(s, " /Length {} >>\nstream\n", rgb.len());
442 f.extend_from_slice(s.as_bytes());
443 f.extend_from_slice(&rgb);
444 f.extend_from_slice(b"\nendstream\nendobj\n");
445 off[6] = f.len();
447 s.clear();
448 s.push_str("6 0 obj\n<< /Producer (jfguiLib)");
449 if !opt.title.is_empty() && !opt.title.starts_with('\0') {
450 pw_title(&mut s, opt.title);
451 }
452 s.push_str(" >>\nendobj\n");
453 f.extend_from_slice(s.as_bytes());
454 if let Some(a) = &alpha {
456 off[7] = f.len();
457 s.clear();
458 let _ = write!(s, "7 0 obj\n<< /Type /XObject /Subtype /Image /Width {w} /Height {h}");
459 let _ = write!(
460 s,
461 " /ColorSpace /DeviceGray /BitsPerComponent 8 /Filter /FlateDecode /Length {} >>\nstream\n",
462 a.len()
463 );
464 f.extend_from_slice(s.as_bytes());
465 f.extend_from_slice(a);
466 f.extend_from_slice(b"\nendstream\nendobj\n");
467 }
468 let startxref = f.len();
470 s.clear();
471 let _ = write!(s, "xref\n0 {}\n0000000000 65535 f \n", nobj + 1);
472 for o in &off[1..=nobj] {
473 let _ = write!(s, "{o:010} 00000 n \n");
474 }
475 let _ = write!(s, "trailer\n<< /Size {} /Root 1 0 R /Info 6 0 R >>\nstartxref\n{startxref}\n", nobj + 1);
476 s.push_str("%%EOF\n");
477 f.extend_from_slice(s.as_bytes());
478 Ok(f)
479}