use std::fs;
use std::path;
use crate::error::{Error, ErrorKind};
use crate::render;
use crate::style::Style;
use crate::Mm;
#[derive(Debug)]
pub struct FontCache {
fonts: Vec<(path::PathBuf, rusttype::Font<'static>)>,
pdf_fonts: Vec<printpdf::IndirectFontRef>,
default_font_family: Option<FontFamily>,
}
impl FontCache {
pub fn new(
default_dir: impl AsRef<path::Path>,
default_name: &str,
) -> Result<FontCache, Error> {
let mut font_cache = FontCache {
fonts: Vec::new(),
pdf_fonts: Vec::new(),
default_font_family: None,
};
font_cache.default_font_family =
Some(font_cache.load_font_family(default_dir, default_name)?);
Ok(font_cache)
}
pub fn load_font(&mut self, path: impl AsRef<path::Path>) -> Result<Font, Error> {
use std::io::Read as _;
let path = path.as_ref();
let mut font_file = fs::File::open(path).map_err(|err| {
Error::new(format!("Failed to open font file {}", path.display()), err)
})?;
let mut buf = Vec::new();
font_file.read_to_end(&mut buf).map_err(|err| {
Error::new(format!("Failed to read font file {}", path.display()), err)
})?;
let rt_font = rusttype::Font::from_bytes(buf).map_err(|err| {
Error::new(
format!("Failed to load rusttype font from file {}", path.display()),
err,
)
})?;
let font = Font::new(self.fonts.len(), &rt_font);
if font.is_ok() {
self.fonts.push((path.to_owned(), rt_font));
}
font
}
pub fn load_font_family(
&mut self,
dir: impl AsRef<path::Path>,
name: &str,
) -> Result<FontFamily, Error> {
let dir = dir.as_ref();
Ok(FontFamily {
regular: self.load_font(&dir.join(format!("{}-Regular.ttf", name)))?,
bold: self.load_font(&dir.join(format!("{}-Bold.ttf", name)))?,
italic: self.load_font(&dir.join(format!("{}-Italic.ttf", name)))?,
bold_italic: self.load_font(&dir.join(format!("{}-BoldItalic.ttf", name)))?,
})
}
pub fn load_pdf_fonts(&mut self, renderer: &render::Renderer) -> Result<(), Error> {
self.pdf_fonts.clear();
for (path, _) in &self.fonts {
self.pdf_fonts.push(renderer.load_font(path)?);
}
Ok(())
}
pub fn default_font_family(&self) -> FontFamily {
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].1
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct FontFamily {
regular: Font,
bold: Font,
italic: Font,
bold_italic: Font,
}
impl FontFamily {
pub fn get(&self, style: Style) -> Font {
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,
scale: f32,
line_height: Mm,
glyph_height: Mm,
}
impl Font {
fn new(idx: usize, rt_font: &rusttype::Font<'static>) -> Result<Font, Error> {
let scale = rt_font.units_per_em();
if scale == 0 {
return Err(Error::new(
"The font is not scalable",
ErrorKind::InvalidFont,
));
}
let scale = f32::from(scale);
let v_metrics = rt_font.v_metrics_unscaled() * (1.0 / scale);
let glyph_height = v_metrics.ascent - v_metrics.descent;
let line_height = glyph_height + v_metrics.line_gap;
Ok(Font {
idx,
scale,
line_height: printpdf::Pt(f64::from(line_height)).into(),
glyph_height: printpdf::Pt(f64::from(glyph_height)).into(),
})
}
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 glyph = font_cache
.get_rt_font(*self)
.glyph(c)
.standalone()
.get_data()
.expect("No data for standalone glyph");
let width = glyph.unit_h_metrics.advance_width / self.scale * f32::from(font_size);
Mm::from(printpdf::Pt(f64::from(width)))
}
pub fn str_width(&self, font_cache: &FontCache, s: &str, font_size: u8) -> Mm {
s.chars()
.map(|c| self.char_width(font_cache, c, font_size))
.sum()
}
}