use std::fs;
use std::io::BufWriter;
use printpdf::{
BuiltinFont, Image, ImageTransform, ImageXObject, Mm, PdfDocument, Px,
};
use crate::converters::{html_typst, typst_fmt};
use crate::{Error, Job, Result};
const PAGE_WIDTH_MM: f32 = 210.0; const PAGE_HEIGHT_MM: f32 = 297.0;
const MARGIN_MM: f32 = 15.0;
const FONT_SIZE_PT: f32 = 10.0;
const LINE_HEIGHT_MM: f32 = 4.5;
pub fn image_to_pdf(job: &Job) -> Result<()> {
let img = image::open(job.input)
.map_err(|e| Error::other(format!("image decode: {e}")))?
.to_rgb8();
let (px_w, px_h) = img.dimensions();
let (doc, page1, layer1) = PdfDocument::new(
"univert",
Mm(PAGE_WIDTH_MM),
Mm(PAGE_HEIGHT_MM),
"Layer 1",
);
let xobject = ImageXObject {
width: Px(px_w as usize),
height: Px(px_h as usize),
color_space: printpdf::ColorSpace::Rgb,
bits_per_component: printpdf::ColorBits::Bit8,
interpolate: true,
image_data: img.into_raw(),
image_filter: None,
smask: None,
clipping_bbox: None,
};
let image = Image { image: xobject };
let max_w_mm = PAGE_WIDTH_MM - 2.0 * MARGIN_MM;
let max_h_mm = PAGE_HEIGHT_MM - 2.0 * MARGIN_MM;
let img_w_mm_at_72dpi = px_w as f32 * 25.4 / 72.0;
let img_h_mm_at_72dpi = px_h as f32 * 25.4 / 72.0;
let scale = (max_w_mm / img_w_mm_at_72dpi)
.min(max_h_mm / img_h_mm_at_72dpi)
.min(1.0);
let draw_w_mm = img_w_mm_at_72dpi * scale;
let draw_h_mm = img_h_mm_at_72dpi * scale;
let tx = (PAGE_WIDTH_MM - draw_w_mm) / 2.0;
let ty = (PAGE_HEIGHT_MM - draw_h_mm) / 2.0;
image.add_to_layer(
doc.get_page(page1).get_layer(layer1),
ImageTransform {
translate_x: Some(Mm(tx)),
translate_y: Some(Mm(ty)),
scale_x: Some(scale),
scale_y: Some(scale),
rotate: None,
dpi: Some(72.0),
},
);
let file = fs::File::create(job.output)?;
doc
.save(&mut BufWriter::new(file))
.map_err(|e| Error::other(format!("pdf save: {e}")))?;
Ok(())
}
pub fn txt_to_pdf(job: &Job) -> Result<()> {
let text = fs::read_to_string(job.input)?;
let (doc, page1, layer1) = PdfDocument::new(
"univert",
Mm(PAGE_WIDTH_MM),
Mm(PAGE_HEIGHT_MM),
"Layer 1",
);
let font = doc
.add_builtin_font(BuiltinFont::Helvetica)
.map_err(|e| Error::other(format!("pdf font: {e}")))?;
let usable_h_mm = PAGE_HEIGHT_MM - 2.0 * MARGIN_MM;
let lines_per_page = (usable_h_mm / LINE_HEIGHT_MM).floor() as usize;
let lines: Vec<&str> = text.lines().collect();
let mut layer_ref = doc.get_page(page1).get_layer(layer1);
let mut first = true;
for chunk in lines.chunks(lines_per_page.max(1)) {
if !first {
let (p, l) =
doc.add_page(Mm(PAGE_WIDTH_MM), Mm(PAGE_HEIGHT_MM), "Layer 1");
layer_ref = doc.get_page(p).get_layer(l);
}
first = false;
layer_ref.begin_text_section();
layer_ref.set_font(&font, FONT_SIZE_PT);
layer_ref.set_line_height(FONT_SIZE_PT * 1.2);
layer_ref.set_text_cursor(Mm(MARGIN_MM), Mm(PAGE_HEIGHT_MM - MARGIN_MM));
for line in chunk {
layer_ref.write_text(*line, &font);
layer_ref.add_line_break();
}
layer_ref.end_text_section();
}
let file = fs::File::create(job.output)?;
doc
.save(&mut BufWriter::new(file))
.map_err(|e| Error::other(format!("pdf save: {e}")))?;
Ok(())
}
pub fn html_to_pdf(job: &Job) -> Result<()> {
let html = fs::read_to_string(job.input)?;
let typst_source = html_typst::html_to_typst(&html);
let input_abs = fs::canonicalize(job.input)?;
let root = input_abs
.parent()
.ok_or_else(|| Error::other("html: input has no parent directory"))?
.to_path_buf();
typst_fmt::compile_source(&typst_source, root, "pdf", job.output)
}