use printpdf::*;
use std::fs::File;
use std::io::BufWriter;
pub struct SimplePdfWriter {
doc: PdfDocumentReference,
page: PdfPageIndex,
layer: PdfLayerIndex,
font: IndirectFontRef,
page_w_mm: f32,
page_h_mm: f32,
margin_l_mm: f32,
margin_r_mm: f32,
margin_t_mm: f32,
margin_b_mm: f32,
font_pt: f32,
leading_mm: f32,
cursor_y_mm: f32,
page_no: usize,
title: String,
}
impl SimplePdfWriter {
pub fn new<T: Into<String>>(
title: T,
font_pt: f32,
margins_mm: (f32, f32, f32, f32),
) -> Result<Self, String> {
let title = title.into();
let (doc, page, layer) = PdfDocument::new(&title, Mm(210.0_f32), Mm(297.0_f32), "Layer 1");
let font = doc
.add_builtin_font(BuiltinFont::Courier)
.map_err(|e| format!("Font error: {e}"))?;
let pt_to_mm: f32 = 0.35277778;
let leading_mm = font_pt * 1.25 * pt_to_mm;
let mut s = Self {
doc,
page,
layer,
font,
page_w_mm: 210.0,
page_h_mm: 297.0,
margin_l_mm: margins_mm.0,
margin_r_mm: margins_mm.1,
margin_t_mm: margins_mm.2,
margin_b_mm: margins_mm.3,
font_pt,
leading_mm,
cursor_y_mm: 0.0, page_no: 1,
title,
};
s.reset_cursor_to_top();
s.draw_header_on_first_page();
s.draw_footer(); Ok(s)
}
fn reset_cursor_to_top(&mut self) {
self.cursor_y_mm = self.page_h_mm - self.margin_t_mm;
}
fn usable_width_mm(&self) -> f32 {
self.page_w_mm - self.margin_l_mm - self.margin_r_mm
}
fn approx_cols_per_line(&self) -> usize {
let pt_to_mm: f32 = 0.35277778;
let char_w_mm = self.font_pt * 0.6 * pt_to_mm;
let cols = (self.usable_width_mm() / char_w_mm).floor();
cols.max(10.0) as usize
}
fn needs_page_break(&self) -> bool {
self.cursor_y_mm <= self.margin_b_mm + self.leading_mm
}
fn new_page(&mut self) {
let (page, layer) = self
.doc
.add_page(Mm(self.page_w_mm), Mm(self.page_h_mm), "Layer");
self.page = page;
self.layer = layer;
self.page_no += 1;
self.reset_cursor_to_top();
self.draw_footer();
}
fn draw_footer(&self) {
let y = Mm(self.margin_b_mm * 0.6);
let x = Mm(self.margin_l_mm);
let layer = self.doc.get_page(self.page).get_layer(self.layer);
layer.use_text(
format!("Page {}", self.page_no),
(self.font_pt - 1.0).max(8.0),
x,
y,
&self.font,
);
}
fn draw_header_on_first_page(&mut self) {
let layer = self.doc.get_page(self.page).get_layer(self.layer);
layer.use_text(
&self.title,
self.font_pt + 3.0,
Mm(self.margin_l_mm),
Mm(self.cursor_y_mm),
&self.font,
);
self.cursor_y_mm -= self.leading_mm * 1.5;
}
fn write_line(&mut self, text: &str) {
if self.needs_page_break() {
self.new_page();
}
let layer = self.doc.get_page(self.page).get_layer(self.layer);
layer.use_text(
text,
self.font_pt,
Mm(self.margin_l_mm),
Mm(self.cursor_y_mm),
&self.font,
);
self.cursor_y_mm -= self.leading_mm;
}
pub fn write_wrapped(&mut self, text: &str) {
let cols = self.approx_cols_per_line();
for para in text.split('\n') {
if para.trim().is_empty() {
self.write_line(""); continue;
}
let mut line = String::new();
for word in para.split_whitespace() {
if line.is_empty() {
line.push_str(word);
} else if line.len() + 1 + word.len() <= cols {
line.push(' ');
line.push_str(word);
} else {
self.write_line(&line);
line.clear();
line.push_str(word);
}
}
if !line.is_empty() {
self.write_line(&line);
}
}
}
pub fn save(self, path: &str) -> Result<(), String> {
let mut out =
BufWriter::new(File::create(path).map_err(|e| format!("Create PDF failed: {e}"))?);
self.doc
.save(&mut out)
.map_err(|e| format!("Save failed: {e}"))
}
}