1use crate::error::{Error, Result};
10use crate::json::Object;
11use std::ops::RangeInclusive;
12use std::path::{Path, PathBuf};
13use std::time::{Duration, SystemTime, UNIX_EPOCH};
14
15#[derive(Debug, Clone, Copy, PartialEq, Default)]
17#[non_exhaustive]
18pub enum PageSize {
19 A3,
21 #[default]
23 A4,
24 A5,
26 Letter,
28 Legal,
30 Millimeters {
32 width: f32,
34 height: f32,
36 },
37}
38
39#[derive(Debug, Clone, Copy, PartialEq)]
41pub struct Margin {
42 pub top: f32,
44 pub right: f32,
46 pub bottom: f32,
48 pub left: f32,
50}
51
52impl Margin {
53 pub fn all(points: f32) -> Self {
55 Self {
56 top: points,
57 right: points,
58 bottom: points,
59 left: points,
60 }
61 }
62
63 pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
65 Self {
66 top,
67 right,
68 bottom,
69 left,
70 }
71 }
72}
73
74impl Default for Margin {
75 fn default() -> Self {
77 Self::all(36.0)
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
83#[non_exhaustive]
84pub enum Conformance {
85 PdfA2b,
87 PdfA2u,
89 PdfA3b,
91 PdfA3u,
93 PdfA2a,
96 PdfA3a,
98 PdfA4,
100 PdfA4f,
102}
103
104impl Conformance {
105 fn name(self) -> &'static str {
106 match self {
107 Self::PdfA2b => "PDF/A-2b",
108 Self::PdfA2u => "PDF/A-2u",
109 Self::PdfA3b => "PDF/A-3b",
110 Self::PdfA3u => "PDF/A-3u",
111 Self::PdfA2a => "PDF/A-2a",
112 Self::PdfA3a => "PDF/A-3a",
113 Self::PdfA4 => "PDF/A-4",
114 Self::PdfA4f => "PDF/A-4f",
115 }
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
121#[non_exhaustive]
122pub enum Accessibility {
123 Tagged,
125 PdfUa1,
127 PdfUa2,
129}
130
131impl Accessibility {
132 fn name(self) -> &'static str {
133 match self {
134 Self::Tagged => "tagged",
135 Self::PdfUa1 => "PDF/UA-1",
136 Self::PdfUa2 => "PDF/UA-2",
137 }
138 }
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
143#[non_exhaustive]
144pub enum ImageFormat {
145 #[default]
147 Png,
148 Jpeg,
150 WebP,
152}
153
154impl ImageFormat {
155 fn name(self) -> &'static str {
156 match self {
157 Self::Png => "png",
158 Self::Jpeg => "jpeg",
159 Self::WebP => "webp",
160 }
161 }
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
166#[non_exhaustive]
167pub enum TextOrder {
168 #[default]
170 Reading,
171 TopToBottom,
173}
174
175impl TextOrder {
176 fn name(self) -> &'static str {
177 match self {
178 Self::Reading => "reading",
179 Self::TopToBottom => "top_to_bottom",
180 }
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
186#[non_exhaustive]
187pub enum BarcodeFormat {
188 QrCode,
190 DataMatrix,
192 Code128,
194 Ean13,
196}
197
198impl BarcodeFormat {
199 pub(crate) fn name(self) -> &'static str {
200 match self {
201 Self::QrCode => "qr_code",
202 Self::DataMatrix => "data_matrix",
203 Self::Code128 => "code128",
204 Self::Ean13 => "ean13",
205 }
206 }
207
208 pub(crate) fn from_name(name: &str) -> Option<Self> {
209 match name {
210 "qr_code" => Some(Self::QrCode),
211 "data_matrix" => Some(Self::DataMatrix),
212 "code128" => Some(Self::Code128),
213 "ean13" => Some(Self::Ean13),
214 _ => None,
215 }
216 }
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Hash)]
224pub struct Pages(String);
225
226impl From<u32> for Pages {
227 fn from(page: u32) -> Self {
228 Self(page.to_string())
229 }
230}
231
232impl From<&str> for Pages {
233 fn from(range: &str) -> Self {
234 Self(range.to_string())
235 }
236}
237
238impl From<String> for Pages {
239 fn from(range: String) -> Self {
240 Self(range)
241 }
242}
243
244impl From<&[u32]> for Pages {
245 fn from(pages: &[u32]) -> Self {
246 Self(
247 pages
248 .iter()
249 .map(u32::to_string)
250 .collect::<Vec<_>>()
251 .join(","),
252 )
253 }
254}
255
256impl<const N: usize> From<[u32; N]> for Pages {
257 fn from(pages: [u32; N]) -> Self {
258 Self::from(&pages[..])
259 }
260}
261
262impl From<Vec<u32>> for Pages {
263 fn from(pages: Vec<u32>) -> Self {
264 Self::from(&pages[..])
265 }
266}
267
268impl From<RangeInclusive<u32>> for Pages {
269 fn from(range: RangeInclusive<u32>) -> Self {
270 Self(format!("{}-{}", range.start(), range.end()))
271 }
272}
273
274fn absolute(path: &Path, what: &str) -> Result<String> {
275 let absolute = std::path::absolute(path)
276 .map_err(|e| Error::argument(format!("{what}: {}: {e}", path.display())))?;
277 absolute
278 .into_os_string()
279 .into_string()
280 .map_err(|p| Error::argument(format!("{what} is not valid Unicode: {p:?}")))
281}
282
283fn unix_seconds(time: SystemTime) -> i64 {
284 match time.duration_since(UNIX_EPOCH) {
285 Ok(d) => d.as_secs() as i64,
286 Err(e) => -(e.duration().as_secs_f64().ceil() as i64),
287 }
288}
289
290fn timeout(o: &mut Object, timeout: Option<Duration>) -> Result<()> {
293 if let Some(t) = timeout {
294 if t.is_zero() || t.as_secs_f64() > 2_147_483.0 {
295 return Err(Error::argument(format!(
296 "timeout must be above 0 and at most 2,147,483 seconds, not {t:?}"
297 )));
298 }
299 o.num("timeout", t.as_secs_f64())?;
300 }
301 Ok(())
302}
303
304#[derive(Debug, Clone, Default)]
306pub struct PdfOptions {
307 base_dir: Option<PathBuf>,
308 allow_file_access: Option<bool>,
309 page_size: Option<PageSize>,
310 landscape: Option<bool>,
311 margin: Option<Margin>,
312 scale: Option<f32>,
313 shrink_to_fit: Option<bool>,
314 title: Option<String>,
315 author: Option<String>,
316 subject: Option<String>,
317 keywords: Option<String>,
318 language: Option<String>,
319 header_text: Option<String>,
320 footer_text: Option<String>,
321 user_style_sheet: Option<String>,
322 fonts: Vec<PathBuf>,
323 use_system_fonts: Option<bool>,
324 compress: Option<bool>,
325 bookmarks: Option<bool>,
326 max_pages: Option<u32>,
327 creation_date: Option<SystemTime>,
328 conformance: Option<Conformance>,
329 accessibility: Option<Accessibility>,
330 timeout: Option<Duration>,
331}
332
333impl PdfOptions {
334 pub fn new() -> Self {
336 Self::default()
337 }
338
339 pub fn base_dir(mut self, dir: impl Into<PathBuf>) -> Self {
342 self.base_dir = Some(dir.into());
343 self
344 }
345
346 pub fn allow_file_access(mut self, allow: bool) -> Self {
349 self.allow_file_access = Some(allow);
350 self
351 }
352
353 pub fn page_size(mut self, size: PageSize) -> Self {
355 self.page_size = Some(size);
356 self
357 }
358
359 pub fn landscape(mut self, landscape: bool) -> Self {
361 self.landscape = Some(landscape);
362 self
363 }
364
365 pub fn margin(mut self, margin: Margin) -> Self {
367 self.margin = Some(margin);
368 self
369 }
370
371 pub fn scale(mut self, scale: f32) -> Self {
373 self.scale = Some(scale);
374 self
375 }
376
377 pub fn shrink_to_fit(mut self, shrink: bool) -> Self {
379 self.shrink_to_fit = Some(shrink);
380 self
381 }
382
383 pub fn title(mut self, title: impl Into<String>) -> Self {
385 self.title = Some(title.into());
386 self
387 }
388
389 pub fn author(mut self, author: impl Into<String>) -> Self {
391 self.author = Some(author.into());
392 self
393 }
394
395 pub fn subject(mut self, subject: impl Into<String>) -> Self {
397 self.subject = Some(subject.into());
398 self
399 }
400
401 pub fn keywords(mut self, keywords: impl Into<String>) -> Self {
403 self.keywords = Some(keywords.into());
404 self
405 }
406
407 pub fn language(mut self, language: impl Into<String>) -> Self {
409 self.language = Some(language.into());
410 self
411 }
412
413 pub fn header_text(mut self, text: impl Into<String>) -> Self {
415 self.header_text = Some(text.into());
416 self
417 }
418
419 pub fn footer_text(mut self, text: impl Into<String>) -> Self {
421 self.footer_text = Some(text.into());
422 self
423 }
424
425 pub fn user_style_sheet(mut self, css: impl Into<String>) -> Self {
427 self.user_style_sheet = Some(css.into());
428 self
429 }
430
431 pub fn font(mut self, path: impl Into<PathBuf>) -> Self {
435 self.fonts.push(path.into());
436 self
437 }
438
439 pub fn use_system_fonts(mut self, use_them: bool) -> Self {
441 self.use_system_fonts = Some(use_them);
442 self
443 }
444
445 pub fn compress(mut self, compress: bool) -> Self {
447 self.compress = Some(compress);
448 self
449 }
450
451 pub fn bookmarks(mut self, bookmarks: bool) -> Self {
453 self.bookmarks = Some(bookmarks);
454 self
455 }
456
457 pub fn max_pages(mut self, pages: u32) -> Self {
459 self.max_pages = Some(pages);
460 self
461 }
462
463 pub fn creation_date(mut self, date: SystemTime) -> Self {
465 self.creation_date = Some(date);
466 self
467 }
468
469 pub fn conformance(mut self, conformance: Conformance) -> Self {
471 self.conformance = Some(conformance);
472 self
473 }
474
475 pub fn accessibility(mut self, accessibility: Accessibility) -> Self {
477 self.accessibility = Some(accessibility);
478 self
479 }
480
481 pub fn timeout(mut self, timeout: Duration) -> Self {
483 self.timeout = Some(timeout);
484 self
485 }
486
487 pub(crate) fn has_base_dir(&self) -> bool {
488 self.base_dir.is_some()
489 }
490
491 pub(crate) fn to_json(&self) -> Result<String> {
492 let mut o = Object::default();
493 if let Some(dir) = &self.base_dir {
494 o.str("base_dir", &absolute(dir, "base_dir")?);
495 }
496 if let Some(v) = self.allow_file_access {
497 o.bool("allow_file_access", v);
498 }
499 match self.page_size {
500 Some(PageSize::Millimeters { width, height }) => {
501 o.nums("page_size", &[width as f64, height as f64])?
502 }
503 Some(size) => o.str(
504 "page_size",
505 match size {
506 PageSize::A3 => "A3",
507 PageSize::A5 => "A5",
508 PageSize::Letter => "Letter",
509 PageSize::Legal => "Legal",
510 _ => "A4",
511 },
512 ),
513 None => {}
514 }
515 if let Some(v) = self.landscape {
516 o.bool("landscape", v);
517 }
518 if let Some(m) = self.margin {
519 o.nums(
520 "margin",
521 &[m.top as f64, m.right as f64, m.bottom as f64, m.left as f64],
522 )?;
523 }
524 if let Some(v) = self.scale {
525 o.num("scale", v as f64)?;
526 }
527 if let Some(v) = self.shrink_to_fit {
528 o.bool("shrink_to_fit", v);
529 }
530 for (name, value) in [
531 ("title", &self.title),
532 ("author", &self.author),
533 ("subject", &self.subject),
534 ("keywords", &self.keywords),
535 ("language", &self.language),
536 ("header_text", &self.header_text),
537 ("footer_text", &self.footer_text),
538 ("user_style_sheet", &self.user_style_sheet),
539 ] {
540 if let Some(v) = value {
541 o.str(name, v);
542 }
543 }
544 if !self.fonts.is_empty() {
545 let fonts = self
546 .fonts
547 .iter()
548 .map(|f| absolute(f, "font"))
549 .collect::<Result<Vec<_>>>()?;
550 o.strs("fonts", &fonts);
551 }
552 if let Some(v) = self.use_system_fonts {
553 o.bool("use_system_fonts", v);
554 }
555 if let Some(v) = self.compress {
556 o.bool("compress", v);
557 }
558 if let Some(v) = self.bookmarks {
559 o.bool("bookmarks", v);
560 }
561 if let Some(v) = self.max_pages {
562 o.int("max_pages", v.min(i32::MAX as u32) as i64);
563 }
564 if let Some(v) = self.creation_date {
565 o.int("creation_date", unix_seconds(v));
566 }
567 if let Some(v) = self.conformance {
568 o.str("conformance", v.name());
569 }
570 if let Some(v) = self.accessibility {
571 o.str("accessibility", v.name());
572 }
573 timeout(&mut o, self.timeout)?;
574 Ok(o.finish())
575 }
576}
577
578#[derive(Debug, Clone, Default)]
580pub struct ImageOptions {
581 dpi: Option<f32>,
582 format: Option<ImageFormat>,
583 pages: Option<Pages>,
584 width: Option<u32>,
585 height: Option<u32>,
586 transparent: Option<bool>,
587 quality: Option<u8>,
588 password: Option<String>,
589 timeout: Option<Duration>,
590}
591
592impl ImageOptions {
593 pub fn new() -> Self {
595 Self::default()
596 }
597
598 pub fn dpi(mut self, dpi: f32) -> Self {
600 self.dpi = Some(dpi);
601 self
602 }
603
604 pub fn format(mut self, format: ImageFormat) -> Self {
606 self.format = Some(format);
607 self
608 }
609
610 pub fn pages(mut self, pages: impl Into<Pages>) -> Self {
612 self.pages = Some(pages.into());
613 self
614 }
615
616 pub fn width(mut self, pixels: u32) -> Self {
618 self.width = Some(pixels);
619 self
620 }
621
622 pub fn height(mut self, pixels: u32) -> Self {
624 self.height = Some(pixels);
625 self
626 }
627
628 pub fn transparent(mut self, transparent: bool) -> Self {
630 self.transparent = Some(transparent);
631 self
632 }
633
634 pub fn quality(mut self, quality: u8) -> Self {
636 self.quality = Some(quality);
637 self
638 }
639
640 pub fn password(mut self, password: impl Into<String>) -> Self {
642 self.password = Some(password.into());
643 self
644 }
645
646 pub fn timeout(mut self, timeout: Duration) -> Self {
648 self.timeout = Some(timeout);
649 self
650 }
651
652 pub(crate) fn to_json(&self) -> Result<String> {
653 let mut o = Object::default();
654 if let Some(v) = self.dpi {
655 o.num("dpi", v as f64)?;
656 }
657 if let Some(v) = self.format {
658 o.str("format", v.name());
659 }
660 if let Some(v) = &self.pages {
661 o.str("pages", &v.0);
662 }
663 if let Some(v) = self.width {
664 o.int("width", v.min(i32::MAX as u32) as i64);
665 }
666 if let Some(v) = self.height {
667 o.int("height", v.min(i32::MAX as u32) as i64);
668 }
669 if let Some(v) = self.transparent {
670 o.bool("transparent", v);
671 }
672 if let Some(v) = self.quality {
673 o.int("quality", v as i64);
674 }
675 if let Some(v) = &self.password {
676 o.str("password", v);
677 }
678 timeout(&mut o, self.timeout)?;
679 Ok(o.finish())
680 }
681}
682
683#[derive(Debug, Clone, Default)]
685pub struct TextOptions {
686 pages: Option<Pages>,
687 password: Option<String>,
688 order: Option<TextOrder>,
689 timeout: Option<Duration>,
690}
691
692impl TextOptions {
693 pub fn new() -> Self {
695 Self::default()
696 }
697
698 pub fn pages(mut self, pages: impl Into<Pages>) -> Self {
700 self.pages = Some(pages.into());
701 self
702 }
703
704 pub fn password(mut self, password: impl Into<String>) -> Self {
706 self.password = Some(password.into());
707 self
708 }
709
710 pub fn order(mut self, order: TextOrder) -> Self {
712 self.order = Some(order);
713 self
714 }
715
716 pub fn timeout(mut self, timeout: Duration) -> Self {
718 self.timeout = Some(timeout);
719 self
720 }
721
722 pub(crate) fn to_json(&self) -> Result<String> {
723 let mut o = Object::default();
724 if let Some(v) = &self.pages {
725 o.str("pages", &v.0);
726 }
727 if let Some(v) = &self.password {
728 o.str("password", v);
729 }
730 if let Some(v) = self.order {
731 o.str("order", v.name());
732 }
733 timeout(&mut o, self.timeout)?;
734 Ok(o.finish())
735 }
736}
737
738#[derive(Debug, Clone, Default)]
740pub struct MarkdownOptions {
741 pages: Option<Pages>,
742 include_links: Option<bool>,
743 include_annotations: Option<bool>,
744 include_images: Option<bool>,
745 page_comments: Option<bool>,
746 max_pages: Option<u32>,
747 password: Option<String>,
748 timeout: Option<Duration>,
749}
750
751impl MarkdownOptions {
752 pub fn new() -> Self {
754 Self::default()
755 }
756
757 pub fn pages(mut self, pages: impl Into<Pages>) -> Self {
759 self.pages = Some(pages.into());
760 self
761 }
762
763 pub fn include_links(mut self, include: bool) -> Self {
765 self.include_links = Some(include);
766 self
767 }
768
769 pub fn include_annotations(mut self, include: bool) -> Self {
771 self.include_annotations = Some(include);
772 self
773 }
774
775 pub fn include_images(mut self, include: bool) -> Self {
777 self.include_images = Some(include);
778 self
779 }
780
781 pub fn page_comments(mut self, comments: bool) -> Self {
783 self.page_comments = Some(comments);
784 self
785 }
786
787 pub fn max_pages(mut self, pages: u32) -> Self {
789 self.max_pages = Some(pages);
790 self
791 }
792
793 pub fn password(mut self, password: impl Into<String>) -> Self {
795 self.password = Some(password.into());
796 self
797 }
798
799 pub fn timeout(mut self, timeout: Duration) -> Self {
801 self.timeout = Some(timeout);
802 self
803 }
804
805 pub(crate) fn to_json(&self) -> Result<String> {
806 let mut o = Object::default();
807 if let Some(v) = &self.pages {
808 o.str("pages", &v.0);
809 }
810 for (name, value) in [
811 ("include_links", self.include_links),
812 ("include_annotations", self.include_annotations),
813 ("include_images", self.include_images),
814 ("page_comments", self.page_comments),
815 ] {
816 if let Some(v) = value {
817 o.bool(name, v);
818 }
819 }
820 if let Some(v) = self.max_pages {
821 o.int("max_pages", v.min(i32::MAX as u32) as i64);
822 }
823 if let Some(v) = &self.password {
824 o.str("password", v);
825 }
826 timeout(&mut o, self.timeout)?;
827 Ok(o.finish())
828 }
829}
830
831#[derive(Debug, Clone, Default)]
833pub struct PageCountOptions {
834 password: Option<String>,
835}
836
837impl PageCountOptions {
838 pub fn new() -> Self {
840 Self::default()
841 }
842
843 pub fn password(mut self, password: impl Into<String>) -> Self {
845 self.password = Some(password.into());
846 self
847 }
848
849 pub(crate) fn to_json(&self) -> Result<String> {
850 let mut o = Object::default();
851 if let Some(v) = &self.password {
852 o.str("password", v);
853 }
854 Ok(o.finish())
855 }
856}
857
858#[derive(Debug, Clone, Default)]
860pub struct PdfAOptions {
861 conformance: Option<Conformance>,
862 timeout: Option<Duration>,
863}
864
865impl PdfAOptions {
866 pub fn new() -> Self {
868 Self::default()
869 }
870
871 pub fn conformance(mut self, conformance: Conformance) -> Self {
874 self.conformance = Some(conformance);
875 self
876 }
877
878 pub fn timeout(mut self, timeout: Duration) -> Self {
880 self.timeout = Some(timeout);
881 self
882 }
883
884 pub(crate) fn to_json(&self) -> Result<String> {
885 let mut o = Object::default();
886 if let Some(v) = self.conformance {
887 o.str("conformance", v.name());
888 }
889 timeout(&mut o, self.timeout)?;
890 Ok(o.finish())
891 }
892}
893
894#[derive(Debug, Clone, Default)]
896pub struct MergeOptions {
897 title: Option<String>,
898 author: Option<String>,
899 bookmarks: Option<bool>,
900 creation_date: Option<SystemTime>,
901 timeout: Option<Duration>,
902}
903
904impl MergeOptions {
905 pub fn new() -> Self {
907 Self::default()
908 }
909
910 pub fn title(mut self, title: impl Into<String>) -> Self {
912 self.title = Some(title.into());
913 self
914 }
915
916 pub fn author(mut self, author: impl Into<String>) -> Self {
918 self.author = Some(author.into());
919 self
920 }
921
922 pub fn bookmarks(mut self, bookmarks: bool) -> Self {
924 self.bookmarks = Some(bookmarks);
925 self
926 }
927
928 pub fn creation_date(mut self, date: SystemTime) -> Self {
930 self.creation_date = Some(date);
931 self
932 }
933
934 pub fn timeout(mut self, timeout: Duration) -> Self {
936 self.timeout = Some(timeout);
937 self
938 }
939
940 pub(crate) fn to_json(&self) -> Result<String> {
941 let mut o = Object::default();
942 if let Some(v) = &self.title {
943 o.str("title", v);
944 }
945 if let Some(v) = &self.author {
946 o.str("author", v);
947 }
948 if let Some(v) = self.bookmarks {
949 o.bool("bookmarks", v);
950 }
951 if let Some(v) = self.creation_date {
952 o.int("creation_date", unix_seconds(v));
953 }
954 timeout(&mut o, self.timeout)?;
955 Ok(o.finish())
956 }
957}
958
959#[derive(Debug, Clone, Default)]
963pub struct RedactOptions {
964 text: Option<String>,
965 pattern: Option<String>,
966 areas: Vec<[f64; 5]>,
967 ignore_case: Option<bool>,
968 fill_color: Option<String>,
969 timeout: Option<Duration>,
970}
971
972impl RedactOptions {
973 pub fn new() -> Self {
975 Self::default()
976 }
977
978 pub fn text(mut self, text: impl Into<String>) -> Self {
980 self.text = Some(text.into());
981 self
982 }
983
984 pub fn pattern(mut self, pattern: impl Into<String>) -> Self {
987 self.pattern = Some(pattern.into());
988 self
989 }
990
991 pub fn area(mut self, page: u32, x: f64, y: f64, width: f64, height: f64) -> Self {
993 self.areas.push([f64::from(page), x, y, width, height]);
994 self
995 }
996
997 pub fn ignore_case(mut self, ignore_case: bool) -> Self {
999 self.ignore_case = Some(ignore_case);
1000 self
1001 }
1002
1003 pub fn fill_color(mut self, color: impl Into<String>) -> Self {
1006 self.fill_color = Some(color.into());
1007 self
1008 }
1009
1010 pub fn timeout(mut self, timeout: Duration) -> Self {
1012 self.timeout = Some(timeout);
1013 self
1014 }
1015
1016 pub(crate) fn to_json(&self) -> Result<String> {
1017 let mut o = Object::default();
1018 if let Some(v) = &self.text {
1019 o.str("text", v);
1020 }
1021 if let Some(v) = &self.pattern {
1022 o.str("pattern", v);
1023 }
1024 if !self.areas.is_empty() {
1025 o.num_lists("areas", &self.areas)?;
1026 }
1027 if let Some(v) = self.ignore_case {
1028 o.bool("ignore_case", v);
1029 }
1030 if let Some(v) = &self.fill_color {
1031 o.str("fill_color", v);
1032 }
1033 timeout(&mut o, self.timeout)?;
1034 Ok(o.finish())
1035 }
1036}
1037
1038#[derive(Debug, Clone, Default)]
1040pub struct BarcodeOptions {
1041 formats: Vec<BarcodeFormat>,
1042 try_inverted: Option<bool>,
1043 pages: Option<Pages>,
1044 password: Option<String>,
1045 dpi: Option<f64>,
1046 timeout: Option<Duration>,
1047}
1048
1049impl BarcodeOptions {
1050 pub fn new() -> Self {
1052 Self::default()
1053 }
1054
1055 pub fn formats(mut self, formats: impl IntoIterator<Item = BarcodeFormat>) -> Self {
1057 self.formats = formats.into_iter().collect();
1058 self
1059 }
1060
1061 pub fn try_inverted(mut self, try_inverted: bool) -> Self {
1063 self.try_inverted = Some(try_inverted);
1064 self
1065 }
1066
1067 pub fn pages(mut self, pages: impl Into<Pages>) -> Self {
1069 self.pages = Some(pages.into());
1070 self
1071 }
1072
1073 pub fn password(mut self, password: impl Into<String>) -> Self {
1075 self.password = Some(password.into());
1076 self
1077 }
1078
1079 pub fn dpi(mut self, dpi: f64) -> Self {
1081 self.dpi = Some(dpi);
1082 self
1083 }
1084
1085 pub fn timeout(mut self, timeout: Duration) -> Self {
1087 self.timeout = Some(timeout);
1088 self
1089 }
1090
1091 pub(crate) fn to_json(&self) -> Result<String> {
1092 let mut o = Object::default();
1093 if !self.formats.is_empty() {
1094 let names: Vec<String> = self.formats.iter().map(|f| f.name().to_string()).collect();
1095 o.strs("formats", &names);
1096 }
1097 if let Some(v) = self.try_inverted {
1098 o.bool("try_inverted", v);
1099 }
1100 if let Some(v) = &self.pages {
1101 o.str("pages", &v.0);
1102 }
1103 if let Some(v) = &self.password {
1104 o.str("password", v);
1105 }
1106 if let Some(v) = self.dpi {
1107 o.num("dpi", v)?;
1108 }
1109 timeout(&mut o, self.timeout)?;
1110 Ok(o.finish())
1111 }
1112}
1113
1114#[cfg(test)]
1115mod tests {
1116 use super::*;
1117
1118 #[test]
1119 fn option_names_are_the_native_ones() {
1120 let json = PdfOptions::new()
1121 .page_size(PageSize::Millimeters {
1122 width: 100.0,
1123 height: 50.0,
1124 })
1125 .margin(Margin::new(1.0, 2.0, 3.0, 4.0))
1126 .footer_text("Page {page}")
1127 .creation_date(UNIX_EPOCH + Duration::from_secs(5))
1128 .conformance(Conformance::PdfA3u)
1129 .timeout(Duration::from_millis(1500))
1130 .to_json()
1131 .unwrap();
1132 assert_eq!(
1133 json,
1134 r#"{"page_size":[100,50],"margin":[1,2,3,4],"footer_text":"Page {page}","creation_date":5,"conformance":"PDF/A-3u","timeout":1.5}"#
1135 );
1136 assert_eq!(PdfOptions::new().to_json().unwrap(), "{}");
1137 assert_eq!(
1138 ImageOptions::new().pages([1, 3]).to_json().unwrap(),
1139 r#"{"pages":"1,3"}"#
1140 );
1141 assert_eq!(
1142 TextOptions::new().pages(2..=4).to_json().unwrap(),
1143 r#"{"pages":"2-4"}"#
1144 );
1145 assert_eq!(
1146 TextOptions::new()
1147 .order(TextOrder::TopToBottom)
1148 .to_json()
1149 .unwrap(),
1150 r#"{"order":"top_to_bottom"}"#
1151 );
1152 assert_eq!(
1153 RedactOptions::new()
1154 .text("secret")
1155 .area(2, 10.0, 20.5, 30.0, 40.0)
1156 .fill_color("transparent")
1157 .to_json()
1158 .unwrap(),
1159 r#"{"text":"secret","areas":[[2,10,20.5,30,40]],"fill_color":"transparent"}"#
1160 );
1161 assert_eq!(
1162 BarcodeOptions::new()
1163 .formats([BarcodeFormat::QrCode, BarcodeFormat::Ean13])
1164 .try_inverted(false)
1165 .to_json()
1166 .unwrap(),
1167 r#"{"formats":["qr_code","ean13"],"try_inverted":false}"#
1168 );
1169 assert_eq!(unix_seconds(UNIX_EPOCH - Duration::from_millis(1500)), -2);
1170 }
1171}