use std::collections::HashMap;
use std::fs::File;
use std::io::Cursor;
use std::path::{Path, PathBuf};
use freetype::{face, Face, Library};
use printpdf::types::plugins::graphics::two_dimensional::font::IndirectFontRef;
use printpdf::Pt;
use crate::document::Document;
use crate::{Error, Result};
#[derive(Debug)]
pub struct Font {
freetype: Face,
printpdf: IndirectFontRef,
}
impl Font {
pub fn from_file<P: AsRef<Path>>(
path: P,
library: &Library,
document: &mut Document,
) -> Result<Font> {
let file = File::open(path.as_ref())
.map_err(|_| Error::FontNotFound(PathBuf::from(path.as_ref())))?;
Ok(Font {
freetype: library.new_face(path.as_ref(), 0)?,
printpdf: document.inner_mut().add_external_font(file)?,
})
}
pub fn from_bytes(bytes: &[u8], library: &Library, document: &mut Document) -> Result<Font> {
let cursor = Cursor::new(bytes);
Ok(Font {
freetype: library.new_memory_face(bytes.to_vec(), 0)?,
printpdf: document.inner_mut().add_external_font(cursor)?,
})
}
pub fn char_width(&self, c: char, scale: Pt) -> Pt {
let scale = scale.0;
let vert_scale = {
if let Ok(_) = self.freetype.load_char(0x0020, face::LoadFlag::NO_SCALE) {
self.freetype.glyph().metrics().vertAdvance
} else {
1000
}
};
let width = if let Ok(_) = self
.freetype
.load_char(c as usize, face::LoadFlag::NO_SCALE)
{
self.freetype.glyph().metrics().horiAdvance
} else {
0
};
Pt(width as f64 / (vert_scale as f64 / scale))
}
pub fn text_width(&self, text: &str, scale: Pt) -> Pt {
let scale = scale.0;
let vert_scale = {
if let Ok(_) = self.freetype.load_char(0x0020, face::LoadFlag::NO_SCALE) {
self.freetype.glyph().metrics().vertAdvance
} else {
1000
}
};
let sum_width = text.chars().fold(0, |acc, ch| {
if let Ok(_) = self
.freetype
.load_char(ch as usize, face::LoadFlag::NO_SCALE)
{
let glyph_w = self.freetype.glyph().metrics().horiAdvance;
acc + glyph_w
} else {
acc
}
});
Pt(sum_width as f64 / (vert_scale as f64 / scale)).into()
}
pub fn printpdf(&self) -> &IndirectFontRef {
&self.printpdf
}
}
pub struct FontConfig<'a> {
pub regular: &'a Font,
pub bold: &'a Font,
pub italic: &'a Font,
pub bold_italic: &'a Font,
}
impl<'a> FontConfig<'a> {
pub fn for_style(&self, style: FontStyle) -> &Font {
match (style.bold, style.italic) {
(false, false) => self.regular,
(true, false) => self.bold,
(false, true) => self.italic,
(true, true) => self.bold_italic,
}
}
}
pub struct FontManager {
library: Library,
fonts: HashMap<String, Font>,
}
impl FontManager {
pub fn init(document: &mut Document) -> Result<FontManager> {
let mut font_manager = FontManager {
library: Library::init()?,
fonts: HashMap::new(),
};
font_manager.add_font(include_bytes!("../assets/fonts/cmunbi.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbl.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbmo.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbmr.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbso.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbsr.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbtl.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbto.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunbx.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunci.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunit.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunobi.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunobx.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunorm.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunoti.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunrm.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunsi.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunsl.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunso.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunssdc.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunss.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunsx.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmuntb.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunti.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmuntt.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmuntx.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunui.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunvi.ttf"), document)?;
font_manager.add_font(include_bytes!("../assets/fonts/cmunvt.ttf"), document)?;
Ok(font_manager)
}
pub fn add_font(&mut self, bytes: &[u8], document: &mut Document) -> Result<()> {
let font = Font::from_bytes(bytes, &self.library, document)?;
let name = match (font.freetype.family_name(), font.freetype.style_name()) {
(Some(family), Some(style)) => format!("{} {}", family, style),
_ => {
eprintln!("Failed to create a built in font, this is a implementation error");
unreachable!();
}
};
self.fonts.insert(name, font);
Ok(())
}
pub fn get(&self, font_name: &str) -> Option<&Font> {
self.fonts.get(font_name)
}
pub fn config<'a>(
&'a self,
regular: &str,
bold: &str,
italic: &str,
bold_italic: &str,
) -> Result<FontConfig<'a>> {
Ok(FontConfig {
regular: self
.fonts
.get(regular)
.ok_or(Error::FontNotFound(PathBuf::from(regular)))?,
bold: self
.fonts
.get(bold)
.ok_or(Error::FontNotFound(PathBuf::from(bold)))?,
italic: self
.fonts
.get(italic)
.ok_or(Error::FontNotFound(PathBuf::from(italic)))?,
bold_italic: self
.fonts
.get(bold_italic)
.ok_or(Error::FontNotFound(PathBuf::from(bold_italic)))?,
})
}
pub fn default_config<'a>(&'a self) -> FontConfig<'a> {
let regular = "CMU Serif Roman";
let bold = "CMU Serif Bold";
let italic = "CMU Serif Italic";
let bold_italic = "CMU Serif BoldItalic";
match self.config(regular, bold, italic, bold_italic) {
Ok(c) => c,
Err(_) => unreachable!("Default font not found, this should never happen"),
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct FontStyle {
pub bold: bool,
pub italic: bool,
}
impl FontStyle {
pub fn regular() -> FontStyle {
FontStyle {
bold: false,
italic: false,
}
}
pub fn bold(self) -> FontStyle {
FontStyle {
bold: true,
italic: self.italic,
}
}
pub fn italic(self) -> FontStyle {
FontStyle {
bold: self.bold,
italic: true,
}
}
pub fn unbold(self) -> FontStyle {
FontStyle {
bold: false,
italic: self.italic,
}
}
pub fn unitalic(self) -> FontStyle {
FontStyle {
bold: self.bold,
italic: false,
}
}
}