use crate::elements::Element;
use crate::pdf_generator::{PageLayout, create_pdf_from_elements_with_layout};
use anyhow::Result;
pub struct PdfBuilder {
elements: Vec<Element>,
layout: PageLayout,
font: String,
font_size: f32,
}
impl PdfBuilder {
pub fn new() -> Self {
Self {
elements: Vec::new(),
layout: PageLayout::portrait(),
font: "Helvetica".to_string(),
font_size: 12.0,
}
}
pub fn with_layout(mut self, layout: PageLayout) -> Self {
self.layout = layout;
self
}
pub fn with_margins(mut self, margin: f32) -> Self {
self.layout = PageLayout {
margin_left: margin,
margin_right: margin,
margin_top: margin,
margin_bottom: margin,
..self.layout
};
self
}
pub fn with_custom_margins(mut self, left: f32, right: f32, top: f32, bottom: f32) -> Self {
self.layout = PageLayout {
margin_left: left,
margin_right: right,
margin_top: top,
margin_bottom: bottom,
..self.layout
};
self
}
pub fn with_font(mut self, font: &str) -> Self {
self.font = font.to_string();
self
}
pub fn with_font_size(mut self, size: f32) -> Self {
self.font_size = size;
self
}
pub fn add_heading(mut self, text: &str, level: u8) -> Self {
self.elements.push(Element::Heading {
text: text.to_string(),
level,
});
self
}
pub fn add_paragraph(mut self, text: &str) -> Self {
self.elements.push(Element::Paragraph {
text: text.to_string(),
});
self
}
pub fn add_code_block(mut self, code: &str, language: &str) -> Self {
self.elements.push(Element::CodeBlock {
code: code.to_string(),
language: language.to_string(),
});
self
}
pub fn add_list_item(mut self, text: &str, depth: u8) -> Self {
self.elements.push(Element::UnorderedListItem {
text: text.to_string(),
depth,
});
self
}
pub fn add_ordered_item(mut self, number: usize, text: &str, depth: u8) -> Self {
self.elements.push(Element::OrderedListItem {
number: number as u32,
text: text.to_string(),
depth,
});
self
}
pub fn add_task_item(mut self, text: &str, checked: bool) -> Self {
self.elements.push(Element::TaskListItem {
text: text.to_string(),
checked,
});
self
}
pub fn add_table_row(mut self, cells: &[&str]) -> Self {
self.elements.push(Element::TableRow {
cells: cells.iter().map(|s| s.to_string()).collect(),
is_separator: false,
alignments: vec![],
});
self
}
pub fn add_table_separator(mut self, cells: &[&str]) -> Self {
self.elements.push(Element::TableRow {
cells: cells.iter().map(|s| s.to_string()).collect(),
is_separator: true,
alignments: vec![],
});
self
}
pub fn add_horizontal_rule(mut self) -> Self {
self.elements.push(Element::HorizontalRule);
self
}
pub fn add_page_break(mut self) -> Self {
self.elements.push(Element::PageBreak);
self
}
pub fn add_spacing(mut self) -> Self {
self.elements.push(Element::EmptyLine);
self
}
pub fn add_blockquote(mut self, text: &str, depth: u8) -> Self {
self.elements.push(Element::BlockQuote {
text: text.to_string(),
depth,
});
self
}
pub fn add_link(mut self, text: &str, url: &str) -> Self {
self.elements.push(Element::Link {
text: text.to_string(),
url: url.to_string(),
});
self
}
pub fn add_image(mut self, alt: &str, path: &str) -> Self {
self.elements.push(Element::Image {
alt: alt.to_string(),
path: path.to_string(),
});
self
}
pub fn add_definition(mut self, term: &str, definition: &str) -> Self {
self.elements.push(Element::DefinitionItem {
term: term.to_string(),
definition: definition.to_string(),
});
self
}
pub fn add_footnote(mut self, label: &str, text: &str) -> Self {
self.elements.push(Element::Footnote {
label: label.to_string(),
text: text.to_string(),
});
self
}
pub fn add_styled_text(mut self, text: &str, bold: bool, italic: bool) -> Self {
self.elements.push(Element::StyledText {
text: text.to_string(),
bold,
italic,
});
self
}
pub fn add_inline_code(mut self, code: &str) -> Self {
self.elements.push(Element::InlineCode {
code: code.to_string(),
});
self
}
pub fn add_math_block(mut self, expression: &str) -> Self {
self.elements.push(Element::MathBlock {
expression: expression.to_string(),
});
self
}
pub fn add_inline_math(mut self, expression: &str) -> Self {
self.elements.push(Element::MathInline {
expression: expression.to_string(),
});
self
}
pub fn add_elements(mut self, elements: impl IntoIterator<Item = Element>) -> Self {
for elem in elements {
self.elements.push(elem);
}
self
}
pub fn add_element(mut self, element: Element) -> Self {
self.elements.push(element);
self
}
pub fn build(self, filename: &str) -> Result<()> {
create_pdf_from_elements_with_layout(
filename,
&self.elements,
&self.font,
self.font_size,
self.layout,
)
}
pub fn build_bytes(self) -> Result<Vec<u8>> {
crate::pdf_generator::generate_pdf_bytes(
&self.elements,
&self.font,
self.font_size,
self.layout,
)
}
pub fn element_count(&self) -> usize {
self.elements.len()
}
pub fn clear(mut self) -> Self {
self.elements.clear();
self
}
}
impl Default for PdfBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_basic() {
let pdf = PdfBuilder::new()
.add_heading("Test Document", 1)
.add_paragraph("This is a test.")
.add_code_block("let x = 42;", "rust")
.build_bytes();
assert!(pdf.is_ok());
let pdf_bytes = pdf.unwrap();
assert!(pdf_bytes.len() > 100);
assert!(pdf_bytes.starts_with(b"%PDF"));
}
#[test]
fn test_builder_with_layout() {
let pdf = PdfBuilder::new()
.with_layout(PageLayout::landscape())
.with_margins(50.0)
.add_heading("Landscape", 1)
.build_bytes();
assert!(pdf.is_ok());
}
#[test]
fn test_builder_count() {
let builder = PdfBuilder::new()
.add_heading("Title", 1)
.add_paragraph("Content")
.add_spacing();
assert_eq!(builder.element_count(), 3);
}
#[test]
fn test_builder_clear() {
let builder = PdfBuilder::new()
.add_heading("Title", 1)
.add_paragraph("Content")
.clear();
assert_eq!(builder.element_count(), 0);
}
#[test]
fn test_builder_table() {
let pdf = PdfBuilder::new()
.add_table_row(&["Name", "Age", "City"])
.add_table_separator(&["----", "----", "----"])
.add_table_row(&["Alice", "30", "NYC"])
.add_table_row(&["Bob", "25", "LA"])
.build_bytes();
assert!(pdf.is_ok());
}
}