use std::fmt;
use std::fs;
use std::path;
use crate::error::{Context as _, Error, ErrorKind};
use crate::render;
use crate::style::Style;
use crate::Mm;
#[derive(Debug)]
pub struct FontCache {
fonts: Vec<FontData>,
pdf_fonts: Vec<printpdf::IndirectFontRef>,
default_font_family: Option<FontFamily<Font>>,
}
impl FontCache {
pub fn new(default_font_family: FontFamily<FontData>) -> FontCache {
let mut font_cache = FontCache {
fonts: Vec::new(),
pdf_fonts: Vec::new(),
default_font_family: None,
};
font_cache.default_font_family = Some(font_cache.add_font_family(default_font_family));
font_cache
}
pub fn add_font(&mut self, font_data: FontData) -> Font {
let is_builtin = match &font_data.raw_data {
RawFontData::Builtin(_) => true,
RawFontData::Embedded(_) => false,
};
let font = Font::new(self.fonts.len(), is_builtin, &font_data.rt_font);
self.fonts.push(font_data);
font
}
pub fn add_font_family(&mut self, family: FontFamily<FontData>) -> FontFamily<Font> {
FontFamily {
regular: self.add_font(family.regular),
bold: self.add_font(family.bold),
italic: self.add_font(family.italic),
bold_italic: self.add_font(family.bold_italic),
}
}
pub fn load_pdf_fonts(&mut self, renderer: &render::Renderer) -> Result<(), Error> {
self.pdf_fonts.clear();
for font in &self.fonts {
let pdf_font = match &font.raw_data {
RawFontData::Builtin(builtin) => renderer.add_builtin_font(*builtin)?,
RawFontData::Embedded(data) => renderer.add_embedded_font(&data)?,
};
self.pdf_fonts.push(pdf_font);
}
Ok(())
}
pub fn default_font_family(&self) -> FontFamily<Font> {
self.default_font_family
.expect("Invariant violated: no default font family for FontCache")
}
pub fn get_pdf_font(&self, font: Font) -> Option<&printpdf::IndirectFontRef> {
self.pdf_fonts.get(font.idx)
}
pub fn get_rt_font(&self, font: Font) -> &rusttype::Font<'static> {
&self.fonts[font.idx].rt_font
}
}
#[derive(Clone, Debug)]
pub struct FontData {
rt_font: rusttype::Font<'static>,
raw_data: RawFontData,
}
impl FontData {
pub fn new(data: Vec<u8>, builtin: Option<printpdf::BuiltinFont>) -> Result<FontData, Error> {
let raw_data = if let Some(builtin) = builtin {
RawFontData::Builtin(builtin)
} else {
RawFontData::Embedded(data.clone())
};
let rt_font = rusttype::Font::from_bytes(data).context("Failed to read rusttype font")?;
if rt_font.units_per_em() == 0 {
Err(Error::new(
"The font is not scalable",
ErrorKind::InvalidFont,
))
} else {
Ok(FontData { rt_font, raw_data })
}
}
pub fn load(
path: impl AsRef<path::Path>,
builtin: Option<printpdf::BuiltinFont>,
) -> Result<FontData, Error> {
let data = fs::read(path.as_ref())
.with_context(|| format!("Failed to open font file {}", path.as_ref().display()))?;
FontData::new(data, builtin)
}
}
#[derive(Clone, Debug)]
enum RawFontData {
Builtin(printpdf::BuiltinFont),
Embedded(Vec<u8>),
}
#[derive(Clone, Copy, Debug)]
enum FontStyle {
Regular,
Bold,
Italic,
BoldItalic,
}
impl FontStyle {
fn name(&self) -> &'static str {
match self {
FontStyle::Regular => "Regular",
FontStyle::Bold => "Bold",
FontStyle::Italic => "Italic",
FontStyle::BoldItalic => "BoldItalic",
}
}
}
impl fmt::Display for FontStyle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Builtin {
Times,
Helvetica,
Courier,
}
impl Builtin {
fn style(&self, style: FontStyle) -> printpdf::BuiltinFont {
match self {
Builtin::Times => match style {
FontStyle::Regular => printpdf::BuiltinFont::TimesRoman,
FontStyle::Bold => printpdf::BuiltinFont::TimesBold,
FontStyle::Italic => printpdf::BuiltinFont::TimesItalic,
FontStyle::BoldItalic => printpdf::BuiltinFont::TimesBoldItalic,
},
Builtin::Helvetica => match style {
FontStyle::Regular => printpdf::BuiltinFont::Helvetica,
FontStyle::Bold => printpdf::BuiltinFont::HelveticaBold,
FontStyle::Italic => printpdf::BuiltinFont::HelveticaOblique,
FontStyle::BoldItalic => printpdf::BuiltinFont::HelveticaBoldOblique,
},
Builtin::Courier => match style {
FontStyle::Regular => printpdf::BuiltinFont::Courier,
FontStyle::Bold => printpdf::BuiltinFont::CourierBold,
FontStyle::Italic => printpdf::BuiltinFont::CourierOblique,
FontStyle::BoldItalic => printpdf::BuiltinFont::CourierBoldOblique,
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FontFamily<T: Clone + fmt::Debug> {
pub regular: T,
pub bold: T,
pub italic: T,
pub bold_italic: T,
}
impl<T: Clone + Copy + fmt::Debug + PartialEq> FontFamily<T> {
pub fn get(&self, style: Style) -> T {
if style.is_bold() && style.is_italic() {
self.bold_italic
} else if style.is_bold() {
self.bold
} else if style.is_italic() {
self.italic
} else {
self.regular
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Font {
idx: usize,
is_builtin: bool,
scale: rusttype::Scale,
line_height: Mm,
glyph_height: Mm,
}
impl Font {
fn new(idx: usize, is_builtin: bool, rt_font: &rusttype::Font<'static>) -> Font {
let units_per_em = rt_font.units_per_em();
assert!(units_per_em != 0);
let units_per_em = f32::from(units_per_em);
let v_metrics = rt_font.v_metrics_unscaled();
let glyph_height = (v_metrics.ascent - v_metrics.descent) / units_per_em;
let scale = rusttype::Scale::uniform(glyph_height);
let line_height = glyph_height + v_metrics.line_gap / units_per_em;
Font {
idx,
is_builtin,
scale,
line_height: printpdf::Pt(f64::from(line_height)).into(),
glyph_height: printpdf::Pt(f64::from(glyph_height)).into(),
}
}
pub fn is_builtin(&self) -> bool {
self.is_builtin
}
pub fn get_line_height(&self, font_size: u8) -> Mm {
self.line_height * f64::from(font_size)
}
pub fn glyph_height(&self, font_size: u8) -> Mm {
self.glyph_height * f64::from(font_size)
}
pub fn char_width(&self, font_cache: &FontCache, c: char, font_size: u8) -> Mm {
let advance_width = font_cache
.get_rt_font(*self)
.glyph(c)
.scaled(self.scale)
.h_metrics()
.advance_width;
Mm::from(printpdf::Pt(f64::from(
advance_width * f32::from(font_size),
)))
}
pub fn str_width(&self, font_cache: &FontCache, s: &str, font_size: u8) -> Mm {
let str_width: Mm = font_cache
.get_rt_font(*self)
.glyphs_for(s.chars())
.map(|g| g.scaled(self.scale).h_metrics().advance_width)
.map(|w| Mm::from(printpdf::Pt(f64::from(w * f32::from(font_size)))))
.sum();
let kerning_width: Mm = self
.kerning(font_cache, s.chars())
.into_iter()
.map(|val| val * f32::from(font_size))
.map(|val| Mm::from(printpdf::Pt(f64::from(val))))
.sum();
str_width + kerning_width
}
pub fn kerning<I>(&self, font_cache: &FontCache, iter: I) -> Vec<f32>
where
I: IntoIterator<Item = char>,
{
let font = font_cache.get_rt_font(*self);
font.glyphs_for(iter.into_iter())
.scan(None, |last, g| {
let pos = if let Some(last) = last {
Some(font.pair_kerning(self.scale, *last, g.id()))
} else {
Some(0.0)
};
*last = Some(g.id());
pos
})
.collect()
}
pub fn glyph_ids<I>(&self, font_cache: &FontCache, iter: I) -> Vec<u16>
where
I: IntoIterator<Item = char>,
{
let font = font_cache.get_rt_font(*self);
font.glyphs_for(iter.into_iter())
.map(|g| g.id().0 as u16)
.collect()
}
}
fn from_file(
dir: impl AsRef<path::Path>,
name: &str,
style: FontStyle,
builtin: Option<Builtin>,
) -> Result<FontData, Error> {
let builtin = builtin.map(|b| b.style(style));
FontData::load(
&dir.as_ref().join(format!("{}-{}.ttf", name, style)),
builtin,
)
}
pub fn from_files(
dir: impl AsRef<path::Path>,
name: &str,
builtin: Option<Builtin>,
) -> Result<FontFamily<FontData>, Error> {
let dir = dir.as_ref();
Ok(FontFamily {
regular: from_file(dir, name, FontStyle::Regular, builtin)?,
bold: from_file(dir, name, FontStyle::Bold, builtin)?,
italic: from_file(dir, name, FontStyle::Italic, builtin)?,
bold_italic: from_file(dir, name, FontStyle::BoldItalic, builtin)?,
})
}