use std::cell::RefCell;
use std::io::Write;
use std::rc::Rc;
use anyhow::{Context, Result};
pub use printpdf::{BuiltinFont, Color, Mm, Rgb};
use printpdf::{
LinePoint, Op, PaintMode, PdfFontHandle, PdfPage, PdfSaveOptions, PdfWarnMsg, Point, Polygon,
PolygonRing, Pt, TextItem, WindingOrder,
};
pub type PdfPageIndex = usize;
pub type PdfLayerIndex = usize;
pub type IndirectFontRef = BuiltinFont;
struct PageBuf {
w: f32,
h: f32,
ops: Rc<RefCell<Vec<Op>>>,
}
struct DocInner {
title: String,
pages: Vec<PageBuf>,
}
#[derive(Clone)]
pub struct PdfDocumentReference {
inner: Rc<RefCell<DocInner>>,
}
pub struct PdfDocument;
impl PdfDocument {
#[allow(clippy::new_ret_no_self)]
pub fn new(
title: impl Into<String>,
width: Mm,
height: Mm,
_initial_layer_name: &str,
) -> (PdfDocumentReference, PdfPageIndex, PdfLayerIndex) {
let first = PageBuf {
w: width.0,
h: height.0,
ops: Rc::new(RefCell::new(Vec::new())),
};
let inner = DocInner {
title: title.into(),
pages: vec![first],
};
let doc = PdfDocumentReference {
inner: Rc::new(RefCell::new(inner)),
};
(doc, 0, 0)
}
}
impl PdfDocumentReference {
pub fn add_builtin_font(&self, font: BuiltinFont) -> Result<IndirectFontRef> {
Ok(font)
}
pub fn add_page(&self, width: Mm, height: Mm, _name: &str) -> (PdfPageIndex, PdfLayerIndex) {
let mut inner = self.inner.borrow_mut();
inner.pages.push(PageBuf {
w: width.0,
h: height.0,
ops: Rc::new(RefCell::new(Vec::new())),
});
(inner.pages.len() - 1, 0)
}
pub fn get_page(&self, page: PdfPageIndex) -> PdfPageHandle {
let ops = self.inner.borrow().pages[page].ops.clone();
PdfPageHandle { ops }
}
pub fn save<W: Write>(&self, writer: &mut W) -> Result<()> {
let inner = self.inner.borrow();
let mut doc = printpdf::PdfDocument::new(&inner.title);
let pages: Vec<PdfPage> = inner
.pages
.iter()
.map(|p| PdfPage::new(Mm(p.w), Mm(p.h), p.ops.borrow().clone()))
.collect();
doc.with_pages(pages);
let mut warnings: Vec<PdfWarnMsg> = Vec::new();
let bytes = doc.save(&PdfSaveOptions::default(), &mut warnings);
writer
.write_all(&bytes)
.context("failed to write PDF bytes")?;
Ok(())
}
}
pub struct PdfPageHandle {
ops: Rc<RefCell<Vec<Op>>>,
}
impl PdfPageHandle {
pub fn get_layer(&self, _layer: PdfLayerIndex) -> PdfLayerReference {
PdfLayerReference {
ops: self.ops.clone(),
}
}
}
#[derive(Clone)]
pub struct PdfLayerReference {
ops: Rc<RefCell<Vec<Op>>>,
}
impl PdfLayerReference {
pub fn set_fill_color(&self, col: Color) {
self.ops.borrow_mut().push(Op::SetFillColor { col });
}
pub fn use_text(
&self,
text: impl Into<String>,
font_size: f32,
x: Mm,
y: Mm,
font: &IndirectFontRef,
) {
let mut ops = self.ops.borrow_mut();
ops.push(Op::StartTextSection);
ops.push(Op::SetTextCursor {
pos: Point {
x: x.into_pt(),
y: y.into_pt(),
},
});
ops.push(Op::SetFont {
font: PdfFontHandle::Builtin(*font),
size: Pt(font_size),
});
ops.push(Op::ShowText {
items: vec![TextItem::Text(text.into())],
});
ops.push(Op::EndTextSection);
}
pub fn fill_rect(&self, x: f32, y: f32, w: f32, h: f32, color: Rgb) {
self.set_fill_color(Color::Rgb(color));
let corners = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)];
let points = corners
.iter()
.map(|&(px, py)| LinePoint {
p: Point::new(Mm(px), Mm(py)),
bezier: false,
})
.collect();
self.ops.borrow_mut().push(Op::DrawPolygon {
polygon: Polygon {
rings: vec![PolygonRing { points }],
mode: PaintMode::Fill,
winding_order: WindingOrder::NonZero,
},
});
}
}