use std::io;
use crate::error::{Context as _, Error, ErrorKind};
use crate::fonts;
use crate::style::{Color, Style};
use crate::{Margins, Mm, Position, Size};
#[cfg(feature = "images")]
use crate::{Rotation, Scale};
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 add_builtin_font(
&self,
builtin: printpdf::BuiltinFont,
) -> Result<printpdf::IndirectFontRef, Error> {
self.doc
.add_builtin_font(builtin)
.context("Failed to load PDF font")
}
pub fn add_embedded_font(&self, data: &[u8]) -> Result<printpdf::IndirectFontRef, Error> {
self.doc
.add_external_font(data)
.context("Failed to load PDF font")
}
pub fn write(self, w: impl io::Write) -> Result<(), Error> {
self.doc
.save(&mut io::BufWriter::new(w))
.context("Failed to save document")
}
}
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)
}
fn transform_position(&self, mut position: Position) -> Position {
position.y = self.size.height - position.y;
position
}
}
#[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 set_height(&mut self, height: Mm) {
self.size.height = height;
}
pub fn split_horizontally(&self, weights: &[usize]) -> Vec<Area<'a>> {
let total_weight: usize = weights.iter().sum();
let factor = self.size.width / total_weight as f64;
let widths = weights.iter().map(|weight| factor * *weight as f64);
let mut offset = Mm(0.0);
let mut areas = Vec::new();
for width in widths {
let mut area = self.clone();
area.origin.x += offset;
area.size.width = width;
areas.push(area);
offset += width;
}
areas
}
#[cfg(feature = "images")]
pub fn add_image(
&self,
image: &image::DynamicImage,
position: Position,
scale: Scale,
rotation: Rotation,
dpi: Option<f64>,
) {
let dynamic_image = printpdf::Image::from_dynamic_image(image);
let real_position = self.transform_position(position);
let layer = self.layer().clone();
dynamic_image.add_to_layer(
layer,
Some(real_position.x.into()),
Some(real_position.y.into()),
rotation.into(),
Some(scale.x),
Some(scale.y),
dpi,
);
}
pub fn draw_line(&self, points: Vec<Position>, style: Style) {
let line_points: Vec<_> = points
.into_iter()
.map(|pos| (self.transform_position(pos).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().set_outline_color(color.into());
}
self.layer().add_shape(line);
if style.color().is_some() {
self.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,
) -> Result<bool, Error> {
if let Some(mut section) = self.text_section(font_cache, position, style) {
section.print_str(s, style)?;
Ok(true)
} else {
Ok(false)
}
}
pub fn text_section<'f>(
&self,
font_cache: &'f fonts::FontCache,
position: Position,
style: Style,
) -> Option<TextSection<'_, 'f, 'a>> {
TextSection::new(font_cache, self, position, style)
}
fn transform_position(&self, mut position: Position) -> Position {
position += self.origin;
self.layer.transform_position(position)
}
fn layer(&self) -> &printpdf::PdfLayerReference {
&self.layer.layer
}
}
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,
) -> Option<TextSection<'a, 'f, 'l>> {
let height = style.font(font_cache).glyph_height(style.font_size());
if position.y + height > area.size.height {
return None;
}
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.into());
let cursor = area.transform_position(position);
section
.layer()
.set_text_cursor(cursor.x.into(), (cursor.y - height).into());
Some(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) -> Result<(), Error> {
let font = style.font(self.font_cache);
let positions = font
.kerning(self.font_cache, s.as_ref().chars())
.into_iter()
.map(|pos| pos * -1000.0)
.map(|pos| pos as i64);
let codepoints = if font.is_builtin() {
encode_win1252(s.as_ref())?
} else {
font.glyph_ids(&self.font_cache, s.as_ref().chars())
};
let font = self
.font_cache
.get_pdf_font(font)
.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_positioned_codepoints(positions.zip(codepoints.iter().copied()));
Ok(())
}
fn layer(&self) -> &printpdf::PdfLayerReference {
self.area.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();
}
}
fn encode_win1252(s: &str) -> Result<Vec<u16>, Error> {
let bytes: Vec<_> = lopdf::Document::encode_text(Some("WinAnsiEncoding"), s)
.into_iter()
.map(u16::from)
.collect();
if bytes.len() != s.chars().count() {
Err(Error::new(
format!(
"Tried to print a string with characters that are not supported by the \
Windows-1252 encoding with a built-in font: {}",
s
),
ErrorKind::UnsupportedEncoding,
))
} else {
Ok(bytes)
}
}