1use crate::style::Style;
13use serde::{Deserialize, Deserializer, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct Document {
19 pub children: Vec<Node>,
22
23 #[serde(default)]
25 pub metadata: Metadata,
26
27 #[serde(default)]
30 pub default_page: PageConfig,
31
32 #[serde(default)]
35 pub fonts: Vec<FontEntry>,
36
37 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub default_style: Option<crate::style::Style>,
41
42 #[serde(default)]
44 pub tagged: bool,
45
46 #[serde(default)]
48 pub pdfa: Option<PdfAConformance>,
49
50 #[serde(default)]
52 pub pdf_ua: bool,
53
54 #[serde(default, skip_serializing_if = "Option::is_none")]
57 pub embedded_data: Option<String>,
58
59 #[serde(default)]
63 pub flatten_forms: bool,
64
65 #[serde(default, skip_serializing_if = "Option::is_none")]
68 pub signature: Option<SignatureConfig>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub enum PdfAConformance {
74 #[serde(rename = "2a")]
76 A2a,
77 #[serde(rename = "2b")]
79 A2b,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct SignatureConfig {
86 pub certificate_pem: String,
88 pub private_key_pem: String,
90 #[serde(default)]
92 pub reason: Option<String>,
93 #[serde(default)]
95 pub location: Option<String>,
96 #[serde(default)]
98 pub contact: Option<String>,
99 #[serde(default)]
101 pub visible: bool,
102 #[serde(default)]
104 pub x: Option<f64>,
105 #[serde(default)]
107 pub y: Option<f64>,
108 #[serde(default)]
110 pub width: Option<f64>,
111 #[serde(default)]
113 pub height: Option<f64>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct FontEntry {
119 pub family: String,
121 pub src: String,
123 #[serde(default = "default_weight")]
125 pub weight: u32,
126 #[serde(default)]
128 pub italic: bool,
129}
130
131fn default_weight() -> u32 {
132 400
133}
134
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
137pub struct Metadata {
138 pub title: Option<String>,
139 pub author: Option<String>,
140 pub subject: Option<String>,
141 pub creator: Option<String>,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub lang: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct PageConfig {
150 #[serde(default = "PageSize::default")]
152 pub size: PageSize,
153
154 #[serde(default)]
156 pub margin: Edges,
157
158 #[serde(default = "default_true")]
160 pub wrap: bool,
161}
162
163impl Default for PageConfig {
164 fn default() -> Self {
165 Self {
166 size: PageSize::A4,
167 margin: Edges::uniform(54.0), wrap: true,
169 }
170 }
171}
172
173fn default_true() -> bool {
174 true
175}
176
177#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
179pub enum PageSize {
180 #[default]
181 A4,
182 A3,
183 A5,
184 Letter,
185 Legal,
186 Tabloid,
187 Custom {
188 width: f64,
189 height: f64,
190 },
191}
192
193impl PageSize {
194 pub fn dimensions(&self) -> (f64, f64) {
196 match self {
197 PageSize::A4 => (595.28, 841.89),
198 PageSize::A3 => (841.89, 1190.55),
199 PageSize::A5 => (419.53, 595.28),
200 PageSize::Letter => (612.0, 792.0),
201 PageSize::Legal => (612.0, 1008.0),
202 PageSize::Tabloid => (792.0, 1224.0),
203 PageSize::Custom { width, height } => (*width, *height),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
210pub struct Edges {
211 pub top: f64,
212 pub right: f64,
213 pub bottom: f64,
214 pub left: f64,
215}
216
217#[derive(Debug, Clone, Copy, Serialize)]
219pub enum EdgeValue {
220 Pt(f64),
221 Auto,
222}
223
224impl Default for EdgeValue {
225 fn default() -> Self {
226 EdgeValue::Pt(0.0)
227 }
228}
229
230impl EdgeValue {
231 pub fn resolve(&self) -> f64 {
233 match self {
234 EdgeValue::Pt(v) => *v,
235 EdgeValue::Auto => 0.0,
236 }
237 }
238
239 pub fn is_auto(&self) -> bool {
241 matches!(self, EdgeValue::Auto)
242 }
243}
244
245impl<'de> Deserialize<'de> for EdgeValue {
246 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
247 where
248 D: Deserializer<'de>,
249 {
250 use serde::de;
251
252 struct EdgeValueVisitor;
253
254 impl<'de> de::Visitor<'de> for EdgeValueVisitor {
255 type Value = EdgeValue;
256
257 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
258 f.write_str("a number or the string \"auto\"")
259 }
260
261 fn visit_f64<E: de::Error>(self, v: f64) -> Result<EdgeValue, E> {
262 Ok(EdgeValue::Pt(v))
263 }
264
265 fn visit_i64<E: de::Error>(self, v: i64) -> Result<EdgeValue, E> {
266 Ok(EdgeValue::Pt(v as f64))
267 }
268
269 fn visit_u64<E: de::Error>(self, v: u64) -> Result<EdgeValue, E> {
270 Ok(EdgeValue::Pt(v as f64))
271 }
272
273 fn visit_str<E: de::Error>(self, v: &str) -> Result<EdgeValue, E> {
274 if v == "auto" {
275 Ok(EdgeValue::Auto)
276 } else {
277 Err(de::Error::invalid_value(de::Unexpected::Str(v), &self))
278 }
279 }
280 }
281
282 deserializer.deserialize_any(EdgeValueVisitor)
283 }
284}
285
286#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
288pub struct MarginEdges {
289 pub top: EdgeValue,
290 pub right: EdgeValue,
291 pub bottom: EdgeValue,
292 pub left: EdgeValue,
293}
294
295impl MarginEdges {
296 pub fn horizontal(&self) -> f64 {
298 self.left.resolve() + self.right.resolve()
299 }
300
301 pub fn vertical(&self) -> f64 {
303 self.top.resolve() + self.bottom.resolve()
304 }
305
306 pub fn has_auto_horizontal(&self) -> bool {
308 self.left.is_auto() || self.right.is_auto()
309 }
310
311 pub fn has_auto_vertical(&self) -> bool {
313 self.top.is_auto() || self.bottom.is_auto()
314 }
315
316 pub fn from_edges(e: Edges) -> Self {
318 MarginEdges {
319 top: EdgeValue::Pt(e.top),
320 right: EdgeValue::Pt(e.right),
321 bottom: EdgeValue::Pt(e.bottom),
322 left: EdgeValue::Pt(e.left),
323 }
324 }
325
326 pub fn to_edges(&self) -> Edges {
328 Edges {
329 top: self.top.resolve(),
330 right: self.right.resolve(),
331 bottom: self.bottom.resolve(),
332 left: self.left.resolve(),
333 }
334 }
335}
336
337impl Edges {
338 pub fn uniform(v: f64) -> Self {
339 Self {
340 top: v,
341 right: v,
342 bottom: v,
343 left: v,
344 }
345 }
346
347 pub fn symmetric(vertical: f64, horizontal: f64) -> Self {
348 Self {
349 top: vertical,
350 right: horizontal,
351 bottom: vertical,
352 left: horizontal,
353 }
354 }
355
356 pub fn horizontal(&self) -> f64 {
357 self.left + self.right
358 }
359
360 pub fn vertical(&self) -> f64 {
361 self.top + self.bottom
362 }
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
367#[serde(rename_all = "camelCase")]
368pub struct Node {
369 pub kind: NodeKind,
371
372 #[serde(default)]
374 pub style: Style,
375
376 #[serde(default)]
378 pub children: Vec<Node>,
379
380 #[serde(default)]
382 pub id: Option<String>,
383
384 #[serde(default, skip_serializing_if = "Option::is_none")]
386 pub source_location: Option<SourceLocation>,
387
388 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub bookmark: Option<String>,
391
392 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub href: Option<String>,
395
396 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub alt: Option<String>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403#[serde(tag = "type")]
404pub enum NodeKind {
405 Page {
407 #[serde(default)]
408 config: PageConfig,
409 },
410
411 View,
413
414 Text {
416 content: String,
417 #[serde(default, skip_serializing_if = "Option::is_none")]
419 href: Option<String>,
420 #[serde(default, skip_serializing_if = "Vec::is_empty")]
422 runs: Vec<TextRun>,
423 },
424
425 Image {
427 src: String,
429 width: Option<f64>,
431 height: Option<f64>,
433 },
434
435 Table {
437 #[serde(default)]
439 columns: Vec<ColumnDef>,
440 },
441
442 TableRow {
444 #[serde(default)]
447 is_header: bool,
448 },
449
450 TableCell {
452 #[serde(default = "default_one")]
454 col_span: u32,
455 #[serde(default = "default_one")]
457 row_span: u32,
458 },
459
460 Fixed {
462 position: FixedPosition,
464 },
465
466 PageBreak,
468
469 Svg {
471 width: f64,
473 height: f64,
475 #[serde(default, skip_serializing_if = "Option::is_none")]
477 view_box: Option<String>,
478 content: String,
480 },
481
482 Canvas {
484 width: f64,
486 height: f64,
488 operations: Vec<CanvasOp>,
490 },
491
492 Barcode {
494 data: String,
496 #[serde(default)]
498 format: crate::barcode::BarcodeFormat,
499 #[serde(default, skip_serializing_if = "Option::is_none")]
501 width: Option<f64>,
502 #[serde(default = "default_barcode_height")]
504 height: f64,
505 },
506
507 QrCode {
509 data: String,
511 #[serde(default, skip_serializing_if = "Option::is_none")]
514 size: Option<f64>,
515 },
516
517 BarChart {
519 data: Vec<ChartDataPoint>,
521 width: f64,
523 height: f64,
525 #[serde(default, skip_serializing_if = "Option::is_none")]
527 color: Option<String>,
528 #[serde(default = "default_true")]
530 show_labels: bool,
531 #[serde(default)]
533 show_values: bool,
534 #[serde(default)]
536 show_grid: bool,
537 #[serde(default, skip_serializing_if = "Option::is_none")]
539 title: Option<String>,
540 },
541
542 LineChart {
544 series: Vec<ChartSeries>,
546 labels: Vec<String>,
548 width: f64,
550 height: f64,
552 #[serde(default)]
554 show_points: bool,
555 #[serde(default)]
557 show_grid: bool,
558 #[serde(default, skip_serializing_if = "Option::is_none")]
560 title: Option<String>,
561 },
562
563 PieChart {
565 data: Vec<ChartDataPoint>,
567 width: f64,
569 height: f64,
571 #[serde(default)]
573 donut: bool,
574 #[serde(default)]
576 show_legend: bool,
577 #[serde(default, skip_serializing_if = "Option::is_none")]
579 title: Option<String>,
580 },
581
582 AreaChart {
584 series: Vec<ChartSeries>,
586 labels: Vec<String>,
588 width: f64,
590 height: f64,
592 #[serde(default)]
594 show_grid: bool,
595 #[serde(default, skip_serializing_if = "Option::is_none")]
597 title: Option<String>,
598 },
599
600 DotPlot {
602 groups: Vec<DotPlotGroup>,
604 width: f64,
606 height: f64,
608 #[serde(default, skip_serializing_if = "Option::is_none")]
610 x_min: Option<f64>,
611 #[serde(default, skip_serializing_if = "Option::is_none")]
613 x_max: Option<f64>,
614 #[serde(default, skip_serializing_if = "Option::is_none")]
616 y_min: Option<f64>,
617 #[serde(default, skip_serializing_if = "Option::is_none")]
619 y_max: Option<f64>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
622 x_label: Option<String>,
623 #[serde(default, skip_serializing_if = "Option::is_none")]
625 y_label: Option<String>,
626 #[serde(default)]
628 show_legend: bool,
629 #[serde(default = "default_dot_size")]
631 dot_size: f64,
632 },
633
634 Watermark {
636 text: String,
638 #[serde(default = "default_watermark_font_size")]
640 font_size: f64,
641 #[serde(default = "default_watermark_angle")]
643 angle: f64,
644 },
645
646 TextField {
648 name: String,
650 #[serde(default, skip_serializing_if = "Option::is_none")]
652 value: Option<String>,
653 #[serde(default, skip_serializing_if = "Option::is_none")]
655 placeholder: Option<String>,
656 width: f64,
658 #[serde(default = "default_form_field_height")]
660 height: f64,
661 #[serde(default)]
663 multiline: bool,
664 #[serde(default)]
666 password: bool,
667 #[serde(default)]
669 read_only: bool,
670 #[serde(default, skip_serializing_if = "Option::is_none")]
672 max_length: Option<u32>,
673 #[serde(default = "default_form_font_size")]
675 font_size: f64,
676 },
677
678 Checkbox {
680 name: String,
682 #[serde(default)]
684 checked: bool,
685 #[serde(default = "default_checkbox_size")]
687 width: f64,
688 #[serde(default = "default_checkbox_size")]
690 height: f64,
691 #[serde(default)]
693 read_only: bool,
694 },
695
696 Dropdown {
698 name: String,
700 options: Vec<String>,
702 #[serde(default, skip_serializing_if = "Option::is_none")]
704 value: Option<String>,
705 width: f64,
707 #[serde(default = "default_form_field_height")]
709 height: f64,
710 #[serde(default)]
712 read_only: bool,
713 #[serde(default = "default_form_font_size")]
715 font_size: f64,
716 },
717
718 RadioButton {
721 name: String,
723 value: String,
725 #[serde(default)]
727 checked: bool,
728 #[serde(default = "default_checkbox_size")]
730 width: f64,
731 #[serde(default = "default_checkbox_size")]
733 height: f64,
734 #[serde(default)]
736 read_only: bool,
737 },
738}
739
740#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct ChartDataPoint {
743 pub label: String,
744 pub value: f64,
745 #[serde(default, skip_serializing_if = "Option::is_none")]
746 pub color: Option<String>,
747}
748
749#[derive(Debug, Clone, Serialize, Deserialize)]
751pub struct ChartSeries {
752 pub name: String,
753 pub data: Vec<f64>,
754 #[serde(default, skip_serializing_if = "Option::is_none")]
755 pub color: Option<String>,
756}
757
758#[derive(Debug, Clone, Serialize, Deserialize)]
760pub struct DotPlotGroup {
761 pub name: String,
762 #[serde(default, skip_serializing_if = "Option::is_none")]
763 pub color: Option<String>,
764 pub data: Vec<(f64, f64)>,
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
769#[serde(tag = "op")]
770pub enum CanvasOp {
771 MoveTo {
772 x: f64,
773 y: f64,
774 },
775 LineTo {
776 x: f64,
777 y: f64,
778 },
779 BezierCurveTo {
780 cp1x: f64,
781 cp1y: f64,
782 cp2x: f64,
783 cp2y: f64,
784 x: f64,
785 y: f64,
786 },
787 QuadraticCurveTo {
788 cpx: f64,
789 cpy: f64,
790 x: f64,
791 y: f64,
792 },
793 ClosePath,
794 Rect {
795 x: f64,
796 y: f64,
797 width: f64,
798 height: f64,
799 },
800 Circle {
801 cx: f64,
802 cy: f64,
803 r: f64,
804 },
805 Ellipse {
806 cx: f64,
807 cy: f64,
808 rx: f64,
809 ry: f64,
810 },
811 Arc {
812 cx: f64,
813 cy: f64,
814 r: f64,
815 start_angle: f64,
816 end_angle: f64,
817 #[serde(default)]
818 counterclockwise: bool,
819 },
820 Stroke,
821 Fill,
822 FillAndStroke,
823 SetFillColor {
824 r: f64,
825 g: f64,
826 b: f64,
827 },
828 SetStrokeColor {
829 r: f64,
830 g: f64,
831 b: f64,
832 },
833 SetLineWidth {
834 width: f64,
835 },
836 SetLineCap {
837 cap: u32,
838 },
839 SetLineJoin {
840 join: u32,
841 },
842 Save,
843 Restore,
844}
845
846#[derive(Debug, Clone, Serialize, Deserialize)]
848#[serde(rename_all = "camelCase")]
849pub struct TextRun {
850 pub content: String,
851 #[serde(default)]
852 pub style: crate::style::Style,
853 #[serde(default, skip_serializing_if = "Option::is_none")]
854 pub href: Option<String>,
855}
856
857#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
859pub enum Position {
860 #[default]
861 Relative,
862 Absolute,
863}
864
865fn default_one() -> u32 {
866 1
867}
868
869fn default_barcode_height() -> f64 {
870 60.0
871}
872
873fn default_dot_size() -> f64 {
874 4.0
875}
876
877fn default_watermark_font_size() -> f64 {
878 60.0
879}
880
881fn default_watermark_angle() -> f64 {
882 -45.0
883}
884
885fn default_form_field_height() -> f64 {
886 24.0
887}
888
889fn default_form_font_size() -> f64 {
890 12.0
891}
892
893fn default_checkbox_size() -> f64 {
894 14.0
895}
896
897#[derive(Debug, Clone, Serialize, Deserialize)]
899pub struct ColumnDef {
900 pub width: ColumnWidth,
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub enum ColumnWidth {
906 Fraction(f64),
908 Fixed(f64),
910 Auto,
912}
913
914#[derive(Debug, Clone, Serialize, Deserialize)]
916pub enum FixedPosition {
917 Header,
919 Footer,
921}
922
923#[derive(Debug, Clone, Serialize, Deserialize)]
925#[serde(rename_all = "camelCase")]
926pub struct SourceLocation {
927 pub file: String,
928 pub line: u32,
929 pub column: u32,
930}
931
932impl Node {
933 pub fn view(style: Style, children: Vec<Node>) -> Self {
935 Self {
936 kind: NodeKind::View,
937 style,
938 children,
939 id: None,
940 source_location: None,
941 bookmark: None,
942 href: None,
943 alt: None,
944 }
945 }
946
947 pub fn text(content: &str, style: Style) -> Self {
949 Self {
950 kind: NodeKind::Text {
951 content: content.to_string(),
952 href: None,
953 runs: vec![],
954 },
955 style,
956 children: vec![],
957 id: None,
958 source_location: None,
959 bookmark: None,
960 href: None,
961 alt: None,
962 }
963 }
964
965 pub fn page(config: PageConfig, style: Style, children: Vec<Node>) -> Self {
967 Self {
968 kind: NodeKind::Page { config },
969 style,
970 children,
971 id: None,
972 source_location: None,
973 bookmark: None,
974 href: None,
975 alt: None,
976 }
977 }
978
979 pub fn is_breakable(&self) -> bool {
981 match &self.kind {
982 NodeKind::View | NodeKind::Table { .. } | NodeKind::Text { .. } => {
983 self.style.wrap.unwrap_or(true)
984 }
985 NodeKind::TableRow { .. } => true,
986 NodeKind::Image { .. } => false,
987 NodeKind::Svg { .. } => false,
988 NodeKind::Canvas { .. } => false,
989 NodeKind::Barcode { .. } => false,
990 NodeKind::QrCode { .. } => false,
991 NodeKind::BarChart { .. } => false,
992 NodeKind::LineChart { .. } => false,
993 NodeKind::PieChart { .. } => false,
994 NodeKind::AreaChart { .. } => false,
995 NodeKind::DotPlot { .. } => false,
996 NodeKind::Watermark { .. } => false,
997 NodeKind::TextField { .. } => false,
998 NodeKind::Checkbox { .. } => false,
999 NodeKind::Dropdown { .. } => false,
1000 NodeKind::RadioButton { .. } => false,
1001 NodeKind::PageBreak => false,
1002 NodeKind::Fixed { .. } => false,
1003 NodeKind::Page { .. } => true,
1004 NodeKind::TableCell { .. } => true,
1005 }
1006 }
1007}