#![allow(clippy::match_on_vec_items)]
#![allow(clippy::cast_possible_truncation)]
pub use crate::raw::ttf::NameKind as StringKind;
use crate::{
error::ParseResult,
raw::ttf::{GlyfOutline, SimpleGlyf, TrueTypeFont},
svg::SvgExt,
};
use std::{borrow::Cow, collections::HashMap};
#[derive(Debug, Clone)]
pub struct Font {
glyphs: Vec<Glyph>,
strings: HashMap<StringKind, String>,
}
impl Font {
pub fn new(font_data: &[u8]) -> ParseResult<Self> {
let font = TrueTypeFont::new(font_data)?;
Ok(font.into())
}
pub fn from_file(path: impl AsRef<std::path::Path>) -> ParseResult<Self> {
let font_data = std::fs::read(path)?;
Self::new(&font_data)
}
#[must_use]
pub fn string(&self, kind: StringKind) -> Option<&str> {
self.strings.get(&kind).map(String::as_str)
}
#[must_use]
pub fn strings(&self) -> &HashMap<StringKind, String> {
&self.strings
}
#[must_use]
pub fn glyph(&self, codepoint: u32) -> Option<&Glyph> {
self.glyphs.iter().find(|g| g.codepoint == codepoint)
}
#[must_use]
pub fn glyph_named(&self, name: &str) -> Option<&Glyph> {
self.glyphs.iter().find(|g| g.name == name)
}
#[must_use]
pub fn glyphs(&self) -> &[Glyph] {
&self.glyphs
}
}
impl From<TrueTypeFont> for Font {
fn from(value: TrueTypeFont) -> Self {
let cmap = value.cmap_table;
let post = value.post_table;
let name = value.name_table;
let glyf = value.glyf_table;
let mut strings = HashMap::new();
for record in name.records {
strings.insert(record.name_id, record.name);
}
let mut glyphs = Vec::new();
for (glyph_index, name) in post.glyph_names.into_iter().enumerate() {
let name = Cow::Owned(name);
let glyph_index = glyph_index as u16;
let codepoint = cmap.unicode_subtable.get_codepoint(glyph_index);
let codepoint = match codepoint {
Some(c) if glyph_index == 0 => c,
Some(c) if c != 0xFFFF => c,
_ => continue,
};
let outline = match glyf[glyph_index as usize] {
GlyfOutline::Simple(ref outline) => outline.clone(),
GlyfOutline::Compound(ref outline) => outline.as_simple(&glyf),
};
let preview = GlyphPreview::Ttf(outline);
glyphs.push(Glyph {
codepoint,
name,
preview,
});
}
Self { glyphs, strings }
}
}
#[derive(Debug, Clone)]
pub enum GlyphPreview {
Ttf(SimpleGlyf),
Svg(Cow<'static, str>),
}
impl SvgExt for GlyphPreview {
fn to_svg(&self) -> String {
match self {
Self::Ttf(outline) => outline.to_svg(),
Self::Svg(svg) => svg.to_string(),
}
}
}
#[derive(Debug, Clone)]
pub struct Glyph {
codepoint: u32,
name: Cow<'static, str>,
preview: GlyphPreview,
}
impl Glyph {
#[must_use]
pub const fn new(codepoint: u32, name: &'static str, preview: GlyphPreview) -> Self {
Self {
codepoint,
name: Cow::Borrowed(name),
preview,
}
}
#[must_use]
pub fn unicode_range(&self) -> &str {
crate::unicode_range::unicode_range(self.codepoint)
}
#[must_use]
pub fn codepoint(&self) -> u32 {
self.codepoint
}
#[must_use]
pub fn char(&self) -> char {
std::char::from_u32(self.codepoint).unwrap_or(char::REPLACEMENT_CHARACTER)
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn outline(&self) -> &GlyphPreview {
&self.preview
}
#[must_use]
pub fn svg_preview(&self) -> String {
self.preview.to_svg()
}
#[cfg(feature = "extended-svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
pub fn svgz_preview(&self) -> std::io::Result<Vec<u8>> {
self.preview.to_svgz()
}
#[cfg(feature = "extended-svg")]
#[cfg_attr(docsrs, doc(cfg(feature = "extended-svg")))]
pub fn svg_dataimage_url(&self) -> std::io::Result<String> {
self.preview.to_svg_dataimage_url()
}
}
impl From<Glyph> for char {
fn from(value: Glyph) -> Self {
value.char()
}
}
impl From<&Glyph> for char {
fn from(value: &Glyph) -> Self {
value.char()
}
}
impl From<Glyph> for u32 {
fn from(value: Glyph) -> Self {
value.codepoint()
}
}
impl From<&Glyph> for u32 {
fn from(value: &Glyph) -> Self {
value.codepoint()
}
}
impl std::fmt::Display for Glyph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.char())
}
}