use thiserror::Error;
use std::io::Write;
use std::collections::HashMap;
use bimap::BiMap;
use enumset::{ EnumSet, EnumSetType };
use flate2::{ Compress, Compression, FlushCompress };
use chrono::{ DateTime, Utc };
use bit_set::BitSet;
use image::DynamicImage;
use mttf::read::FontFile;
use mttf::read::common::{ Platform, PlatformEncoding, WindowsEncoding };
use mttf::read::name::{ NamingTable, NameKind };
use mttf::read::os_2::Os2MetricsTable;
use mttf::read::cmap::{ CharacterToGlyphMap, Subtable, SegmentMappingTable, SegmentCoverageTable };
pub const A3: (f32, f32) = (841.89, 1190.55);
pub const A4: (f32, f32) = (595.28, 841.89);
pub const A5: (f32, f32) = (420.94, 595.28);
pub const LETTER: (f32, f32) = (612.0, 792.0);
pub const LEGAL: (f32, f32) = (612.0, 1008.0);
fn compress(data: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(data.len());
let mut compressor = Compress::new(Compression::best(), true);
compressor.compress_vec(data, &mut out, FlushCompress::Full).unwrap();
out.shrink_to_fit();
out
}
#[derive(Error, Debug)]
pub enum FontError {
#[error("failed to parse font: {0}")]
ParseError(#[from] mttf::read::ParseError),
#[error("failed to parse font: {0}")]
TableError(#[from] mttf::read::TableError),
#[error("failed to parse font: {0}")]
StringError(#[from] mttf::read::name::StringError),
#[error("could not detect family name")]
CouldNotDetectFamilyName,
#[error("could not detect font variant")]
CouldNotDetectFontVariant,
#[error("no supported character to glyph map")]
NoSupportedCharacterToGlyphMap,
}
#[derive(Clone)]
pub struct Font {
name: String,
uncompressed_size: usize,
data: Vec<u8>, units_per_em: u16,
bounding_box: (i16, i16, i16, i16),
underline_position: i16,
underline_thickness: i16,
italic_angle: mttf::read::core::Fixed,
is_fixed_pitch: bool,
ascent: i16,
descent: i16,
default_aw: u16,
cap_height: i16,
weight_class: u16,
char_to_cid: HashMap<char, u32>,
cid_to_char: Vec<char>,
cid_to_gid: Vec<u8>, cid_to_aw: Vec<u16>,
}
impl Font {
pub fn cid_of(&self, c: char) -> u32 {
self.char_to_cid.get(&c).copied().unwrap_or_default()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FontVariant {
Regular,
Bold,
Italic,
BoldItalic,
}
#[derive(Clone, Default)]
pub struct FontLibrary {
fonts: Vec<Font>,
font_map: BiMap<(String, FontVariant), u32>,
}
impl FontLibrary {
fn parse_font(data: &[u8]) -> Result<(String, FontVariant, Font), FontError> {
let font = FontFile::try_from(data)?;
let name = font.naming_table(true)?;
let head = font.header_table(true)?;
let post = font.post_script_table(true)?;
let hhea = font.horizontal_header_table(true)?;
let hmtx = font.horizontal_metrics_table(true)?;
let os_2 = font.os_2_metrics_table(true)?;
let cmap = font.character_to_glyph_map(true)?;
let family = Self::get_family(name)?;
let variant = Self::get_variant(name)?;
let name = Self::get_font_name(name);
let units_per_em = head.units_per_em();
let bounding_box = head.bounding_box();
let underline_position = post.underline_position();
let underline_thickness = post.underline_thickness();
let italic_angle = post.italic_angle();
let is_fixed_pitch = post.is_fixed_pitch() != 0;
let ascent = hhea.ascender();
let descent = hhea.descender();
let default_aw = hmtx.default_advance_width().unwrap_or_default();
let cap_height = match os_2 {
Os2MetricsTable::Version0(_) => ascent,
Os2MetricsTable::Version1(_) => ascent,
Os2MetricsTable::Version2(x) => x.cap_height(),
Os2MetricsTable::Version3(x) => x.cap_height(),
Os2MetricsTable::Version4(x) => x.cap_height(),
Os2MetricsTable::Version5(x) => x.cap_height(),
};
let weight_class = os_2.weight_class();
let mut char_to_cid = HashMap::new();
let mut cid_to_char = Vec::new();
let mut cid_to_gid = Vec::new();
let mut cid_to_aw = Vec::new();
if let Some(cmap) = Self::get_unicode_full_cmap(cmap) {
for group in cmap.groups() {
for (i, gid) in group.chars().zip(group.glyphs()) {
let c = char::from_u32(i).unwrap();
let cid = cid_to_char.len() as u32;
let aw = hmtx.get(gid as u16).advance_width;
char_to_cid.insert(c, cid);
cid_to_char.push(c);
cid_to_gid.push((gid >> 8) as u8);
cid_to_gid.push((gid & 0xFF) as u8);
cid_to_aw.push(aw);
}
}
} else if let Some(cmap) = Self::get_unicode_bmp_cmap(cmap) {
for seg in cmap.segments() {
for i in seg.chars() {
let ch = char::from_u32(i as u32).unwrap();
let cid = cid_to_char.len() as u32;
let gid = seg.map(i) as u32;
let aw = hmtx.get(gid as u16).advance_width;
char_to_cid.insert(ch, cid);
cid_to_char.push(ch);
cid_to_gid.push((gid >> 8) as u8);
cid_to_gid.push((gid & 0xFF) as u8);
cid_to_aw.push(aw);
}
}
} else {
return Err(FontError::NoSupportedCharacterToGlyphMap);
}
let font = Font {
name,
uncompressed_size: data.len(),
data: compress(data),
units_per_em,
bounding_box,
underline_position,
underline_thickness,
italic_angle,
is_fixed_pitch,
ascent,
descent,
default_aw,
cap_height,
weight_class,
char_to_cid,
cid_to_char,
cid_to_gid: compress(&cid_to_gid),
cid_to_aw,
};
Ok((family, variant, font))
}
fn get_family(name: NamingTable<'_>) -> Result<String, FontError> {
for name in name.iter() {
if name.kind() == NameKind::FamilyName {
return Ok(name.text()?.flatten().collect());
}
}
Err(FontError::CouldNotDetectFamilyName)
}
fn get_variant(name: NamingTable<'_>) -> Result<FontVariant, FontError> {
for name in name.iter() {
if name.kind() == NameKind::FontSubfamily {
let name: String = name.text()?.flatten().collect();
let variant = match (name.contains("Bold"), name.contains("Italic") || name.contains("Oblique")) {
(false, false) => FontVariant::Regular,
(true, false) => FontVariant::Bold,
(false, true) => FontVariant::Italic,
(true, true) => FontVariant::BoldItalic,
};
return Ok(variant);
}
}
Err(FontError::CouldNotDetectFontVariant)
}
fn get_font_name(name: NamingTable<'_>) -> String {
let mut family_name: Option<String> = None;
let mut full_name: Option<String> = None;
let mut post_script_name: Option<String> = None;
for name in name.iter() {
match name.kind() {
NameKind::FamilyName => if let Ok(name) = name.text() { family_name = Some(name.flatten().collect()); },
NameKind::FullName => if let Ok(name) = name.text() { full_name = Some(name.flatten().collect()); },
NameKind::PostScriptName => if let Ok(name) = name.text() { post_script_name = Some(name.flatten().collect()); },
_ => (),
}
}
if let Some(name) = post_script_name { name }
else if let Some(name) = &full_name { name.replace(' ', "-") }
else if let Some(name) = &family_name { name.replace(' ', "-") }
else { String::new() }
}
fn get_unicode_bmp_cmap(cmap: CharacterToGlyphMap<'_>) -> Option<SegmentMappingTable<'_>> {
for cmap in cmap.subtables().flatten() {
if let Subtable::SegmentMappingTable(cmap) = cmap {
let enc = cmap.encoding_and_language();
if enc.platform() == Platform::Unicode || enc.encoding() == PlatformEncoding::Windows(WindowsEncoding::UnicodeBmp) {
return Some(cmap);
}
}
}
None
}
fn get_unicode_full_cmap(cmap: CharacterToGlyphMap<'_>) -> Option<SegmentCoverageTable<'_>> {
for cmap in cmap.subtables().flatten() {
if let Subtable::SegmentCoverageTable(cmap) = cmap {
let enc = cmap.encoding_and_language();
if enc.platform() == Platform::Unicode || enc.encoding() == PlatformEncoding::Windows(WindowsEncoding::UnicodeFullRepertoire) {
return Some(cmap);
}
}
}
None
}
pub fn with(mut self, data: &[u8]) -> Result<Self, FontError> {
let (family, variant, font) = Self::parse_font(data)?;
self.fonts.push(font);
let font_id = self.fonts.len() as u32;
self.font_map.insert((family, variant), font_id);
Ok(self)
}
pub fn get(&self, id: u32) -> &Font {
&self.fonts[id as usize - 1]
}
pub fn map(&self, family: &str, variant: FontVariant) -> u32 {
match self.font_map.get_by_left(&(family.to_owned(), variant)) {
Some(id) => *id,
None => 0,
}
}
pub fn rmap(&self, font_id: u32) -> (&str, FontVariant) {
match self.font_map.get_by_right(&font_id) {
Some(x) => (x.0.as_ref(), x.1),
None => ("", FontVariant::Regular),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ColorSpace {
Gray,
Rgb,
}
#[derive(Clone)]
pub struct Image {
width: u32,
height: u32,
color_space: ColorSpace,
color_plane: Vec<u8>, alpha_plane: Option<Vec<u8>>, }
impl Image {
pub fn width(&self) -> u32 { self.width }
pub fn height(&self) -> u32 { self.height }
pub fn color_space(&self) -> ColorSpace { self.color_space }
pub fn has_alpha(&self) -> bool { self.alpha_plane.is_some() }
}
impl From<&DynamicImage> for Image {
fn from(value: &DynamicImage) -> Self {
match value {
DynamicImage::ImageLuma8(_) |
DynamicImage::ImageLuma16(_) => {
let img = value.to_luma8();
let width = img.width();
let height = img.height();
let color = compress(img.as_ref());
Self {
width,
height,
color_space: ColorSpace::Gray,
color_plane: color,
alpha_plane: None,
}
},
DynamicImage::ImageLumaA8(_) |
DynamicImage::ImageLumaA16(_) => {
let img = value.to_luma_alpha8();
let width = img.width();
let height = img.height();
let mut color = vec![0; width as usize * height as usize];
let mut alpha = vec![0; width as usize * height as usize];
for (x, y, p) in img.enumerate_pixels() {
color[(y * width + x) as usize] = p.0[0];
alpha[(y * width + x) as usize] = p.0[1];
}
let color = compress(&color);
let alpha = compress(&alpha);
Self {
width,
height,
color_space: ColorSpace::Gray,
color_plane: color,
alpha_plane: Some(alpha),
}
},
DynamicImage::ImageRgb8(_) |
DynamicImage::ImageRgb16(_) |
DynamicImage::ImageRgb32F(_) => {
let img = value.to_rgb8();
let width = img.width();
let height = img.height();
let color = compress(img.as_ref());
Self {
width,
height,
color_space: ColorSpace::Rgb,
color_plane: color,
alpha_plane: None,
}
},
DynamicImage::ImageRgba8(_) |
DynamicImage::ImageRgba16(_) |
DynamicImage::ImageRgba32F(_) => {
let img = value.to_rgba8();
let width = img.width();
let height = img.height();
let mut color = vec![0; width as usize * height as usize * 3];
let mut alpha = vec![0; width as usize * height as usize];
for (x, y, p) in img.enumerate_pixels() {
color[(y * width + x) as usize * 3] = p.0[0];
color[(y * width + x) as usize * 3 + 1] = p.0[1];
color[(y * width + x) as usize * 3 + 2] = p.0[2];
alpha[(y * width + x) as usize] = p.0[3];
}
let color = compress(&color);
let alpha = compress(&alpha);
Self {
width,
height,
color_space: ColorSpace::Rgb,
color_plane: color,
alpha_plane: Some(alpha),
}
},
_ => panic!("unimplemented image type"),
}
}
}
#[derive(Clone)] struct Page {
buffer: Vec<u8>,
size: (f32, f32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct ImageRef(u32);
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum ZoomMode {
#[default]
Default,
FullPage,
FullWidth,
Real,
Percent(f32),
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum LayoutMode {
#[default]
Default,
Single,
Continuous,
Two,
}
#[derive(Debug, Clone)]
pub struct Metadata {
pub producer: String,
pub title: Option<String>,
pub author: Option<String>,
pub subject: Option<String>,
pub keywords: Option<String>,
pub creator: Option<String>,
pub creation_date: Option<DateTime<Utc>>,
}
impl Default for Metadata {
fn default() -> Self {
Metadata {
producer: "mpdf by Roman Meusch".to_owned(),
title: None,
author: None,
subject: None,
keywords: None,
creator: None,
creation_date: None,
}
}
}
#[derive(Debug, EnumSetType)]
pub enum Capability {
Pdf1_4,
Transparency,
}
pub struct Document<'f> {
pages: Vec<Page>,
font_library: &'f FontLibrary,
used_fonts: BitSet,
images: Vec<Image>,
zoom_mode: ZoomMode,
layout_mode: LayoutMode,
metadata: Metadata,
capabilities: EnumSet<Capability>,
}
impl<'f> Document<'f> {
pub fn new(font_library: &'f FontLibrary) -> Self {
Self {
pages: Vec::new(),
font_library,
used_fonts: BitSet::new(),
images: Vec::new(),
zoom_mode: ZoomMode::default(),
layout_mode: LayoutMode::default(),
metadata: Metadata::default(),
capabilities: EnumSet::EMPTY,
}
}
pub fn add_image(&mut self, image: Image) -> ImageRef {
let index = self.images.len();
if image.has_alpha() {
self.capabilities.insert(Capability::Pdf1_4);
self.capabilities.insert(Capability::Transparency);
}
self.images.push(image);
ImageRef(index as u32)
}
pub fn zoom_mode(&mut self, zoom_mode: ZoomMode) {
self.zoom_mode = zoom_mode;
}
pub fn layout_mode(&mut self, layout_mode: LayoutMode) {
self.layout_mode = layout_mode;
}
pub fn metadata(&mut self) -> &mut Metadata {
&mut self.metadata
}
pub fn encode(&self) -> Vec<u8> {
let mut encoder = Encoder::new(self);
encoder.run();
encoder.buffer
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Color {
Gray(u8),
Rgb(u8, u8, u8),
}
impl Default for Color {
fn default() -> Self {
Color::Gray(0)
}
}
impl Color {
pub fn pdf_string(self, uppercase: bool) -> String {
match self {
Self::Gray(x) if uppercase => format!("{:.3} G", x as f32 / 255.0),
Self::Gray(x) => format!("{:.3} g", x as f32 / 255.0),
Self::Rgb(r, g, b) if uppercase => format!("{:.3} {:.3} {:.3} RG", r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0),
Self::Rgb(r, g, b) => format!("{:.3} {:.3} {:.3} rg", r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum CursorTarget {
EndOfLine,
NextLine,
NextLineKeepX,
}
#[derive(Debug, Clone, Copy)]
pub enum TextAlign {
Left,
Right,
Center,
}
#[derive(Debug, EnumSetType)]
pub enum Border {
Left,
Top,
Right,
Bottom,
}
impl Border {
pub const NONE: EnumSet<Border> = EnumSet::EMPTY;
pub const ALL: EnumSet<Border> = EnumSet::ALL;
}
#[derive(Clone)]
pub struct Style {
pub line_width: f32,
pub font_id: u32,
pub underline: bool,
pub font_size: f32,
pub line_height: f32,
pub draw_color: Color,
pub fill_color: Color,
pub text_color: Color,
}
impl Default for Style {
fn default() -> Self {
Self {
line_width: 0.567,
font_id: 0,
underline: false,
font_size: 12.0,
line_height: 16.0,
draw_color: Color::default(),
fill_color: Color::default(),
text_color: Color::default(),
}
}
}
pub trait RenderTarget<'f> {
fn font_library(&self) -> &'f FontLibrary;
fn size(&self) -> (f32, Option<f32>);
fn init(&self) -> Option<Block<'f>>;
fn write(&mut self, data: &[u8], height: f32, fonts: &BitSet) -> Option<Block<'f>>;
}
pub struct RenderContext<'t, 'f> {
render_target: &'t mut dyn RenderTarget<'f>,
font_library: &'f FontLibrary,
style: Style,
width: f32,
height: Option<f32>,
left: f32,
right: f32,
x: f32,
y: f32,
buffer: Vec<u8>,
fonts: BitSet,
}
impl<'t, 'f> RenderContext<'t, 'f> {
pub fn new<T>(render_target: &'t mut T) -> Self where T: RenderTarget<'f> {
let font_library = render_target.font_library();
let size = render_target.size();
let mut ctx = Self {
render_target,
font_library,
style: Style::default(),
width: size.0,
height: size.1,
left: 0.0,
right: 0.0,
x: 0.0,
y: 0.0,
buffer: Vec::new(),
fonts: BitSet::new(),
};
if let Some(block) = ctx.render_target.init() {
ctx.put(&block);
}
ctx
}
pub fn flush(&mut self) {
let intro = self.render_target.write(&self.buffer, self.y, &self.fonts);
self.x = self.left;
self.y = 0.0;
self.buffer.clear();
writeln!(&mut self.buffer, "{:.2} w", self.style.line_width).unwrap();
writeln!(&mut self.buffer, "BT /F{} {:.2} Tf ET", self.style.font_id, self.style.font_size).unwrap();
writeln!(&mut self.buffer, "{}", self.style.draw_color.pdf_string(true)).unwrap();
writeln!(&mut self.buffer, "{}", self.style.fill_color.pdf_string(false)).unwrap();
if let Some(intro) = intro {
self.put(&intro);
}
}
pub fn width(&self) -> f32 { self.width }
pub fn height(&self) -> Option<f32> { self.height }
pub fn font_library(&self) -> &'f FontLibrary { self.font_library }
pub fn style(&self) -> &Style { &self.style }
pub fn line_width(&mut self, line_width: f32) {
if self.style.line_width != line_width {
self.style.line_width = line_width;
writeln!(&mut self.buffer, "{:.2} w", line_width).unwrap();
}
}
pub fn font_id(&mut self, font_id: u32) {
if self.style.font_id != font_id {
self.style.font_id = font_id;
self.fonts.insert(font_id as usize);
writeln!(&mut self.buffer, "BT /F{} {:.2} Tf ET", font_id, self.style.font_size).unwrap();
}
}
pub fn font(&mut self, font_family: &str, font_variant: FontVariant) {
let font_id = self.font_library.map(font_family, font_variant);
self.font_id(font_id)
}
pub fn font_family(&mut self, font_family: &str) {
let (_, font_variant) = self.font_library.rmap(self.style.font_id);
let font_id = self.font_library.map(font_family, font_variant);
self.font_id(font_id)
}
pub fn font_variant(&mut self, font_variant: FontVariant) {
let (font_family, _) = self.font_library.rmap(self.style.font_id);
let font_id = self.font_library.map(font_family, font_variant);
self.font_id(font_id)
}
pub fn underline(&mut self, underline: bool) {
self.style.underline = underline;
}
pub fn font_size(&mut self, font_size: f32) {
if self.style.font_size != font_size {
self.style.font_size = font_size;
writeln!(&mut self.buffer, "BT /F{} {:.2} Tf ET", self.style.font_id, self.style.font_size).unwrap();
}
}
pub fn line_height(&mut self, line_height: f32) {
self.style.line_height = line_height;
}
pub fn draw_color(&mut self, draw_color: Color) {
if self.style.draw_color != draw_color {
self.style.draw_color = draw_color;
writeln!(&mut self.buffer, "{}", draw_color.pdf_string(true)).unwrap();
}
}
pub fn fill_color(&mut self, fill_color: Color) {
if self.style.fill_color != fill_color {
self.style.fill_color = fill_color;
writeln!(&mut self.buffer, "{}", fill_color.pdf_string(false)).unwrap();
}
}
pub fn text_color(&mut self, text_color: Color) {
self.style.text_color = text_color;
}
pub fn block(&self) -> Block<'f> {
Block::new(self.font_library, self.width)
}
pub fn fits(&self, h: f32) -> bool {
match self.height {
Some(height) => self.y + h <= height,
None => true,
}
}
pub fn put(&mut self, block: &Block) {
if !self.fits(block.height) {
self.flush();
}
writeln!(&mut self.buffer, "q 1.0 0.0 0.0 1.0 {:.3} {:.3} cm", self.x, -self.y).unwrap();
self.buffer.extend_from_slice(&block.buffer);
writeln!(&mut self.buffer, "Q").unwrap();
self.y += block.height;
self.x = self.left;
self.fonts.union_with(&block.fonts);
}
pub fn place(&mut self, origin: (f32, f32), block: &Block) {
writeln!(&mut self.buffer, "q 1.0 0.0 0.0 1.0 {:.3} {:.3} cm", origin.0, -origin.1).unwrap();
self.buffer.extend_from_slice(&block.buffer);
writeln!(&mut self.buffer, "Q").unwrap();
self.fonts.union_with(&block.fonts);
}
pub fn cursor(&self) -> (f32, f32) { (self.x, self.y) }
pub fn indent(&mut self, left: f32, right: f32) {
self.left += left;
self.right += right;
if self.x < self.left { self.x = self.left; }
}
pub fn unindent(&mut self, left: f32, right: f32) {
if self.x == self.left { self.x -= left; }
self.left -= left;
self.right -= right;
}
pub fn path(&mut self) -> PathContext<'_> {
PathContext {
output: &mut self.buffer,
buffer: Vec::new(),
}
}
pub fn line(&mut self, start: (f32, f32), stop: (f32, f32)) {
self.path().move_to(start).line_to(stop).draw(PathStyle::Stroke)
}
pub fn rectangle(&mut self, origin: (f32, f32), size: (f32, f32), style: PathStyle) {
self.path().rectangle(origin, size).draw(style)
}
pub fn rounded_rectangle(&mut self, origin: (f32, f32), size: (f32, f32), radius: f32, style: PathStyle) {
let x0 = origin.0;
let y0 = origin.1;
let x1 = x0 + size.0;
let y1 = y0 + size.1;
self.path()
.move_to((x0 + radius, y0))
.line_to((x1 - radius, y0))
.cubic_to((x1 - radius * 0.5, y0),
(x1, y0 + radius * 0.5),
(x1, y0 + radius))
.line_to((x1, y1 - radius))
.cubic_to((x1, y1 - radius * 0.5),
(x1 - radius * 0.5, y1),
(x1 - radius, y1))
.line_to((x0 + radius, y1))
.cubic_to((x0 + radius * 0.5, y1),
(x0, y1 - radius * 0.5),
(x0, y1 - radius))
.line_to((x0, y0 + radius))
.cubic_to((x0, y0 + radius * 0.5),
(x0 + radius * 0.5, y0),
(x0 + radius, y0))
.draw(style)
}
fn write_escaped_byte(buf: &mut Vec<u8>, c: u8) {
match c {
b'\\' => {
buf.push(b'\\');
buf.push(b'\\');
},
b'(' => {
buf.push(b'\\');
buf.push(b'(');
},
b')' => {
buf.push(b'\\');
buf.push(b')');
},
b'\r' => {
buf.push(b'\\');
buf.push(b'r');
},
c => buf.push(c),
}
}
fn current_font(&self) -> &Font { self.font_library.get(self.style.font_id) }
fn write_encoded_string(&mut self, text: &str) {
for c in text.chars() {
if (c as u32) >= 0x1F3FB && (c as u32) <= 0x1F3FF { continue; } let c = self.current_font().cid_of(c);
Self::write_escaped_byte(&mut self.buffer, (c >> 8) as u8);
Self::write_escaped_byte(&mut self.buffer, (c & 255) as u8);
}
}
fn do_underline(&mut self, origin: (f32, f32), text: &str) {
let font = self.current_font();
let scale = self.style.font_size / font.units_per_em as f32;
let underline_position = font.underline_position as f32 * scale;
let underline_thickness = font.underline_thickness as f32 * scale;
let text_width = self.text_width(text);
self.path()
.rectangle((origin.0, origin.1 + underline_position), (text_width, underline_thickness))
.draw(PathStyle::Fill(FillMode::NonZero));
}
pub fn text(&mut self, origin: (f32, f32), text: &str) {
if self.style.fill_color != self.style.text_color {
let text_color = self.style.text_color;
write!(&mut self.buffer, "q {} ", text_color.pdf_string(false)).unwrap();
}
write!(&mut self.buffer, "BT {:.2} {:.2} Td (", origin.0, -origin.1).unwrap();
self.write_encoded_string(text);
write!(&mut self.buffer, ") Tj ET").unwrap();
if self.style.underline {
write!(&mut self.buffer, " ").unwrap();
self.do_underline(origin, text);
}
if self.style.fill_color != self.style.text_color {
write!(&mut self.buffer, " Q").unwrap();
}
writeln!(&mut self.buffer).unwrap();
}
pub fn text_width(&self, text: &str) -> f32 {
let font = self.current_font();
let scale = self.style.font_size / font.units_per_em as f32;
let mut width: f32 = 0.0;
for c in text.chars() {
if (c as u32) >= 0x1F3FB && (c as u32) <= 0x1F3FF { continue; } let cid = font.cid_of(c);
width += font.cid_to_aw[cid as usize] as f32 * scale;
}
width
}
pub fn cell(&mut self, w: f32, text: &str, border: EnumSet<Border>, target: CursorTarget, align: TextAlign, fill: bool) {
let h = self.style.line_height;
if !self.fits(h) {
self.flush();
}
let w = if w == 0.0 {
self.width - self.right - self.x
} else { w };
if fill {
let origin = (self.x, self.y);
self.path().rectangle(origin, (w, h)).draw(PathStyle::Fill(FillMode::NonZero));
}
if !border.is_empty() {
let (x0, y0) = (self.x, self.y);
let (x1, y1) = (x0 + w, y0 + h);
let mut path = self.path();
if border.contains(Border::Left) {
path = path.move_to((x0, y0)).line_to((x0, y1));
}
if border.contains(Border::Top) {
path = path.move_to((x0, y0)).line_to((x1, y0));
}
if border.contains(Border::Right) {
path = path.move_to((x1, y0)).line_to((x1, y1));
}
if border.contains(Border::Bottom) {
path = path.move_to((x0, y1)).line_to((x1, y1));
}
path.draw(PathStyle::Stroke);
}
if !text.is_empty() {
let dx = match align {
TextAlign::Left => 0.0,
TextAlign::Right => w - self.text_width(text),
TextAlign::Center => w * 0.5 - self.text_width(text) * 0.5,
};
let origin = (self.x + dx, self.y + 0.5 * h + 0.3 * self.style.font_size);
self.text(origin, text);
}
match target {
CursorTarget::EndOfLine => self.x += w,
CursorTarget::NextLine => { self.y += h; self.x = self.left; },
CursorTarget::NextLineKeepX => self.y += h,
};
}
pub fn write_nb(&mut self, text: &str, target: CursorTarget) {
let h = self.style.line_height;
let l = self.text_width(text);
let w = self.width - self.right - self.x;
if l > w {
self.x = self.left;
self.y += h;
}
self.cell(l, text, Border::NONE, target, TextAlign::Left, false)
}
pub fn write(&mut self, text: &str, target: CursorTarget) {
let h = self.style.line_height;
let chars: Vec<char> = text.chars().collect();
let mut w = self.width - self.right - self.x;
let mut sep = None;
let mut i = 0;
let mut j = 0;
let mut l = 0.0;
let mut nl = 1;
while i < chars.len() {
let c = chars[i];
if c == '\n' {
let line: String = chars[j..i].iter().collect();
self.cell(w, &line, Border::NONE, CursorTarget::NextLineKeepX, TextAlign::Left, false);
i += 1;
sep = None;
j = i;
l = 0.0;
if nl == 1 {
self.x = self.left;
w = self.width - self.right - self.x;
}
nl += 1;
continue;
}
if c == ' ' {
sep = Some(i);
}
l += self.text_width(&String::from(c));
if l > w {
if let Some(sep) = sep {
let line: String = chars[j..sep].iter().collect();
self.cell(w, &line, Border::NONE, CursorTarget::NextLineKeepX, TextAlign::Left, false);
i = sep + 1;
} else {
if self.x > self.left {
self.x = self.left;
self.y += h;
w = self.width - self.right - self.x;
i += 1;
nl += 1;
continue;
}
if i == j {
i += 1;
}
let line: String = chars[j..i].iter().collect();
self.cell(w, &line, Border::NONE, CursorTarget::NextLineKeepX, TextAlign::Left, false);
}
sep = None;
j = i;
l = 0.0;
if nl == 1 {
self.x = self.left;
w = self.width - self.right - self.x;
}
} else {
i += 1;
}
}
if i != j {
let line: String = chars[j..i].iter().collect();
self.cell(l, &line, Border::NONE, target, TextAlign::Left, false);
}
}
pub fn ln(&mut self, h: impl Into<Option<f32>>) {
self.x = self.left;
self.y += h.into().unwrap_or(self.style.line_height);
}
pub fn image(&mut self, image: ImageRef, origin: (f32, f32), size: (f32, f32)) {
let image = image.0 + 1;
writeln!(&mut self.buffer,
"q {:.2} 0 0 {:.2} {:.2} {:.2} cm /I{} Do Q",
size.0, size.1, origin.0, -origin.1 - size.1, image).unwrap();
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum FillMode {
#[default]
NonZero,
EvenOdd,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum PathStyle {
#[default]
Stroke,
Fill(FillMode),
Both(FillMode),
}
pub struct PathContext<'a> {
output: &'a mut Vec<u8>,
buffer: Vec<u8>,
}
impl<'a> PathContext<'a> {
pub fn move_to(mut self, to: (f32, f32)) -> Self {
write!(&mut self.buffer, "{:.2} {:.2} m ", to.0, -to.1).unwrap();
self
}
pub fn line_to(mut self, to: (f32, f32)) -> Self {
write!(&mut self.buffer, "{:.2} {:.2} l ", to.0, -to.1).unwrap();
self
}
pub fn cubic_to(mut self, cp1: (f32, f32), cp2: (f32, f32), to: (f32, f32)) -> Self {
write!(&mut self.buffer, "{:.2} {:.2} {:.2} {:.2} {:.2} {:.2} c ", cp1.0, -cp1.1, cp2.0, -cp2.1, to.0, -to.1).unwrap();
self
}
pub fn rectangle(mut self, origin: (f32, f32), size: (f32, f32)) -> Self {
write!(&mut self.buffer, "{:.2} {:.2} {:.2} {:.2} re ", origin.0, -origin.1, size.0, -size.1).unwrap();
self
}
pub fn close(mut self) -> Self {
write!(&mut self.buffer, "h ").unwrap();
self
}
pub fn draw(mut self, style: PathStyle) {
match style {
PathStyle::Stroke => self.buffer.push(b'S'),
PathStyle::Fill(FillMode::NonZero) => self.buffer.push(b'f'),
PathStyle::Fill(FillMode::EvenOdd) => { self.buffer.push(b'f'); self.buffer.push(b'*'); },
PathStyle::Both(FillMode::NonZero) => self.buffer.push(b'B'),
PathStyle::Both(FillMode::EvenOdd) => { self.buffer.push(b'B'); self.buffer.push(b'*'); },
};
self.buffer.push(b'\n');
self.output.append(&mut self.buffer);
}
}
#[derive(Clone)]
pub struct Block<'f> {
font_library: &'f FontLibrary,
width: f32,
height: f32,
buffer: Vec<u8>,
fonts: BitSet,
}
impl<'f> Block<'f> {
pub fn new(font_library: &'f FontLibrary, width: f32) -> Self {
Self {
font_library,
width,
height: 0.0,
buffer: Vec::new(),
fonts: BitSet::new(),
}
}
pub fn width(&self) -> f32 { self.width }
pub fn height(&self) -> f32 { self.height }
pub fn data(&self) -> &[u8] { &self.buffer }
pub fn fonts(&self) -> &BitSet { &self.fonts }
}
impl<'f> RenderTarget<'f> for Block<'f> {
fn font_library(&self) -> &'f FontLibrary {
self.font_library
}
fn size(&self) -> (f32, Option<f32>) {
(self.width, None)
}
fn init(&self) -> Option<Block<'f>> {
None
}
fn write(&mut self, data: &[u8], height: f32, fonts: &BitSet) -> Option<Block<'f>> {
self.height += height;
self.buffer.extend_from_slice(data);
self.fonts.union_with(fonts);
None
}
}
pub trait Template {
fn header(&self, _ctx: &mut RenderContext, _page_def: &PageDef) {}
fn intro(&self, _ctx: &mut RenderContext) {}
fn footer(&self, _ctx: &mut RenderContext, _page_def: &PageDef) {}
}
pub struct Clear;
impl Template for Clear {}
#[derive(Clone)]
pub struct PageDef {
pub size: (f32, f32),
pub margin: (f32, f32, f32, f32), }
impl PageDef {
pub fn page_size(&self) -> (f32, f32) { self.size }
pub fn content_size(&self) -> (f32, f32) { (self.size.0 - self.margin.0 - self.margin.2, self.size.1 - self.margin.1 - self.margin.3) }
}
pub struct PageSet<'c, 'f> {
document: &'c mut Document<'f>,
template: &'c dyn Template,
page_def: PageDef,
intro_on_every_page: bool,
}
impl<'c, 'f> PageSet<'c, 'f> {
pub fn new<T: Template>(document: &'c mut Document<'f>, page_def: PageDef, template: &'c T, intro_on_every_page: bool) -> Self {
Self {
document,
template,
page_def,
intro_on_every_page,
}
}
}
impl<'c, 'f> RenderTarget<'f> for PageSet<'c, 'f> {
fn font_library(&self) -> &'f FontLibrary {
self.document.font_library
}
fn size(&self) -> (f32, Option<f32>) {
let size = self.page_def.content_size();
(size.0, Some(size.1))
}
fn init(&self) -> Option<Block<'f>> {
let mut block = Block::new(self.font_library(), self.page_def.content_size().0);
let mut ctx = RenderContext::new(&mut block);
self.template.intro(&mut ctx);
ctx.flush();
Some(block)
}
fn write(&mut self, data: &[u8], _height: f32, fonts: &BitSet) -> Option<Block<'f>> {
let page_def = &self.page_def;
let size = page_def.size;
let mut header = Block::new(self.font_library(), size.0);
let mut footer = Block::new(self.font_library(), size.0);
{
let mut ctx = RenderContext::new(&mut header);
self.template.header(&mut ctx, page_def);
ctx.flush();
}
{
let mut ctx = RenderContext::new(&mut footer);
self.template.footer(&mut ctx, page_def);
ctx.flush();
}
let mut buffer = Vec::new();
{
writeln!(&mut buffer, "q 1.0 0.0 0.0 1.0 0.0 {:.3} cm", size.1).unwrap();
buffer.append(&mut header.buffer);
writeln!(&mut buffer, "Q").unwrap();
}
{
writeln!(&mut buffer, "q 1.0 0.0 0.0 1.0 {:.3} {:.3} cm", page_def.margin.0, size.1 - page_def.margin.1).unwrap();
buffer.extend_from_slice(data);
writeln!(&mut buffer, "Q").unwrap();
}
{
writeln!(&mut buffer, "q 1.0 0.0 0.0 1.0 0.0 {:.3} cm", size.1).unwrap();
buffer.append(&mut footer.buffer);
writeln!(&mut buffer, "Q").unwrap();
}
self.document.pages.push(Page { buffer, size });
self.document.used_fonts.union_with(&header.fonts);
self.document.used_fonts.union_with(fonts);
self.document.used_fonts.union_with(&footer.fonts);
if self.intro_on_every_page {
let mut block = Block::new(self.font_library(), self.page_def.content_size().0);
let mut ctx = RenderContext::new(&mut block);
self.template.intro(&mut ctx);
ctx.flush();
Some(block)
} else {
None
}
}
}
struct Encoder<'a> {
document: &'a Document<'a>,
compress: bool,
offsets: Vec<usize>,
buffer: Vec<u8>,
}
impl<'a> Encoder<'a> {
fn new(document: &'a Document) -> Self {
Self {
document,
compress: true,
offsets: vec![0, 0],
buffer: Vec::new(),
}
}
fn get_offset(&self) -> usize {
self.buffer.len()
}
fn new_obj(&mut self) -> u32 {
let index = self.offsets.len() + 1;
self.offsets.push(self.get_offset());
writeln!(&mut self.buffer, "{} 0 obj", index).unwrap();
index as u32
}
fn put_stream(&mut self, data: &[u8]) {
writeln!(&mut self.buffer, "stream").unwrap();
self.buffer.extend_from_slice(data);
writeln!(&mut self.buffer).unwrap();
writeln!(&mut self.buffer, "endstream").unwrap();
}
fn put_stream_object(&mut self, data: &[u8]) -> std::io::Result<u32> {
let object_index = self.new_obj();
if self.compress {
let data = compress(data);
writeln!(&mut self.buffer, "<</Filter /FlateDecode /Length {}>>", data.len()).unwrap();
self.put_stream(&data);
} else {
writeln!(&mut self.buffer, "<</Length {}>>", data.len()).unwrap();
self.put_stream(data);
}
writeln!(&mut self.buffer, "endobj").unwrap();
Ok(object_index)
}
fn put_page(&mut self, page: Page) -> u32 {
let page_contents = self.put_stream_object(&page.buffer).unwrap();
let page_index = self.new_obj();
writeln!(&mut self.buffer, "<</Type /Page").unwrap();
writeln!(&mut self.buffer, "/Parent 1 0 R").unwrap();
writeln!(&mut self.buffer, "/Resources 2 0 R").unwrap();
if self.document.capabilities.contains(Capability::Transparency) {
writeln!(&mut self.buffer, "/Group <</Type /Group /S /Transparency /CS /DeviceRGB>>").unwrap();
}
writeln!(&mut self.buffer, "/Contents {} 0 R>>", page_contents).unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
page_index
}
fn put_pages(&mut self) -> Option<u32> {
let mut page_indices = Vec::with_capacity(self.document.pages.len());
for page in self.document.pages.clone() {
let object_index = self.put_page(page);
page_indices.push(object_index);
}
self.offsets[0] = self.get_offset();
writeln!(&mut self.buffer, "1 0 obj").unwrap();
writeln!(&mut self.buffer, "<</Type /Pages").unwrap();
write!(&mut self.buffer, "/Kids [").unwrap();
for index in &page_indices {
write!(&mut self.buffer, "{} 0 R ", index).unwrap();
}
writeln!(&mut self.buffer, "]").unwrap();
writeln!(&mut self.buffer, "/Count {}", page_indices.len()).unwrap();
if let Some(page) = self.document.pages.first() {
writeln!(&mut self.buffer, "/MediaBox [0 0 {:.2} {:.2}]", page.size.0, page.size.1).unwrap();
}
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
page_indices.first().cloned()
}
fn put_advance_widths(&mut self, font: &Font, scale: f32) {
let mut remaining_codes = Vec::new();
let mut remaining_width = Vec::new();
let mut range = 0..=0;
let mut width = font.cid_to_aw[0];
let mut i = 1;
while i < font.cid_to_aw.len() {
let c = i as u16;
let w = font.cid_to_aw[i];
if c == range.end() + 1 && w == width {
range = *range.start()..=c;
} else {
if range.start() == range.end() {
remaining_codes.push(*range.start());
remaining_width.push(width);
} else {
write!(&mut self.buffer, "{} {} {} ", range.start(), range.end(), width as f32 * scale).unwrap();
}
range = c..=c;
width = w;
}
i += 1;
}
if range.start() == range.end() {
remaining_codes.push(*range.start());
remaining_width.push(width);
} else {
write!(&mut self.buffer, "{} {} {} ", range.start(), range.end(), width as f32 * scale).unwrap();
}
let mut range = 0..=0;
let mut i = 1;
while i < remaining_codes.len() {
let c = remaining_codes[i];
if remaining_codes[*range.end()] + 1 == c {
range = *range.start()..=i;
} else {
write!(&mut self.buffer, "{} [ ", remaining_codes[*range.start()]).unwrap();
for k in range {
write!(&mut self.buffer, "{} ", remaining_width[k] as f32 * scale).unwrap();
}
write!(&mut self.buffer, "] ").unwrap();
range = i..=i;
}
i += 1;
}
if !remaining_codes.is_empty() {
write!(&mut self.buffer, "{} [ ", remaining_codes[*range.start()]).unwrap();
for k in range {
write!(&mut self.buffer, "{} ", remaining_width[k] as f32 * scale).unwrap();
}
write!(&mut self.buffer, "] ").unwrap();
}
}
fn put_font(&mut self, font: &Font) -> u32 {
let font_name = format!("MPDFAA+{}", &font.name);
let units_per_em = font.units_per_em;
let scale = 1000.0 / units_per_em as f32;
let bbox = (
font.bounding_box.0 as f32 * scale,
font.bounding_box.1 as f32 * scale,
font.bounding_box.2 as f32 * scale,
font.bounding_box.3 as f32 * scale,
);
let italic_angle = font.italic_angle.as_f32();
let is_fixed_pitch = font.is_fixed_pitch;
let ascent = font.ascent as f32 * scale;
let descent = font.descent as f32 * scale;
let default_aw = font.default_aw as f32 * scale;
let cap_height = font.cap_height as f32 * scale;
let weight_class = font.weight_class;
let stem_v = 50 + (weight_class as f32 / 65.0).powi(2) as i32;
let mut flags: u32 = 4;
if italic_angle != 0.0 { flags |= 64 };
if weight_class >= 600 { flags |= 262144 };
if is_fixed_pitch { flags |= 1 };
let font_file = self.new_obj();
writeln!(&mut self.buffer, "<</Filter /FlateDecode /Length {} /Length1 {}>>", font.data.len(), font.uncompressed_size).unwrap();
self.put_stream(&font.data);
writeln!(&mut self.buffer, "endobj").unwrap();
let system_info = self.new_obj();
writeln!(&mut self.buffer, "<</Registry (Adobe)").unwrap();
writeln!(&mut self.buffer, "/Ordering (UCS)").unwrap();
writeln!(&mut self.buffer, "/Supplement 0").unwrap();
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
let mut to_unicode = Vec::new();
writeln!(&mut to_unicode, "/CIDInit /ProcSet findresource begin").unwrap();
writeln!(&mut to_unicode, "12 dict begin").unwrap();
writeln!(&mut to_unicode, "begincmap").unwrap();
writeln!(&mut to_unicode, "/CIDSystemInfo").unwrap();
writeln!(&mut to_unicode, "<</Registry (Adobe)").unwrap();
writeln!(&mut to_unicode, "/Ordering (UCS)").unwrap();
writeln!(&mut to_unicode, "/Supplement 0").unwrap();
writeln!(&mut to_unicode, ">> def").unwrap();
writeln!(&mut to_unicode, "/CMapName /Adobe-Identity-UCS def").unwrap();
writeln!(&mut to_unicode, "/CMapType 2 def").unwrap();
writeln!(&mut to_unicode, "1 begincodespacerange").unwrap();
writeln!(&mut to_unicode, "<0000> <FFFF>").unwrap();
writeln!(&mut to_unicode, "endcodespacerange").unwrap();
writeln!(&mut to_unicode, "1 beginbfchar").unwrap();
for (i, c) in font.cid_to_char.iter().enumerate() {
writeln!(&mut to_unicode, "<{:04X}> <{:04X}>", i, *c as u32).unwrap();
}
writeln!(&mut to_unicode, "endbfchar").unwrap();
writeln!(&mut to_unicode, "endcmap").unwrap();
writeln!(&mut to_unicode, "CMapName currentdict /CMap defineresource pop").unwrap();
writeln!(&mut to_unicode, "end").unwrap();
writeln!(&mut to_unicode, "end").unwrap();
let to_unicode = {
let index = self.new_obj();
writeln!(&mut self.buffer, "<</Length {}>>", to_unicode.len()).unwrap();
self.put_stream(&to_unicode);
writeln!(&mut self.buffer, "endobj").unwrap();
index
};
let cid_to_gid_map = {
let index = self.new_obj();
writeln!(&mut self.buffer, "<</Filter /FlateDecode /Length {}>>", font.cid_to_gid.len()).unwrap();
self.put_stream(&font.cid_to_gid);
writeln!(&mut self.buffer, "endobj").unwrap();
index
};
let font_descriptor = self.new_obj();
writeln!(&mut self.buffer, "<</Type /FontDescriptor").unwrap();
writeln!(&mut self.buffer, "/FontName /{}", &font_name).unwrap();
writeln!(&mut self.buffer, "/Ascent {}", ascent.round() as i32).unwrap();
writeln!(&mut self.buffer, "/Descent {}", descent.round() as i32).unwrap();
writeln!(&mut self.buffer, "/CapHeight {}", cap_height.round() as i32).unwrap();
writeln!(&mut self.buffer, "/Flags {}", flags).unwrap();
writeln!(&mut self.buffer, "/FontBBox [{} {} {} {}]",
bbox.0.round() as i32, bbox.1.round() as i32,
bbox.2.round() as i32, bbox.3.round() as i32).unwrap();
writeln!(&mut self.buffer, "/ItalicAngle {}", italic_angle).unwrap();
writeln!(&mut self.buffer, "/StemV {}", stem_v).unwrap();
writeln!(&mut self.buffer, "/MissingWidth {}", default_aw).unwrap();
writeln!(&mut self.buffer, "/FontFile2 {} 0 R", font_file).unwrap();
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
let cid_font_type_2 = self.new_obj();
writeln!(&mut self.buffer, "<</Type /Font").unwrap();
writeln!(&mut self.buffer, "/Subtype /CIDFontType2").unwrap();
writeln!(&mut self.buffer, "/BaseFont /{}", &font_name).unwrap();
writeln!(&mut self.buffer, "/CIDSystemInfo {} 0 R", system_info).unwrap();
writeln!(&mut self.buffer, "/FontDescriptor {} 0 R", font_descriptor).unwrap();
writeln!(&mut self.buffer, "/DW {}", default_aw).unwrap();
write!(&mut self.buffer, "/W [ ").unwrap();
self.put_advance_widths(font, scale);
writeln!(&mut self.buffer, "]").unwrap();
writeln!(&mut self.buffer, "/CIDToGIDMap {} 0 R", cid_to_gid_map).unwrap();
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
let type0_font = self.new_obj();
writeln!(&mut self.buffer, "<</Type /Font").unwrap();
writeln!(&mut self.buffer, "/Subtype /Type0").unwrap();
writeln!(&mut self.buffer, "/BaseFont /{}", &font_name).unwrap();
writeln!(&mut self.buffer, "/Encoding /Identity-H").unwrap();
writeln!(&mut self.buffer, "/DescendantFonts [{} 0 R]", cid_font_type_2).unwrap();
writeln!(&mut self.buffer, "/ToUnicode {} 0 R", to_unicode).unwrap();
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
type0_font
}
fn put_fonts(&mut self) -> Vec<(u32, u32)> {
let mut font_indices = Vec::with_capacity(self.document.used_fonts.len());
for id in &self.document.used_fonts {
if id == 0 { continue; }
let font = self.document.font_library.get(id as u32);
let obj = self.put_font(font);
font_indices.push((id as u32, obj));
}
font_indices
}
fn put_images(&mut self) -> Vec<u32> {
let mut image_indices = Vec::with_capacity(self.document.images.len());
for image in &self.document.images {
let alpha_index = match &image.alpha_plane {
None => 0,
Some(alpha) => {
let index = self.new_obj();
writeln!(&mut self.buffer, "<</Type /XObject").unwrap();
writeln!(&mut self.buffer, "/Subtype /Image").unwrap();
writeln!(&mut self.buffer, "/Width {} /Height {}", image.width, image.height).unwrap();
writeln!(&mut self.buffer, "/ColorSpace /DeviceGray").unwrap();
writeln!(&mut self.buffer, "/BitsPerComponent 8").unwrap();
writeln!(&mut self.buffer, "/DecodeParams <</Predictor 15 /Colors 1 /BitsPerComponent 8 /Columns {}>>", image.width).unwrap();
writeln!(&mut self.buffer, "/Filter /FlateDecode /Length {}>>", alpha.len()).unwrap();
self.put_stream(alpha);
writeln!(&mut self.buffer, "endobj").unwrap();
index
},
};
let image_index = self.new_obj();
writeln!(&mut self.buffer, "<</Type /XObject").unwrap();
writeln!(&mut self.buffer, "/Subtype /Image").unwrap();
writeln!(&mut self.buffer, "/Width {} /Height {}", image.width, image.height).unwrap();
writeln!(&mut self.buffer, "/ColorSpace /{}", match image.color_space {
ColorSpace::Gray => "DeviceGray",
ColorSpace::Rgb => "DeviceRGB",
}).unwrap();
writeln!(&mut self.buffer, "/BitsPerComponent 8").unwrap();
if alpha_index > 0 {
writeln!(&mut self.buffer, "/SMask {} 0 R", alpha_index).unwrap();
}
writeln!(&mut self.buffer, "/DecodeParams <</Predictor 15 /Colors {} /BitsPerComponent 8 /Columns {}>>", match image.color_space {
ColorSpace::Gray => 1,
ColorSpace::Rgb => 3,
}, image.width).unwrap();
writeln!(&mut self.buffer, "/Filter /FlateDecode /Length {}>>", image.color_plane.len()).unwrap();
self.put_stream(&image.color_plane);
writeln!(&mut self.buffer, "endobj").unwrap();
image_indices.push(image_index);
}
image_indices
}
fn put_resource_dict(&mut self, font_indices: &[(u32, u32)], image_indices: &[u32]){
writeln!(&mut self.buffer, "/ProcSet [/PDF /Text /ImageB /ImageC /ImageI]").unwrap();
writeln!(&mut self.buffer, "/Font <<").unwrap();
for (i, n) in font_indices {
writeln!(&mut self.buffer, "/F{} {} 0 R", i, n).unwrap();
}
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "/XObject <<").unwrap();
for (i, n) in image_indices.iter().enumerate() {
writeln!(&mut self.buffer, "/I{} {} 0 R", i + 1, n).unwrap();
}
writeln!(&mut self.buffer, ">>").unwrap();
}
fn put_resources(&mut self) {
let font_indices = self.put_fonts();
let image_indices = self.put_images();
self.offsets[1] = self.get_offset();
writeln!(&mut self.buffer, "2 0 obj").unwrap();
writeln!(&mut self.buffer, "<<").unwrap();
self.put_resource_dict(&font_indices, &image_indices);
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
}
fn put_dict_string(&mut self, key: &str, value: &str) {
write!(&mut self.buffer, "/{} ", key).unwrap();
if value.is_ascii() {
let value = value.replace('\\', "\\\\")
.replace('(', "\\(")
.replace(')', "\\)")
.replace('\r', "\\r");
write!(&mut self.buffer, "({})", value).unwrap();
} else {
self.buffer.push(b'\xFE');
self.buffer.push(b'\xFF');
for c in value.encode_utf16() {
self.buffer.push((c >> 8) as u8);
self.buffer.push((c & 255) as u8);
}
}
writeln!(&mut self.buffer).unwrap();
}
fn put_info(&mut self) {
let metadata = self.document.metadata.clone();
self.put_dict_string("Producer", &metadata.producer);
if let Some(value) = &metadata.title {
self.put_dict_string("Title", value);
}
if let Some(value) = &metadata.author {
self.put_dict_string("Author", value);
}
if let Some(value) = &metadata.subject {
self.put_dict_string("Subject", value);
}
if let Some(value) = &metadata.keywords {
self.put_dict_string("Keywords", value);
}
if let Some(value) = &metadata.creator {
self.put_dict_string("Creator", value);
}
let creation_date = metadata.creation_date.unwrap_or_default();
let creation_date = creation_date.format("%Y%m%d%H%M%S%z").to_string();
let split = creation_date.len() - 2;
let creation_date = format!("D:{}'{}", &creation_date[..split], &creation_date[split..]);
self.put_dict_string("CreationDate", &creation_date);
}
fn put_catalog(&mut self, first_page_index: Option<u32>) {
writeln!(&mut self.buffer, "/Type /Catalog").unwrap();
writeln!(&mut self.buffer, "/Pages 1 0 R").unwrap();
if let Some(first_page_index) = first_page_index {
match self.document.zoom_mode {
ZoomMode::Default => (),
ZoomMode::FullPage => writeln!(&mut self.buffer, "/OpenAction [{} 0 R /Fit]", first_page_index).unwrap(),
ZoomMode::FullWidth => writeln!(&mut self.buffer, "/OpenAction [{} 0 R /FitH null]", first_page_index).unwrap(),
ZoomMode::Real => writeln!(&mut self.buffer, "/OpenAction [{} 0 R /XYZ null null 1]", first_page_index).unwrap(),
ZoomMode::Percent(v) => writeln!(&mut self.buffer, "/OpenAction [{} 0 R /XYZ null null {}]", first_page_index, v * 0.01).unwrap(),
};
match self.document.layout_mode {
LayoutMode::Default => (),
LayoutMode::Single => writeln!(&mut self.buffer, "/PageLayout /SinglePage").unwrap(),
LayoutMode::Continuous => writeln!(&mut self.buffer, "/PageLayout /OneColumn").unwrap(),
LayoutMode::Two => writeln!(&mut self.buffer, "/PageLayout /TwoColumnLeft").unwrap(),
}
}
}
fn put_header(&mut self) {
if self.document.capabilities.contains(Capability::Pdf1_4) {
writeln!(&mut self.buffer, "%PDF-1.4").unwrap();
} else {
writeln!(&mut self.buffer, "%PDF-1.3").unwrap();
}
}
fn put_trailer(&mut self, catalog_index: u32, info_index: u32) {
writeln!(&mut self.buffer, "/Size {}", self.offsets.len() + 1).unwrap();
writeln!(&mut self.buffer, "/Root {} 0 R", catalog_index).unwrap();
writeln!(&mut self.buffer, "/Info {} 0 R", info_index).unwrap();
}
fn run(&mut self) {
self.put_header();
let first_page_index = self.put_pages();
self.put_resources();
let info_index = self.new_obj();
writeln!(&mut self.buffer, "<<").unwrap();
self.put_info();
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
let catalog_index = self.new_obj();
writeln!(&mut self.buffer, "<<").unwrap();
self.put_catalog(first_page_index);
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "endobj").unwrap();
let offset = self.get_offset();
writeln!(&mut self.buffer, "xref").unwrap();
writeln!(&mut self.buffer, "0 {}", self.offsets.len() + 1).unwrap();
writeln!(&mut self.buffer, "0000000000 65535 f ").unwrap();
for offset in &self.offsets {
writeln!(&mut self.buffer, "{:010} 00000 n ", offset).unwrap();
}
writeln!(&mut self.buffer, "trailer").unwrap();
writeln!(&mut self.buffer, "<<").unwrap();
self.put_trailer(catalog_index, info_index);
writeln!(&mut self.buffer, ">>").unwrap();
writeln!(&mut self.buffer, "startxref").unwrap();
writeln!(&mut self.buffer, "{}", offset).unwrap();
writeln!(&mut self.buffer, "%%EOF").unwrap();
}
}