use crate::{Result, TextError};
use std::sync::Arc;
#[derive(Clone)]
pub enum FontData {
Owned(Arc<Vec<u8>>),
Shared(Arc<dyn AsRef<[u8]> + Send + Sync>),
}
impl FontData {
pub fn from_vec(data: Vec<u8>) -> Self {
FontData::Owned(Arc::new(data))
}
pub fn from_mapped(data: Arc<dyn AsRef<[u8]> + Send + Sync>) -> Self {
FontData::Shared(data)
}
pub fn as_bytes(&self) -> &[u8] {
match self {
FontData::Owned(data) => data.as_ref(),
FontData::Shared(data) => data.as_ref().as_ref(),
}
}
}
impl std::fmt::Debug for FontData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FontData::Owned(data) => write!(f, "FontData::Owned({} bytes)", data.len()),
FontData::Shared(data) => {
write!(
f,
"FontData::Shared({} bytes)",
data.as_ref().as_ref().len()
)
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FontWeight {
Thin = 100,
ExtraLight = 200,
Light = 300,
#[default]
Regular = 400,
Medium = 500,
SemiBold = 600,
Bold = 700,
ExtraBold = 800,
Black = 900,
}
impl FontWeight {
pub fn from_number(weight: u16) -> Self {
match weight {
0..=149 => FontWeight::Thin,
150..=249 => FontWeight::ExtraLight,
250..=349 => FontWeight::Light,
350..=449 => FontWeight::Regular,
450..=549 => FontWeight::Medium,
550..=649 => FontWeight::SemiBold,
650..=749 => FontWeight::Bold,
750..=849 => FontWeight::ExtraBold,
_ => FontWeight::Black,
}
}
pub fn to_number(self) -> u16 {
self as u16
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum FontStyle {
#[default]
Normal,
Italic,
Oblique,
}
#[derive(Debug, Clone, Copy)]
pub struct FontMetrics {
pub units_per_em: u16,
pub ascender: i16,
pub descender: i16,
pub line_gap: i16,
pub cap_height: Option<i16>,
pub x_height: Option<i16>,
pub underline_position: Option<i16>,
pub underline_thickness: Option<i16>,
}
impl FontMetrics {
pub fn line_height(&self) -> i16 {
self.ascender - self.descender + self.line_gap
}
pub fn scale(&self, value: i16, font_size: f32) -> f32 {
value as f32 * font_size / self.units_per_em as f32
}
pub fn ascender_px(&self, font_size: f32) -> f32 {
self.scale(self.ascender, font_size)
}
pub fn descender_px(&self, font_size: f32) -> f32 {
self.scale(self.descender, font_size)
}
pub fn line_height_px(&self, font_size: f32) -> f32 {
self.scale(self.line_height(), font_size)
}
}
pub struct FontFace {
data: FontData,
face_index: u32,
metrics: FontMetrics,
glyph_count: u16,
family_name: String,
weight: FontWeight,
style: FontStyle,
}
impl FontFace {
pub fn from_data(data: Vec<u8>) -> Result<Self> {
Self::from_data_with_index(data, 0)
}
pub fn from_data_with_index(data: Vec<u8>, face_index: u32) -> Result<Self> {
let font_data = FontData::from_vec(data);
Self::from_font_data(font_data, face_index)
}
pub fn from_font_data(data: FontData, face_index: u32) -> Result<Self> {
let face = ttf_parser::Face::parse(data.as_bytes(), face_index)
.map_err(|e| TextError::FontParseError(format!("{:?}", e)))?;
let metrics = FontMetrics {
units_per_em: face.units_per_em(),
ascender: face.ascender(),
descender: face.descender(),
line_gap: face.line_gap(),
cap_height: face.capital_height(),
x_height: face.x_height(),
underline_position: face.underline_metrics().map(|m| m.position),
underline_thickness: face.underline_metrics().map(|m| m.thickness),
};
let family_name = face
.names()
.into_iter()
.find(|n| n.name_id == ttf_parser::name_id::FAMILY)
.and_then(|n| n.to_string())
.unwrap_or_else(|| "Unknown".to_string());
let weight = face
.tables()
.os2
.map(|os2| FontWeight::from_number(os2.weight().to_number()))
.unwrap_or(FontWeight::Regular);
let style = if face.is_italic() {
FontStyle::Italic
} else if face.is_oblique() {
FontStyle::Oblique
} else {
FontStyle::Normal
};
let glyph_count = face.number_of_glyphs();
Ok(Self {
data,
face_index,
metrics,
glyph_count,
family_name,
weight,
style,
})
}
pub fn from_file(path: &std::path::Path) -> Result<Self> {
let data = std::fs::read(path)
.map_err(|e| TextError::FontLoadError(format!("Failed to read file: {}", e)))?;
Self::from_data(data)
}
pub fn metrics(&self) -> &FontMetrics {
&self.metrics
}
pub fn glyph_count(&self) -> u16 {
self.glyph_count
}
pub fn family_name(&self) -> &str {
&self.family_name
}
pub fn weight(&self) -> FontWeight {
self.weight
}
pub fn style(&self) -> FontStyle {
self.style
}
pub fn data(&self) -> &[u8] {
self.data.as_bytes()
}
pub fn face_index(&self) -> u32 {
self.face_index
}
pub(crate) fn as_ttf_face(&self) -> Option<ttf_parser::Face<'_>> {
ttf_parser::Face::parse(self.data.as_bytes(), self.face_index).ok()
}
pub fn glyph_id(&self, c: char) -> Option<u16> {
self.as_ttf_face()
.and_then(|face| face.glyph_index(c))
.map(|id| id.0)
}
pub fn has_glyph(&self, c: char) -> bool {
self.glyph_id(c).map(|id| id != 0).unwrap_or(false)
}
pub fn glyph_advance(&self, glyph_id: u16) -> Option<u16> {
self.as_ttf_face()
.and_then(|face| face.glyph_hor_advance(ttf_parser::GlyphId(glyph_id)))
}
}
impl std::fmt::Debug for FontFace {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FontFace")
.field("family_name", &self.family_name)
.field("weight", &self.weight)
.field("style", &self.style)
.field("glyph_count", &self.glyph_count)
.finish()
}
}
pub struct Font {
family: String,
faces: Vec<FontFace>,
}
impl Font {
pub fn new(face: FontFace) -> Self {
let family = face.family_name().to_string();
Self {
family,
faces: vec![face],
}
}
pub fn add_face(&mut self, face: FontFace) {
self.faces.push(face);
}
pub fn get_face(&self, weight: FontWeight, style: FontStyle) -> Option<&FontFace> {
if let Some(face) = self
.faces
.iter()
.find(|f| f.weight == weight && f.style == style)
{
return Some(face);
}
let style_matches: Vec<_> = self.faces.iter().filter(|f| f.style == style).collect();
if !style_matches.is_empty() {
return style_matches
.iter()
.min_by_key(|f| (f.weight.to_number() as i32 - weight.to_number() as i32).abs())
.copied();
}
self.faces
.iter()
.min_by_key(|f| (f.weight.to_number() as i32 - weight.to_number() as i32).abs())
}
pub fn default_face(&self) -> Option<&FontFace> {
self.get_face(FontWeight::Regular, FontStyle::Normal)
.or_else(|| self.faces.first())
}
pub fn family(&self) -> &str {
&self.family
}
}