use crate::style::Style;
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Document {
pub children: Vec<Node>,
#[serde(default)]
pub metadata: Metadata,
#[serde(default)]
pub default_page: PageConfig,
#[serde(default)]
pub fonts: Vec<FontEntry>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub default_style: Option<crate::style::Style>,
#[serde(default)]
pub tagged: bool,
#[serde(default)]
pub pdfa: Option<PdfAConformance>,
#[serde(default)]
pub pdf_ua: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub embedded_data: Option<String>,
#[serde(default)]
pub flatten_forms: bool,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "signature")]
pub certification: Option<CertificationConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PdfAConformance {
#[serde(rename = "2a")]
A2a,
#[serde(rename = "2b")]
A2b,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactionRegion {
pub page: usize,
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
#[serde(default)]
pub color: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PatternType {
Literal,
Regex,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RedactionPattern {
pub pattern: String,
pub pattern_type: PatternType,
#[serde(default)]
pub page: Option<usize>,
#[serde(default)]
pub color: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CertificationConfig {
pub certificate_pem: String,
pub private_key_pem: String,
#[serde(default)]
pub reason: Option<String>,
#[serde(default)]
pub location: Option<String>,
#[serde(default)]
pub contact: Option<String>,
#[serde(default)]
pub visible: bool,
#[serde(default)]
pub x: Option<f64>,
#[serde(default)]
pub y: Option<f64>,
#[serde(default)]
pub width: Option<f64>,
#[serde(default)]
pub height: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FontEntry {
pub family: String,
pub src: String,
#[serde(default = "default_weight")]
pub weight: u32,
#[serde(default)]
pub italic: bool,
}
fn default_weight() -> u32 {
400
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Metadata {
pub title: Option<String>,
pub author: Option<String>,
pub subject: Option<String>,
pub creator: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lang: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PageConfig {
#[serde(default = "PageSize::default")]
pub size: PageSize,
#[serde(default)]
pub margin: Edges,
#[serde(default = "default_true")]
pub wrap: bool,
}
impl Default for PageConfig {
fn default() -> Self {
Self {
size: PageSize::A4,
margin: Edges::uniform(54.0), wrap: true,
}
}
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum PageSize {
#[default]
A4,
A3,
A5,
Letter,
Legal,
Tabloid,
Custom {
width: f64,
height: f64,
},
}
impl PageSize {
pub fn dimensions(&self) -> (f64, f64) {
match self {
PageSize::A4 => (595.28, 841.89),
PageSize::A3 => (841.89, 1190.55),
PageSize::A5 => (419.53, 595.28),
PageSize::Letter => (612.0, 792.0),
PageSize::Legal => (612.0, 1008.0),
PageSize::Tabloid => (792.0, 1224.0),
PageSize::Custom { width, height } => (*width, *height),
}
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct Edges {
pub top: f64,
pub right: f64,
pub bottom: f64,
pub left: f64,
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum EdgeValue {
Pt(f64),
Auto,
}
impl Default for EdgeValue {
fn default() -> Self {
EdgeValue::Pt(0.0)
}
}
impl EdgeValue {
pub fn resolve(&self) -> f64 {
match self {
EdgeValue::Pt(v) => *v,
EdgeValue::Auto => 0.0,
}
}
pub fn is_auto(&self) -> bool {
matches!(self, EdgeValue::Auto)
}
}
impl<'de> Deserialize<'de> for EdgeValue {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
struct EdgeValueVisitor;
impl<'de> de::Visitor<'de> for EdgeValueVisitor {
type Value = EdgeValue;
fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.write_str("a number or the string \"auto\"")
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<EdgeValue, E> {
Ok(EdgeValue::Pt(v))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<EdgeValue, E> {
Ok(EdgeValue::Pt(v as f64))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<EdgeValue, E> {
Ok(EdgeValue::Pt(v as f64))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<EdgeValue, E> {
if v == "auto" {
Ok(EdgeValue::Auto)
} else {
Err(de::Error::invalid_value(de::Unexpected::Str(v), &self))
}
}
}
deserializer.deserialize_any(EdgeValueVisitor)
}
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub struct MarginEdges {
pub top: EdgeValue,
pub right: EdgeValue,
pub bottom: EdgeValue,
pub left: EdgeValue,
}
impl MarginEdges {
pub fn horizontal(&self) -> f64 {
self.left.resolve() + self.right.resolve()
}
pub fn vertical(&self) -> f64 {
self.top.resolve() + self.bottom.resolve()
}
pub fn has_auto_horizontal(&self) -> bool {
self.left.is_auto() || self.right.is_auto()
}
pub fn has_auto_vertical(&self) -> bool {
self.top.is_auto() || self.bottom.is_auto()
}
pub fn from_edges(e: Edges) -> Self {
MarginEdges {
top: EdgeValue::Pt(e.top),
right: EdgeValue::Pt(e.right),
bottom: EdgeValue::Pt(e.bottom),
left: EdgeValue::Pt(e.left),
}
}
pub fn to_edges(&self) -> Edges {
Edges {
top: self.top.resolve(),
right: self.right.resolve(),
bottom: self.bottom.resolve(),
left: self.left.resolve(),
}
}
}
impl Edges {
pub fn uniform(v: f64) -> Self {
Self {
top: v,
right: v,
bottom: v,
left: v,
}
}
pub fn symmetric(vertical: f64, horizontal: f64) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
pub fn horizontal(&self) -> f64 {
self.left + self.right
}
pub fn vertical(&self) -> f64 {
self.top + self.bottom
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Node {
pub kind: NodeKind,
#[serde(default)]
pub style: Style,
#[serde(default)]
pub children: Vec<Node>,
#[serde(default)]
pub id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_location: Option<SourceLocation>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bookmark: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub alt: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum NodeKind {
Page {
#[serde(default)]
config: PageConfig,
},
View,
Text {
content: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
href: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
runs: Vec<TextRun>,
},
Image {
src: String,
width: Option<f64>,
height: Option<f64>,
},
Table {
#[serde(default)]
columns: Vec<ColumnDef>,
},
TableRow {
#[serde(default)]
is_header: bool,
},
TableCell {
#[serde(default = "default_one")]
col_span: u32,
#[serde(default = "default_one")]
row_span: u32,
},
Fixed {
position: FixedPosition,
},
PageBreak,
Svg {
width: f64,
height: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
view_box: Option<String>,
content: String,
},
Canvas {
width: f64,
height: f64,
operations: Vec<CanvasOp>,
},
Barcode {
data: String,
#[serde(default)]
format: crate::barcode::BarcodeFormat,
#[serde(default, skip_serializing_if = "Option::is_none")]
width: Option<f64>,
#[serde(default = "default_barcode_height")]
height: f64,
},
QrCode {
data: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
size: Option<f64>,
},
BarChart {
data: Vec<ChartDataPoint>,
width: f64,
height: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
color: Option<String>,
#[serde(default = "default_true")]
show_labels: bool,
#[serde(default)]
show_values: bool,
#[serde(default)]
show_grid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
},
LineChart {
series: Vec<ChartSeries>,
labels: Vec<String>,
width: f64,
height: f64,
#[serde(default)]
show_points: bool,
#[serde(default)]
show_grid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
},
PieChart {
data: Vec<ChartDataPoint>,
width: f64,
height: f64,
#[serde(default)]
donut: bool,
#[serde(default)]
show_legend: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
},
AreaChart {
series: Vec<ChartSeries>,
labels: Vec<String>,
width: f64,
height: f64,
#[serde(default)]
show_grid: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
title: Option<String>,
},
DotPlot {
groups: Vec<DotPlotGroup>,
width: f64,
height: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
x_min: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
x_max: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
y_min: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
y_max: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
x_label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
y_label: Option<String>,
#[serde(default)]
show_legend: bool,
#[serde(default = "default_dot_size")]
dot_size: f64,
},
Watermark {
text: String,
#[serde(default = "default_watermark_font_size")]
font_size: f64,
#[serde(default = "default_watermark_angle")]
angle: f64,
},
TextField {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
value: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
placeholder: Option<String>,
width: f64,
#[serde(default = "default_form_field_height")]
height: f64,
#[serde(default)]
multiline: bool,
#[serde(default)]
password: bool,
#[serde(default)]
read_only: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
max_length: Option<u32>,
#[serde(default = "default_form_font_size")]
font_size: f64,
},
Checkbox {
name: String,
#[serde(default)]
checked: bool,
#[serde(default = "default_checkbox_size")]
width: f64,
#[serde(default = "default_checkbox_size")]
height: f64,
#[serde(default)]
read_only: bool,
},
Dropdown {
name: String,
options: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
value: Option<String>,
width: f64,
#[serde(default = "default_form_field_height")]
height: f64,
#[serde(default)]
read_only: bool,
#[serde(default = "default_form_font_size")]
font_size: f64,
},
RadioButton {
name: String,
value: String,
#[serde(default)]
checked: bool,
#[serde(default = "default_checkbox_size")]
width: f64,
#[serde(default = "default_checkbox_size")]
height: f64,
#[serde(default)]
read_only: bool,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChartDataPoint {
pub label: String,
pub value: f64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChartSeries {
pub name: String,
pub data: Vec<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DotPlotGroup {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
pub data: Vec<(f64, f64)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op")]
pub enum CanvasOp {
MoveTo {
x: f64,
y: f64,
},
LineTo {
x: f64,
y: f64,
},
BezierCurveTo {
cp1x: f64,
cp1y: f64,
cp2x: f64,
cp2y: f64,
x: f64,
y: f64,
},
QuadraticCurveTo {
cpx: f64,
cpy: f64,
x: f64,
y: f64,
},
ClosePath,
Rect {
x: f64,
y: f64,
width: f64,
height: f64,
},
Circle {
cx: f64,
cy: f64,
r: f64,
},
Ellipse {
cx: f64,
cy: f64,
rx: f64,
ry: f64,
},
Arc {
cx: f64,
cy: f64,
r: f64,
start_angle: f64,
end_angle: f64,
#[serde(default)]
counterclockwise: bool,
},
Stroke,
Fill,
FillAndStroke,
SetFillColor {
r: f64,
g: f64,
b: f64,
},
SetStrokeColor {
r: f64,
g: f64,
b: f64,
},
SetLineWidth {
width: f64,
},
SetLineCap {
cap: u32,
},
SetLineJoin {
join: u32,
},
Save,
Restore,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TextRun {
pub content: String,
#[serde(default)]
pub style: crate::style::Style,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub href: Option<String>,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
pub enum Position {
#[default]
Relative,
Absolute,
}
fn default_one() -> u32 {
1
}
fn default_barcode_height() -> f64 {
60.0
}
fn default_dot_size() -> f64 {
4.0
}
fn default_watermark_font_size() -> f64 {
60.0
}
fn default_watermark_angle() -> f64 {
-45.0
}
fn default_form_field_height() -> f64 {
24.0
}
fn default_form_font_size() -> f64 {
12.0
}
fn default_checkbox_size() -> f64 {
14.0
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnDef {
pub width: ColumnWidth,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ColumnWidth {
Fraction(f64),
Fixed(f64),
Auto,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FixedPosition {
Header,
Footer,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SourceLocation {
pub file: String,
pub line: u32,
pub column: u32,
}
impl Node {
pub fn view(style: Style, children: Vec<Node>) -> Self {
Self {
kind: NodeKind::View,
style,
children,
id: None,
source_location: None,
bookmark: None,
href: None,
alt: None,
}
}
pub fn text(content: &str, style: Style) -> Self {
Self {
kind: NodeKind::Text {
content: content.to_string(),
href: None,
runs: vec![],
},
style,
children: vec![],
id: None,
source_location: None,
bookmark: None,
href: None,
alt: None,
}
}
pub fn page(config: PageConfig, style: Style, children: Vec<Node>) -> Self {
Self {
kind: NodeKind::Page { config },
style,
children,
id: None,
source_location: None,
bookmark: None,
href: None,
alt: None,
}
}
pub fn is_breakable(&self) -> bool {
match &self.kind {
NodeKind::View | NodeKind::Table { .. } | NodeKind::Text { .. } => {
self.style.wrap.unwrap_or(true)
}
NodeKind::TableRow { .. } => true,
NodeKind::Image { .. } => false,
NodeKind::Svg { .. } => false,
NodeKind::Canvas { .. } => false,
NodeKind::Barcode { .. } => false,
NodeKind::QrCode { .. } => false,
NodeKind::BarChart { .. } => false,
NodeKind::LineChart { .. } => false,
NodeKind::PieChart { .. } => false,
NodeKind::AreaChart { .. } => false,
NodeKind::DotPlot { .. } => false,
NodeKind::Watermark { .. } => false,
NodeKind::TextField { .. } => false,
NodeKind::Checkbox { .. } => false,
NodeKind::Dropdown { .. } => false,
NodeKind::RadioButton { .. } => false,
NodeKind::PageBreak => false,
NodeKind::Fixed { .. } => false,
NodeKind::Page { .. } => true,
NodeKind::TableCell { .. } => true,
}
}
}