mod sources;
mod vmo_stream;
use anyhow::{format_err, Error};
use byteorder::{BigEndian, ReadBytesExt};
use char_set::CharSet;
use freetype_ffi::{
FT_Add_Default_Modules, FT_Done_Face, FT_Done_Library, FT_Err_Ok, FT_Face, FT_Get_First_Char,
FT_Get_Next_Char, FT_Get_Postscript_Name, FT_Get_Sfnt_Name, FT_Get_Sfnt_Name_Count, FT_Library,
FT_New_Library, FT_Open_Face, FT_SfntName, FT_MEMORY, TT_MS_ID_SYMBOL_CS, TT_MS_ID_UNICODE_CS,
TT_MS_LANGID_ENGLISH_UNITED_STATES, TT_NAME_ID_FULL_NAME, TT_PLATFORM_MICROSOFT,
};
use std::ffi::CStr;
use std::io::Cursor;
use std::ops::Range;
use std::ptr;
pub use crate::sources::{FTOpenArgs, FontAssetSource};
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct FontInfo {
pub char_set: CharSet,
pub postscript_name: Option<String>,
pub full_name: Option<String>,
}
pub trait FontInfoLoader {
fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
where
S: TryInto<FontAssetSource, Error = E>,
E: Sync + Send + Into<Error>;
}
#[derive(Debug)]
pub struct FontInfoLoaderImpl {
library: FTLibrary,
}
impl FontInfoLoaderImpl {
pub fn new() -> Result<FontInfoLoaderImpl, Error> {
let library = FTLibrary::new()?;
Ok(Self { library })
}
pub fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
where
S: TryInto<FontAssetSource, Error = E>,
E: Sync + Send + Into<Error>,
{
let source: FontAssetSource = source.try_into().map_err(|e| e.into())?;
let open_args: FTOpenArgs = source.try_into()?;
let face = self.library.open_face(open_args, index)?;
let code_points = face.code_points().collect();
let postscript_name = face.postscript_name().ok().filter(|x| !x.is_empty());
let full_name = face.full_name()?.filter(|x| !x.is_empty());
Ok(FontInfo { char_set: CharSet::new(code_points), postscript_name, full_name })
}
}
impl FontInfoLoader for FontInfoLoaderImpl {
fn load_font_info<S, E>(&self, source: S, index: u32) -> Result<FontInfo, Error>
where
S: TryInto<FontAssetSource, Error = E>,
E: Sync + Send + Into<Error>,
{
FontInfoLoaderImpl::load_font_info(&self, source, index)
}
}
#[derive(Debug)]
struct FTLibrary {
ft_library: FT_Library,
}
impl FTLibrary {
pub fn new() -> Result<FTLibrary, Error> {
unsafe {
let mut ft_library = ptr::null_mut();
if FT_New_Library(&FT_MEMORY, &mut ft_library) != FT_Err_Ok {
return Err(format_err!("Failed to initialize FreeType library."));
}
FT_Add_Default_Modules(ft_library);
Ok(FTLibrary { ft_library })
}
}
pub fn open_face(&self, open_args: FTOpenArgs, index: u32) -> Result<FTFace, Error> {
unsafe {
let mut ft_face: FT_Face = ptr::null_mut();
let err = FT_Open_Face(self.ft_library, open_args.as_ref(), index as i64, &mut ft_face);
if err != FT_Err_Ok {
return Err(format_err!(
"Failed to parse font {} from {:?}: FreeType error {}",
index,
open_args.source,
err
));
}
Ok(FTFace { ft_face, open_args })
}
}
}
impl std::ops::Drop for FTLibrary {
fn drop(&mut self) {
unsafe {
FT_Done_Library(self.ft_library);
}
}
}
struct FTFace {
ft_face: FT_Face,
#[allow(dead_code)]
open_args: FTOpenArgs,
}
impl FTFace {
pub fn code_points<'f>(&'f self) -> CodePointsIterator<'f> {
CodePointsIterator { face: self, state: CodePointsIteratorState::Uninitialized }
}
pub fn postscript_name(&self) -> Result<String, Error> {
let postscript_name = unsafe {
let postscript_name =
FT_Get_Postscript_Name(self.ft_face) as *const std::os::raw::c_char;
let postscript_name = CStr::from_ptr(postscript_name);
postscript_name
.to_str()
.map_err(|e| format_err!("Failed to decode Postscript name. Error: {:?}", e))?
.to_string()
};
Ok(postscript_name)
}
pub fn full_name(&self) -> Result<Option<String>, Error> {
self.get_name(TT_NAME_ID_FULL_NAME)
}
fn get_name(&self, name_id: u16) -> Result<Option<String>, Error> {
for name_entry in self.sfnt_names() {
let name_entry = name_entry?;
if let Some(name) = FTFace::decode_name_if_matches(&name_entry, name_id)? {
return Ok(Some(name));
}
}
Ok(None)
}
fn sfnt_name_count(&self) -> u32 {
unsafe { FT_Get_Sfnt_Name_Count(self.ft_face) }
}
fn sfnt_names<'a>(&'a self) -> SfntNamesIterator<'a> {
SfntNamesIterator { face: self, iter: 0..self.sfnt_name_count() }
}
fn decode_name_if_matches(
name_entry: &FT_SfntName,
name_id: u16,
) -> Result<Option<String>, Error> {
if name_entry.name_id != name_id {
return Ok(None);
}
match (name_entry.platform_id, name_entry.encoding_id, name_entry.language_id) {
(TT_PLATFORM_MICROSOFT, TT_MS_ID_SYMBOL_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES)
| (TT_PLATFORM_MICROSOFT, TT_MS_ID_UNICODE_CS, TT_MS_LANGID_ENGLISH_UNITED_STATES) => {
let name = FTFace::decode_name_utf16_be(name_entry)?;
Ok(Some(name))
}
_ => Ok(None),
}
}
fn decode_name_utf16_be(name_entry: &FT_SfntName) -> Result<String, Error> {
let as_u8 = unsafe {
std::slice::from_raw_parts(name_entry.string, name_entry.string_len as usize)
};
let num_code_units = (name_entry.string_len / 2) as usize;
let mut reader = Cursor::new(as_u8);
let mut as_u16 = Vec::with_capacity(num_code_units);
for _i in 0..num_code_units {
let ch = reader.read_u16::<BigEndian>()?;
as_u16.push(ch);
}
Ok(String::from_utf16(as_u16.as_slice())?)
}
}
impl std::ops::Drop for FTFace {
fn drop(&mut self) {
unsafe {
FT_Done_Face(self.ft_face);
}
}
}
#[derive(Debug, Copy, Clone)]
enum CodePointsIteratorState {
Uninitialized,
Active(u64),
Fused,
}
pub struct CodePointsIterator<'f> {
face: &'f FTFace,
state: CodePointsIteratorState,
}
impl<'f> CodePointsIterator<'f> {
fn next_internal(&mut self, prev_code_point: Option<u64>) -> Option<u32> {
let mut glyph_index: u32 = 0;
let code_point = unsafe {
match prev_code_point {
Some(prev_code_point) => {
FT_Get_Next_Char(self.face.ft_face, prev_code_point, &mut glyph_index)
}
None => FT_Get_First_Char(self.face.ft_face, &mut glyph_index),
}
};
if glyph_index > 0 {
self.state = CodePointsIteratorState::Active(code_point);
Some(code_point as u32)
} else {
self.state = CodePointsIteratorState::Fused;
None
}
}
}
impl<'f> std::iter::Iterator for CodePointsIterator<'f> {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
match self.state.clone() {
CodePointsIteratorState::Uninitialized => self.next_internal(None),
CodePointsIteratorState::Active(prev_code_point) => {
self.next_internal(Some(prev_code_point))
}
CodePointsIteratorState::Fused => None,
}
}
}
impl<'f> std::iter::FusedIterator for CodePointsIterator<'f> {}
pub struct SfntNamesIterator<'f> {
face: &'f FTFace,
iter: Range<u32>,
}
impl<'f> SfntNamesIterator<'f> {
fn get(&self, idx: u32) -> Result<FT_SfntName, Error> {
let mut entry = FT_SfntName::default();
let err_code = unsafe { FT_Get_Sfnt_Name(self.face.ft_face, idx, &mut entry) };
if err_code == FT_Err_Ok {
Ok(entry)
} else {
Err(format_err!("FreeType error while getting name {}: {}", idx, err_code))
}
}
}
impl<'f> Iterator for SfntNamesIterator<'f> {
type Item = Result<FT_SfntName, Error>;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|idx| self.get(idx))
}
}
impl<'f> ExactSizeIterator for SfntNamesIterator<'f> {
fn len(&self) -> usize {
self.iter.len()
}
}