use thiserror::Error;
use std::io::Write;
use std::ops::RangeInclusive;
use std::collections::HashMap;
use bimap::BiMap;
use enumset::{ EnumSet, EnumSetType };
use flate2::{ Compress, Compression, FlushCompress };
use chrono::{ DateTime, Utc };
use range_set::RangeSet;
use image::DynamicImage;
use mchr::Pullable;
use mttf::FontFile;
use mttf::core::{ RandomAccess, Fixed };
use mttf::common::{ Platform, PlatformEncoding, WindowsEncoding };
use mttf::name::{ NamingTable, NameKind };
use mttf::os_2::Os2MetricsTable;
use mttf::cmap::{ CharacterToGlyphMap, Subtable, SegmentMappingTable, SegmentCoverageTable };
use mttf::gsub::{ GlyphSubstitutionTable, LookupTable };
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(Debug, EnumSetType)]
pub enum Script {
Default,
Latin,
}
pub type ScriptSet = EnumSet<Script>;
impl Script {
pub fn default_set() -> ScriptSet {
Self::Default | Self::Latin
}
pub fn tag(&self) -> &'static [u8; 4] {
match self {
Self::Default => b"DFLT",
Self::Latin => b"latn",
}
}
}
#[derive(Debug, EnumSetType)]
pub enum Language {
German,
}
pub type LanguageSet = EnumSet<Language>;
impl Language {
pub fn default_set() -> LanguageSet {
Self::German.into()
}
pub fn tag(&self) -> &'static [u8; 4] {
match self {
Self::German => b"DEU ",
}
}
}
#[derive(Debug, EnumSetType)]
pub enum Feature {
StandardLigatures,
LocalizedForms,
GlyphComposition,
SmallCapitals,
Subscript,
Superscript,
}
pub type FeatureSet = EnumSet<Feature>;
impl Feature {
pub fn default_set() -> FeatureSet {
Feature::StandardLigatures | Feature::LocalizedForms | Feature::GlyphComposition
}
pub fn tag(&self) -> &'static [u8; 4] {
match self {
Self::StandardLigatures => b"liga",
Self::LocalizedForms => b"locl",
Self::GlyphComposition => b"ccmp",
Self::SmallCapitals => b"smcp",
Self::Subscript => b"subs",
Self::Superscript => b"sups",
}
}
pub fn from_tag(tag: &[u8; 4]) -> Option<Feature> {
match tag {
b"liga" => Some(Self::StandardLigatures),
b"locl" => Some(Self::LocalizedForms),
b"ccmp" => Some(Self::GlyphComposition),
b"smcp" => Some(Self::SmallCapitals),
b"subs" => Some(Self::Subscript),
b"sups" => Some(Self::Superscript),
_ => None,
}
}
}
#[derive(Error, Debug)]
pub enum FontError {
#[error("failed to read font: {0}")]
ReadError(#[from] mttf::ReadError),
#[error("failed to parse font: {0}")]
StringError(#[from] mttf::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>, glyph_count: u16,
units_per_em: u16,
bounding_box: (i16, i16, i16, i16),
underline_position: i16,
underline_thickness: i16,
italic_angle: Fixed,
is_fixed_pitch: bool,
ascent: i16,
descent: i16,
default_aw: u16,
cap_height: i16,
weight_class: u16,
char_to_glyph: HashMap<char, u16>,
glyph_to_char: HashMap<u16, char>,
glyph_to_aw: Vec<u16>,
gsub: Option<Vec<u8>>,
}
impl Font {
pub fn glyph_count(&self) -> u16 { self.glyph_count }
pub fn units_per_em(&self) -> u16 { self.units_per_em }
pub fn bounding_box(&self) -> (i16, i16, i16, i16) { self.bounding_box }
pub fn underline_position(&self) -> i16 { self.underline_position }
pub fn underline_thickness(&self) -> i16 { self.underline_thickness }
pub fn italic_angle(&self) -> Fixed { self.italic_angle }
pub fn is_fixed_pitch(&self) -> bool { self.is_fixed_pitch }
pub fn ascent(&self) -> i16 { self.ascent }
pub fn descent(&self) -> i16 { self.descent }
pub fn default_aw(&self) -> u16 { self.default_aw }
pub fn cap_height(&self) -> i16 { self.cap_height }
pub fn weight_class(&self) -> u16 { self.weight_class }
pub fn char_to_glyph(&self, c: char) -> u16 { self.char_to_glyph.get(&c).copied().unwrap_or_default() }
pub fn glyph_to_char(&self, glyph: u16) -> char { self.glyph_to_char.get(&glyph).copied().unwrap_or_default() }
pub fn glyph_to_aw(&self, glyph: u16) -> u16 { self.glyph_to_aw[glyph as usize] }
pub fn glyph_substitution_table(&self) -> Option<GlyphSubstitutionTable<'_>> {
self.gsub.as_ref().map(|x| GlyphSubstitutionTable::try_from(x.as_ref()).unwrap())
}
fn get_gsub_table_indices(gsub: GlyphSubstitutionTable<'_>, scripts: ScriptSet, languages: LanguageSet, features: FeatureSet) -> Vec<u16> {
let mut systems = Vec::new();
if let Ok(list) = gsub.script_list() {
for script in scripts {
if let Ok(Some(script)) = list.find(script.tag()) {
if let Ok(system) = script.default_system() {
systems.push(system);
}
for lang in languages {
if let Ok(Some(system)) = script.additional_systems().find(lang.tag()) {
systems.push(system);
}
}
}
}
}
if systems.is_empty() {
return vec![];
}
let mut feature_indices = Vec::new();
for system in systems {
if let Some(feature) = system.required_feature() {
feature_indices.push(feature);
}
for feature in system.optional_features().iter() {
feature_indices.push(feature);
}
}
if feature_indices.is_empty() {
return vec![];
}
let mut table_indices = Vec::new();
if let Ok(list) = gsub.feature_list() {
for feature_index in feature_indices {
if let Ok(feature) = list.get(feature_index) {
if let Some(feature_enum) = Feature::from_tag(feature.tag().as_ref()) {
if features.contains(feature_enum) {
for table_index in feature.lookup_tables().iter() {
table_indices.push(table_index);
}
}
}
}
}
}
table_indices
}
pub fn glyph_string(&self, text: &str, scripts: ScriptSet, languages: LanguageSet, features: FeatureSet) -> Vec<u16> {
let mut glyphs: Vec<u16> = text.chars().map(|x| self.char_to_glyph(x)).collect();
let gsub = match self.glyph_substitution_table() {
Some(gsub) => gsub,
None => return glyphs,
};
let table_indices = Self::get_gsub_table_indices(gsub, scripts, languages, features);
if table_indices.is_empty() {
return glyphs;
}
let mut single = Vec::new();
let mut ligature = Vec::new();
if let Ok(list) = gsub.lookup_list() {
for table_index in table_indices {
if let Ok(table) = list.get(table_index) {
match table {
LookupTable::Single(table) => for table in table.subtables().flatten() {
single.push(table);
},
LookupTable::Ligature(table) => for table in table.subtables().flatten() {
ligature.push(table);
},
}
}
}
}
if single.is_empty() && ligature.is_empty() {
return glyphs;
}
let mut i = 0;
while i < glyphs.len() {
for table in &single {
if let Ok(Some(subst)) = table.map(glyphs[i]) {
glyphs[i] = subst;
}
}
'ligature: for table in &ligature {
if let Ok(Some(set)) = table.get_by_prefix(glyphs[i]) {
for lig in set.iter().flatten() {
let end = i + 1 + lig.component_glyphs().len();
if end <= glyphs.len() {
let mut matches = true;
for (k, g) in lig.component_glyphs().iter().enumerate() {
if glyphs[i + 1 + k] != g {
matches = false;
break;
}
}
if matches {
glyphs.splice(i..end, std::iter::once(lig.ligature_glyph()));
break 'ligature;
}
}
}
}
}
i += 1;
}
glyphs
}
pub fn glyph_string_width(&self, glyphs: &[u16]) -> u32 {
glyphs.iter().map(|&x| self.glyph_to_aw(x) as u32).sum()
}
}
#[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 maxp = font.maximum_profile(true)?;
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 gsub = match font.glyph_substitution_table(true) {
Ok(gsub) => Some(gsub),
Err(mttf::ReadError::TableNotFound) => None,
Err(err) => Err(err)?,
};
let family = Self::get_family(name)?;
let variant = Self::get_variant(name)?;
let font_name = Self::get_font_name(name);
let glyph_count = maxp.num_glyphs();
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_glyph = HashMap::new();
let mut glyph_to_char = HashMap::new();
let mut glyph_to_aw = Vec::with_capacity(maxp.num_glyphs() as usize);
if let Some(cmap) = Self::get_unicode_full_cmap(cmap) {
for group in cmap.groups() {
for (char_code, glyph) in group.chars().zip(group.glyphs()) {
let ch = char::from_u32(char_code).unwrap();
char_to_glyph.insert(ch, glyph as u16);
glyph_to_char.insert(glyph as u16, ch);
}
}
} else if let Some(cmap) = Self::get_unicode_bmp_cmap(cmap) {
for seg in cmap.segments() {
for char_code in seg.chars() {
let ch = char::from_u32(char_code as u32).unwrap();
let glyph = seg.map(char_code);
char_to_glyph.insert(ch, glyph);
glyph_to_char.insert(glyph, ch);
}
}
} else {
return Err(FontError::NoSupportedCharacterToGlyphMap);
}
for hm in hmtx.iter() {
glyph_to_aw.push(hm.advance_width);
}
let gsub = gsub.map(|x| x.bytes().to_vec());
let font = Font {
name: font_name,
uncompressed_size: data.len(),
data: compress(data),
glyph_count,
units_per_em,
bounding_box,
underline_position,
underline_thickness,
italic_angle,
is_fixed_pitch,
ascent,
descent,
default_aw,
cap_height,
weight_class,
char_to_glyph,
glyph_to_char,
glyph_to_aw,
gsub,
};
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()?.buffered().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()?.buffered().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.buffered().collect()); },
NameKind::FullName => if let Ok(name) = name.text() { full_name = Some(name.buffered().collect()); },
NameKind::PostScriptName => if let Ok(name) = name.text() { post_script_name = Some(name.buffered().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 len(&self) -> usize {
self.fonts.len()
}
pub fn is_empty(&self) -> bool {
self.fonts.is_empty()
}
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"),
}
}
}
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 type CapabilitySet = EnumSet<Capability>;
pub type GlyphSet = RangeSet<[RangeInclusive<u16>; 32]>;
pub struct Document<'f> {
pages: Vec<Page>,
font_library: &'f FontLibrary,
used_glyphs: Vec<GlyphSet>,
images: Vec<Image>,
zoom_mode: ZoomMode,
layout_mode: LayoutMode,
metadata: Metadata,
capabilities: CapabilitySet,
}
impl<'f> Document<'f> {
pub fn new(font_library: &'f FontLibrary) -> Self {
Self {
pages: Vec::new(),
font_library,
used_glyphs: vec![GlyphSet::new(); font_library.fonts.len()],
images: Vec::new(),
zoom_mode: ZoomMode::default(),
layout_mode: LayoutMode::default(),
metadata: Metadata::default(),
capabilities: CapabilitySet::empty(),
}
}
pub fn font_library(&self) -> &'f FontLibrary { self.font_library }
pub fn used_glyphs(&self) -> &[GlyphSet] { &self.used_glyphs }
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 images(&self) -> &[Image] { &self.images }
pub fn set_zoom_mode(&mut self, zoom_mode: ZoomMode) { self.zoom_mode = zoom_mode; }
pub fn zoom_mode(&self) -> ZoomMode { self.zoom_mode }
pub fn set_layout_mode(&mut self, layout_mode: LayoutMode) { self.layout_mode = layout_mode; }
pub fn layout_mode(&self) -> LayoutMode { self.layout_mode }
pub fn metadata(&mut self) -> &mut Metadata { &mut self.metadata }
pub fn capabilities(&self) -> CapabilitySet { self.capabilities }
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,
}
pub type BorderSet = EnumSet<Border>;
#[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 scripts: ScriptSet,
pub languages: LanguageSet,
pub features: FeatureSet,
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,
scripts: Script::default_set(),
languages: Language::default_set(),
features: Feature::default_set(),
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, used_glyphs: &[GlyphSet]) -> 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>,
used_glyphs: Vec<GlyphSet>,
}
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(),
used_glyphs: vec![GlyphSet::new(); font_library.fonts.len()],
};
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.used_glyphs);
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;
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 enable_scripts(&mut self, scripts: impl Into<ScriptSet>) {
let scripts = scripts.into();
self.style.scripts.insert_all(scripts);
}
pub fn disable_scripts(&mut self, scripts: impl Into<ScriptSet>) {
let scripts = scripts.into();
self.style.scripts.remove_all(scripts);
}
pub fn enable_languages(&mut self, languages: impl Into<LanguageSet>) {
let languages = languages.into();
self.style.languages.insert_all(languages);
}
pub fn disable_languages(&mut self, languages: impl Into<LanguageSet>) {
let languages = languages.into();
self.style.languages.remove_all(languages);
}
pub fn enable_features(&mut self, features: impl Into<FeatureSet>) {
let features = features.into();
self.style.features.insert_all(features);
}
pub fn disable_features(&mut self, features: impl Into<FeatureSet>) {
let features = features.into();
self.style.features.remove_all(features);
}
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;
for i in 0..self.font_library.fonts.len() {
for range in block.used_glyphs[i].as_ref().iter() {
self.used_glyphs[i].insert_range(range.clone());
}
}
}
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();
for i in 0..self.font_library.fonts.len() {
for range in block.used_glyphs[i].as_ref().iter() {
self.used_glyphs[i].insert_range(range.clone());
}
}
}
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, f32, f32, f32), style: PathStyle) {
self.path().rounded_rectangle(origin, size, radius).draw(style)
}
fn current_font(&self) -> &Font { self.font_library.get(self.style.font_id) }
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 write_encoded_string(&mut self, glyphs: &[u16]) {
let output = &mut self.buffer;
let used_glyphs = &mut self.used_glyphs[self.style.font_id as usize - 1];
for &glyph in glyphs {
Self::write_escaped_byte(output, (glyph >> 8) as u8);
Self::write_escaped_byte(output, (glyph & 255) as u8);
used_glyphs.insert(glyph);
}
}
fn do_underline(&mut self, origin: (f32, f32), glyphs: &[u16]) {
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 width = font.glyph_string_width(glyphs) as f32 * scale;
self.path()
.rectangle((origin.0, origin.1 + underline_position), (width, underline_thickness))
.draw(PathStyle::Fill(FillMode::NonZero));
}
pub fn text(&mut self, origin: (f32, f32), text: &str) {
if self.style.font_id == 0 { return; }
let font = self.font_library.get(self.style.font_id);
let glyphs = font.glyph_string(text, self.style.scripts, self.style.languages, self.style.features);
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(&glyphs);
write!(&mut self.buffer, ") Tj ET").unwrap();
if self.style.underline {
write!(&mut self.buffer, " ").unwrap();
self.do_underline(origin, &glyphs);
}
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 {
if self.style.font_id == 0 { return 0.0; }
let font = self.font_library.get(self.style.font_id);
let glyphs = font.glyph_string(text, self.style.scripts, self.style.languages, self.style.features);
let width = font.glyph_string_width(&glyphs);
width as f32 * self.style.font_size / self.current_font().units_per_em as f32
}
pub fn cell(&mut self, w: f32, text: &str, border: impl Into<BorderSet>, target: CursorTarget, align: TextAlign, fill: bool) {
let border = border.into();
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, BorderSet::empty(), target, TextAlign::Left, false)
}
pub fn write(&mut self, mut text: &str, target: CursorTarget) {
let mut line_space = self.width - self.right - self.x;
'restart: while !text.is_empty() {
let mut line_length = 0;
while let Some(offset) = text[line_length..].find(' ') {
let sep = line_length + offset;
let width = self.text_width(&text[0..sep]);
if line_length > 0 && width > line_space {
self.cell(line_space, &text[0..line_length], BorderSet::empty(), CursorTarget::NextLine, TextAlign::Left, false);
text = &text[line_length..];
line_space = self.width - self.right - self.x;
continue 'restart;
} else {
line_length = sep + 1;
}
}
if let Some(offset) = text[line_length..].find('\n') {
let sep = line_length + offset;
let width = self.text_width(&text[0..sep]);
if width > line_space {
self.cell(line_space, &text[0..line_length], BorderSet::empty(), CursorTarget::NextLine, TextAlign::Left, false);
text = &text[line_length..];
line_space = self.width - self.right - self.x;
} else {
self.cell(line_space, &text[0..sep], BorderSet::empty(), CursorTarget::NextLine, TextAlign::Left, false);
text = &text[sep + 1..];
line_space = self.width - self.right - self.x;
}
continue 'restart;
}
{
let width = self.text_width(&text[0..]);
if line_length > 0 && width > line_space {
self.cell(line_space, &text[0..line_length], BorderSet::empty(), CursorTarget::NextLine, TextAlign::Left, false);
text = &text[line_length..];
line_space = self.width - self.right - self.x;
} else {
self.cell(width, &text[0..], BorderSet::empty(), target, TextAlign::Left, false);
break;
}
}
}
}
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 rounded_rectangle(self, origin: (f32, f32), size: (f32, f32), radius: (f32, f32, f32, f32)) -> Self {
let x0 = origin.0;
let y0 = origin.1;
let x1 = x0 + size.0;
let y1 = y0 + size.1;
self.move_to((x0 + radius.0, y0))
.line_to((x1 - radius.1, y0))
.cubic_to((x1 - radius.1 * 0.5, y0),
(x1, y0 + radius.1 * 0.5),
(x1, y0 + radius.1))
.line_to((x1, y1 - radius.2))
.cubic_to((x1, y1 - radius.2 * 0.5),
(x1 - radius.2 * 0.5, y1),
(x1 - radius.2, y1))
.line_to((x0 + radius.3, y1))
.cubic_to((x0 + radius.3 * 0.5, y1),
(x0, y1 - radius.3 * 0.5),
(x0, y1 - radius.3))
.line_to((x0, y0 + radius.0))
.cubic_to((x0, y0 + radius.0 * 0.5),
(x0 + radius.0 * 0.5, y0),
(x0 + radius.0, y0))
.close()
}
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);
}
fn eat_float(path: &mut &str) -> f32 {
let mut s = *path;
s = s.trim_start();
if let Some(end) = s.find(|x: char| x != '.' && !x.is_ascii_digit()) {
*path = &s[end..];
s[0..end].parse().unwrap()
} else {
*path = &s[s.len()..];
s.parse().unwrap()
}
}
pub fn svg(mut self, mut path: &str) -> Self {
let mut prev_x = 0.0;
let mut prev_y = 0.0;
path = path.trim_start();
while let Some(op) = path.chars().next() {
(_, path) = path.split_at(1);
match op {
'M' => {
let x = Self::eat_float(&mut path);
let y = Self::eat_float(&mut path);
self = self.move_to((x, y));
prev_x = x;
prev_y = y;
},
'L' => {
let x = Self::eat_float(&mut path);
let y = Self::eat_float(&mut path);
self = self.line_to((x, y));
prev_x = x;
prev_y = y;
},
'H' => {
let x = Self::eat_float(&mut path);
self = self.line_to((x, prev_y));
prev_x = x;
},
'V' => {
let y = Self::eat_float(&mut path);
self = self.line_to((prev_x, y));
prev_y = y;
},
'C' => {
let x1 = Self::eat_float(&mut path);
let y1 = Self::eat_float(&mut path);
let x2 = Self::eat_float(&mut path);
let y2 = Self::eat_float(&mut path);
let x3 = Self::eat_float(&mut path);
let y3 = Self::eat_float(&mut path);
self = self.cubic_to((x1, y1), (x2, y2), (x3, y3));
prev_x = x3;
prev_y = y3;
},
'Z' => {
self = self.close();
},
x => panic!("unsupported vector operator: {}", x),
}
path = path.trim_start();
}
self
}
}
#[derive(Clone)]
pub struct Block<'f> {
font_library: &'f FontLibrary,
width: f32,
height: f32,
buffer: Vec<u8>,
used_glyphs: Vec<GlyphSet>,
}
impl<'f> Block<'f> {
pub fn new(font_library: &'f FontLibrary, width: f32) -> Self {
Self {
font_library,
width,
height: 0.0,
buffer: Vec::new(),
used_glyphs: vec![GlyphSet::new(); font_library.len()],
}
}
pub fn width(&self) -> f32 { self.width }
pub fn height(&self) -> f32 { self.height }
pub fn data(&self) -> &[u8] { &self.buffer }
pub fn used_glyphs(&self) -> &[GlyphSet] { &self.used_glyphs }
}
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, used_glyphs: &[GlyphSet]) -> Option<Block<'f>> {
self.height += height;
self.buffer.extend_from_slice(data);
for (i, glyphs) in used_glyphs.iter().enumerate() {
for range in glyphs.as_ref().iter() {
self.used_glyphs[i].insert_range(range.clone());
}
}
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, used_glyphs: &[GlyphSet]) -> 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 });
for (i, glyphs) in self.document.used_glyphs.iter_mut().enumerate() {
for range in header.used_glyphs[i].as_ref().iter() {
glyphs.insert_range(range.clone());
}
for range in used_glyphs[i].as_ref().iter() {
glyphs.insert_range(range.clone());
}
for range in footer.used_glyphs[i].as_ref().iter() {
glyphs.insert_range(range.clone());
}
}
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 {
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.glyph_to_aw[0];
let mut i = 1;
while i < font.glyph_to_aw.len() {
let c = i as u16;
let w = font.glyph_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, used_glyphs: &GlyphSet) -> 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 (&glyph, &ch) in font.glyph_to_char.iter() {
let ch = ch as u32;
if ch <= 0xFFFF {
writeln!(&mut to_unicode, "<{:04X}> <{:04X}>", glyph, ch).unwrap();
} else {
let hi = 0xD800 | (ch >> 10);
let lo = 0xDC00 | (ch & 0x3FF);
writeln!(&mut to_unicode, "<{:04X}> <{:04X}{:04X}>", glyph, hi, lo).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 mut map = Vec::with_capacity(font.glyph_count as usize * 2);
for glyph in 0..font.glyph_count {
if used_glyphs.contains(glyph) {
map.push((glyph >> 8) as u8);
map.push((glyph & 255) as u8);
} else {
map.push(0);
map.push(0);
}
}
let map = compress(&map);
let index = self.new_obj();
writeln!(&mut self.buffer, "<</Filter /FlateDecode /Length {}>>", map.len()).unwrap();
self.put_stream(&map);
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_glyphs.len());
for (i, glyphs) in self.document.used_glyphs.iter().enumerate() {
if !glyphs.is_empty() {
let id = i as u32 + 1;
let font = self.document.font_library.get(id);
let obj = self.put_font(font, glyphs);
font_indices.push((id, 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();
}
}