use std::fs;
use std::io;
use std::path;
use crate::error::Error;
use crate::fonts;
use crate::style::{Color, Style};
use crate::{Margins, Mm, Position, Size};
pub struct Renderer {
doc: printpdf::PdfDocumentReference,
pages: Vec<Page>,
}
impl Renderer {
pub fn new(size: impl Into<Size>, title: impl AsRef<str>) -> Result<Renderer, Error> {
let size = size.into();
let (doc, page_idx, layer_idx) = printpdf::PdfDocument::new(
title.as_ref(),
size.width.into(),
size.height.into(),
"Layer 1",
);
let page_ref = doc.get_page(page_idx);
let layer_ref = page_ref.get_layer(layer_idx);
let page = Page::new(page_ref, layer_ref, size);
Ok(Renderer {
doc,
pages: vec![page],
})
}
pub fn with_conformance(mut self, conformance: printpdf::PdfConformance) -> Self {
self.doc = self.doc.with_conformance(conformance);
self
}
pub fn add_page(&mut self, size: impl Into<Size>) {
let size = size.into();
let (page_idx, layer_idx) =
self.doc
.add_page(size.width.into(), size.height.into(), "Layer 1");
let page_ref = self.doc.get_page(page_idx);
let layer_ref = page_ref.get_layer(layer_idx);
self.pages.push(Page::new(page_ref, layer_ref, size))
}
pub fn page_count(&self) -> usize {
self.pages.len()
}
pub fn get_page(&self, idx: usize) -> Option<&Page> {
self.pages.get(idx)
}
pub fn get_page_mut(&mut self, idx: usize) -> Option<&mut Page> {
self.pages.get_mut(idx)
}
pub fn first_page(&self) -> &Page {
&self.pages[0]
}
pub fn first_page_mut(&mut self) -> &mut Page {
&mut self.pages[0]
}
pub fn last_page(&self) -> &Page {
&self.pages[self.pages.len() - 1]
}
pub fn last_page_mut(&mut self) -> &mut Page {
let idx = self.pages.len() - 1;
&mut self.pages[idx]
}
pub fn load_font(
&self,
path: impl AsRef<path::Path>,
) -> Result<printpdf::IndirectFontRef, Error> {
let path = path.as_ref();
let font_file = fs::File::open(path).map_err(|err| {
Error::new(format!("Failed to open font file {}", path.display()), err)
})?;
self.doc.add_external_font(font_file).map_err(|err| {
Error::new(
format!("Failed to load PDF font from file {}", path.display()),
err,
)
})
}
pub fn write(self, w: impl io::Write) -> Result<(), Error> {
self.doc
.save(&mut io::BufWriter::new(w))
.map_err(|err| Error::new("Failed to save document", err))
}
}
pub struct Page {
page: printpdf::PdfPageReference,
size: Size,
layers: Vec<Layer>,
}
impl Page {
fn new(
page: printpdf::PdfPageReference,
layer: printpdf::PdfLayerReference,
size: Size,
) -> Page {
Page {
page,
size,
layers: vec![Layer::new(layer, size)],
}
}
pub fn add_layer(&mut self, name: impl Into<String>) {
let layer = self.page.add_layer(name);
self.layers.push(Layer::new(layer, self.size));
}
pub fn layer_count(&self) -> usize {
self.layers.len()
}
pub fn get_layer(&self, idx: usize) -> Option<&Layer> {
self.layers.get(idx)
}
pub fn first_layer(&self) -> &Layer {
&self.layers[0]
}
pub fn last_layer(&self) -> &Layer {
&self.layers[self.layers.len() - 1]
}
}
pub struct Layer {
layer: printpdf::PdfLayerReference,
size: Size,
}
impl Layer {
fn new(layer: printpdf::PdfLayerReference, size: Size) -> Layer {
Layer { layer, size }
}
pub fn area(&self) -> Area<'_> {
Area::new(self, Position::default(), self.size)
}
}
#[derive(Clone)]
pub struct Area<'a> {
layer: &'a Layer,
origin: Position,
size: Size,
}
impl<'a> Area<'a> {
fn new(layer: &'a Layer, origin: Position, size: Size) -> Area<'a> {
Area {
layer,
origin,
size,
}
}
pub fn add_margins(&mut self, margins: impl Into<Margins>) {
let margins = margins.into();
self.origin.x += margins.left;
self.origin.y += margins.top;
self.size.width -= margins.left + margins.right;
self.size.height -= margins.top + margins.bottom;
}
pub fn size(&self) -> Size {
self.size
}
pub fn add_offset(&mut self, offset: impl Into<Position>) {
let offset = offset.into();
self.origin.x += offset.x;
self.origin.y += offset.y;
self.size.width -= offset.x;
self.size.height -= offset.y;
}
pub fn set_size(&mut self, size: impl Into<Size>) {
self.size = size.into();
}
pub fn set_width(&mut self, width: Mm) {
self.size.width = width;
}
pub fn draw_line(&self, points: Vec<Position>, style: Style) {
let line_points: Vec<_> = points
.into_iter()
.map(|pos| {
(
printpdf::Point::new(
(self.origin.x + pos.x).into(),
(self.layer.size.height - self.origin.y - pos.y).into(),
),
false,
)
})
.collect();
let line = printpdf::Line {
points: line_points,
is_closed: false,
has_fill: false,
has_stroke: true,
is_clipping_path: false,
};
if let Some(color) = style.color() {
self.layer.layer.set_outline_color(color.into());
}
self.layer.layer.add_shape(line);
if style.color().is_some() {
self.layer
.layer
.set_outline_color(Color::Rgb(0, 0, 0).into());
}
}
pub fn print_str<S: AsRef<str>>(
&self,
font_cache: &fonts::FontCache,
position: Position,
style: Style,
s: S,
) -> bool {
if let Ok(mut section) = self.text_section(font_cache, position, style) {
section.print_str(s, style);
true
} else {
false
}
}
pub fn text_section<'f>(
&self,
font_cache: &'f fonts::FontCache,
position: Position,
style: Style,
) -> Result<TextSection<'_, 'f, 'a>, ()> {
TextSection::new(font_cache, self, position, style)
}
}
pub struct TextSection<'a, 'f, 'l> {
font_cache: &'f fonts::FontCache,
area: &'a Area<'l>,
line_height: Mm,
cursor: Position,
fill_color: Option<Color>,
}
impl<'a, 'f, 'l> TextSection<'a, 'f, 'l> {
fn new(
font_cache: &'f fonts::FontCache,
area: &'a Area<'l>,
position: Position,
style: Style,
) -> Result<TextSection<'a, 'f, 'l>, ()> {
let height = style.font(font_cache).glyph_height(style.font_size());
if position.y + height > area.size.height {
return Err(());
}
let line_height = style.line_height(font_cache);
let section = TextSection {
font_cache,
area,
line_height,
cursor: position,
fill_color: None,
};
section.layer().begin_text_section();
section.layer().set_line_height(line_height.0 as i64);
section.layer().set_text_cursor(
(section.area.origin.x + position.x).into(),
(section.area.layer.size.height - section.area.origin.y - position.y - height).into(),
);
Ok(section)
}
#[must_use]
pub fn add_newline(&mut self) -> bool {
if self.cursor.y + self.line_height > self.area.size.height {
false
} else {
self.layer().add_line_break();
self.cursor.y += self.line_height;
true
}
}
pub fn print_str(&mut self, s: impl AsRef<str>, style: Style) {
let font = self
.font_cache
.get_pdf_font(style.font(self.font_cache))
.expect("Could not find PDF font in font cache");
if let Some(color) = style.color() {
self.layer().set_fill_color(color.into());
} else if self.fill_color.is_some() {
self.layer().set_fill_color(Color::Rgb(0, 0, 0).into());
}
self.fill_color = style.color();
self.layer().set_font(font, style.font_size().into());
self.layer().write_text(s.as_ref(), font);
}
fn layer(&self) -> &printpdf::PdfLayerReference {
&self.area.layer.layer
}
}
impl<'a, 'f, 'l> Drop for TextSection<'a, 'f, 'l> {
fn drop(&mut self) {
if self.fill_color.is_some() {
self.layer().set_fill_color(Color::Rgb(0, 0, 0).into());
}
self.layer().end_text_section();
}
}