use crate::color::Color;
use crate::content::{ContentStream, TextAlign};
use crate::font::BuiltinFont;
use crate::image::PdfImage;
use crate::object::{PdfDict, PdfObject};
use crate::table::Table;
#[allow(non_upper_case_globals)]
#[allow(non_snake_case)]
pub mod PageSize {
pub const A4: (f64, f64) = (595.276, 841.890);
pub const A3: (f64, f64) = (841.890, 1190.551);
pub const A5: (f64, f64) = (419.528, 595.276);
pub const LETTER: (f64, f64) = (612.0, 792.0);
pub const LEGAL: (f64, f64) = (612.0, 1008.0);
pub const TABLOID: (f64, f64) = (792.0, 1224.0);
}
struct RegisteredFont {
key: String, bold_key: String, font: BuiltinFont,
bold_font: BuiltinFont,
}
pub struct PageBuilder {
pub width: f64,
pub height: f64,
content: ContentStream,
fonts: Vec<RegisteredFont>,
font_counter: usize,
images: Vec<(String, PdfImage)>,
image_counter: usize,
pub margin: f64,
}
impl PageBuilder {
pub fn new(width: f64, height: f64) -> Self {
Self {
width,
height,
content: ContentStream::new(),
fonts: Vec::new(),
font_counter: 0,
images: Vec::new(),
image_counter: 0,
margin: 40.0,
}
}
pub fn a4() -> Self { Self::new(PageSize::A4.0, PageSize::A4.1) }
pub fn letter() -> Self { Self::new(PageSize::LETTER.0, PageSize::LETTER.1) }
pub fn a3() -> Self { Self::new(PageSize::A3.0, PageSize::A3.1) }
pub fn margin(mut self, m: f64) -> Self {
self.margin = m;
self
}
pub fn register_font(&mut self, font: BuiltinFont, bold: BuiltinFont) -> String {
self.font_counter += 1;
let key = format!("F{}", self.font_counter);
self.fonts.push(RegisteredFont {
key: format!("{}Reg", key),
bold_key: format!("{}Bold", key),
font,
bold_font: bold,
});
key
}
pub fn use_helvetica(&mut self) -> String {
self.register_font(BuiltinFont::Helvetica, BuiltinFont::HelveticaBold)
}
pub fn use_times(&mut self) -> String {
self.register_font(BuiltinFont::TimesRoman, BuiltinFont::TimesBold)
}
pub fn use_courier(&mut self) -> String {
self.register_font(BuiltinFont::Courier, BuiltinFont::CourierBold)
}
pub fn reg_key(&self, prefix: &str) -> String {
format!("{}Reg", prefix)
}
pub fn bold_key(&self, prefix: &str) -> String {
format!("{}Bold", prefix)
}
pub fn font_for_key(&self, key: &str) -> BuiltinFont {
for f in &self.fonts {
if f.key == key { return f.font; }
if f.bold_key == key { return f.bold_font; }
}
BuiltinFont::Helvetica
}
pub fn text(
&mut self,
text: &str,
x: f64, y: f64,
font_key: &str,
size: f64,
color: Color,
) -> &mut Self {
let font = self.font_for_key(font_key);
self.content.draw_text(text, x, y, font_key, font, size, color, TextAlign::Left);
self
}
pub fn text_centered(
&mut self,
text: &str,
cx: f64, y: f64,
font_key: &str,
size: f64,
color: Color,
) -> &mut Self {
let font = self.font_for_key(font_key);
self.content.draw_text(text, cx, y, font_key, font, size, color, TextAlign::Center);
self
}
pub fn text_right(
&mut self,
text: &str,
x: f64, y: f64,
font_key: &str,
size: f64,
color: Color,
) -> &mut Self {
let font = self.font_for_key(font_key);
self.content.draw_text(text, x, y, font_key, font, size, color, TextAlign::Right);
self
}
pub fn text_box(
&mut self,
text: &str,
x: f64, y: f64, width: f64,
font_key: &str,
size: f64,
color: Color,
line_height: f64,
) -> f64 {
let font = self.font_for_key(font_key);
self.content.draw_text_box(text, x, y, width, font_key, font, size, color, line_height)
}
pub fn filled_rect(&mut self, x: f64, y: f64, w: f64, h: f64, color: Color) -> &mut Self {
self.content.filled_rect(x, y, w, h, color);
self
}
pub fn stroked_rect(&mut self, x: f64, y: f64, w: f64, h: f64, color: Color, lw: f64) -> &mut Self {
self.content.stroked_rect(x, y, w, h, color, lw);
self
}
pub fn line(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, color: Color, width: f64) -> &mut Self {
self.content.line(x1, y1, x2, y2, color, width);
self
}
pub fn circle_fill(&mut self, cx: f64, cy: f64, r: f64, color: Color) -> &mut Self {
self.content.save();
self.content.fill_color(color);
self.content.circle(cx, cy, r);
self.content.fill();
self.content.restore();
self
}
pub fn image(&mut self, img: PdfImage, x: f64, y: f64, w: f64, h: f64) -> &mut Self {
self.image_counter += 1;
let key = format!("Im{}", self.image_counter);
self.content.draw_image(&key, x, y, w, h);
self.images.push((key, img));
self
}
pub fn image_ex(
&mut self,
img: PdfImage,
x: f64, y: f64, w: f64, h: f64,
opts: &crate::image::ImageOptions,
) -> &mut Self {
use crate::color::Color;
if let Some(ref shadow) = opts.shadow {
let alpha = shadow.opacity;
let spread = shadow.blur_spread;
let (sr, sg, sb) = match shadow.color {
Color::Rgb(r, g, b) => (r, g, b),
Color::Gray(v) => (v, v, v),
Color::Cmyk(c, m, y, _k) => (1.0 - c, 1.0 - m, 1.0 - y),
};
for i in 0..3u8 {
let s = spread * (1.0 - i as f64 * 0.3);
let a = alpha * (1.0 - i as f64 * 0.25);
let blend = |ch: f64| -> f64 { (ch * a + 1.0 * (1.0 - a)).min(1.0) };
let c = Color::Rgb(blend(sr), blend(sg), blend(sb));
self.content.save();
self.content.fill_color(c);
let sx = x + shadow.offset_x - s / 2.0;
let sy = y - shadow.offset_y - s / 2.0;
self.content.push_raw(format!(
"{:.4} {:.4} {:.4} {:.4} re f",
sx, sy, w + s, h + s
));
self.content.restore();
}
}
let has_crop = opts.crop.is_some();
if has_crop {
self.content.save();
if let Some(ref crop) = opts.crop {
let img_w = img.width as f64;
let img_h = img.height as f64;
let cx = x + (crop.x as f64 / img_w) * w;
let cy = y + (crop.y as f64 / img_h) * h;
let cw = (crop.width as f64 / img_w) * w;
let ch = (crop.height as f64 / img_h) * h;
self.content.push_raw(format!(
"{:.4} {:.4} {:.4} {:.4} re W n",
cx, cy, cw, ch
));
}
}
self.image_counter += 1;
let key = format!("Im{}", self.image_counter);
self.content.draw_image(&key, x, y, w, h);
self.images.push((key, img));
if has_crop {
self.content.restore();
}
if let Some(ref border) = opts.border {
self.content.save();
self.content.stroke_color(border.color);
self.content.push_raw(format!("{:.4} w", border.width));
if border.radius > 0.0 {
let r = border.radius;
let k = 0.5523 * r; let (lx, ly, rx, ry) = (x, y, x + w, y + h);
self.content.push_raw(format!("{:.4} {:.4} m", lx + r, ly));
self.content.push_raw(format!("{:.4} {:.4} l", rx - r, ly));
self.content.push_raw(format!("{:.4} {:.4} {:.4} {:.4} {:.4} {:.4} c", rx-r+k,ly, rx,ly+r-k, rx,ly+r));
self.content.push_raw(format!("{:.4} {:.4} l", rx, ry - r));
self.content.push_raw(format!("{:.4} {:.4} {:.4} {:.4} {:.4} {:.4} c", rx,ry-r+k, rx-r+k,ry, rx-r,ry));
self.content.push_raw(format!("{:.4} {:.4} l", lx + r, ry));
self.content.push_raw(format!("{:.4} {:.4} {:.4} {:.4} {:.4} {:.4} c", lx+r-k,ry, lx,ry-r+k, lx,ry-r));
self.content.push_raw(format!("{:.4} {:.4} l", lx, ly + r));
self.content.push_raw(format!("{:.4} {:.4} {:.4} {:.4} {:.4} {:.4} c", lx,ly+r-k, lx+r-k,ly, lx+r,ly));
self.content.push_raw("h S");
} else {
self.content.push_raw(format!(
"{:.4} {:.4} {:.4} {:.4} re S",
x, y, w, h
));
}
self.content.restore();
}
self
}
pub fn table(&mut self, tbl: &Table, x: f64, y_top: f64) -> f64 {
let prefix = if !self.fonts.is_empty() {
self.fonts[0].key.trim_end_matches("Reg").to_string()
} else {
"F1".to_string()
};
tbl.render(&mut self.content, x, y_top, &prefix)
}
pub fn hyperlink(
&mut self,
text: &str,
url: &str,
x: f64, y: f64,
font_key: &str,
size: f64,
) -> &mut Self {
let font = self.font_for_key(font_key);
let text_w = font.string_width(text, size);
self.content.draw_text(text, x, y, font_key, font, size, Color::DARK_BLUE, TextAlign::Left);
self.content.line(x, y - 1.0, x + text_w, y - 1.0, Color::DARK_BLUE, 0.5);
let _ = url;
self
}
pub fn content_stream(&mut self) -> &mut ContentStream {
&mut self.content
}
pub(crate) fn finish(self) -> (Vec<u8>, Vec<(String, PdfImage)>, PdfDict) {
let mut resources = PdfDict::new();
if !self.fonts.is_empty() {
let mut font_dict = PdfDict::new();
for f in &self.fonts {
let mut font_obj = PdfDict::new();
font_obj.set("Type", PdfObject::name("Font"));
font_obj.set("Subtype", PdfObject::name("Type1"));
font_obj.set("BaseFont", PdfObject::name(f.font.pdf_name()));
font_obj.set("Encoding", PdfObject::name("WinAnsiEncoding"));
font_dict.set(f.key.clone(), PdfObject::Dictionary(font_obj));
let mut bold_obj = PdfDict::new();
bold_obj.set("Type", PdfObject::name("Font"));
bold_obj.set("Subtype", PdfObject::name("Type1"));
bold_obj.set("BaseFont", PdfObject::name(f.bold_font.pdf_name()));
bold_obj.set("Encoding", PdfObject::name("WinAnsiEncoding"));
font_dict.set(f.bold_key.clone(), PdfObject::Dictionary(bold_obj));
}
resources.set("Font", PdfObject::Dictionary(font_dict));
}
(self.content.to_bytes(), self.images, resources)
}
}