1use std::fmt;
2use std::sync::atomic::{AtomicU32, Ordering};
3use std::sync::{Arc, OnceLock};
4
5use skrifa::instance::{LocationRef, Size};
6use skrifa::{FontRef, MetadataProvider};
7
8pub const BUNDLED_MONO: &[u8] = include_bytes!("../fonts/JetBrainsMono-Regular.ttf");
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub struct FontId(u32);
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
18pub struct GlyphId(pub u32);
19
20#[derive(Debug, Clone, Copy, PartialEq)]
22pub struct LineMetrics {
23 pub ascent: f32,
25 pub descent: f32,
27 pub line_gap: f32,
29}
30
31impl LineMetrics {
32 pub fn line_height(&self) -> f32 {
34 self.ascent - self.descent + self.line_gap
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum FontError {
41 Parse,
43}
44
45impl fmt::Display for FontError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::Parse => f.write_str("the data is not a readable OpenType/TrueType font"),
49 }
50 }
51}
52
53impl std::error::Error for FontError {}
54
55static NEXT_FONT_ID: AtomicU32 = AtomicU32::new(1);
56
57pub type FontBytes = Arc<dyn AsRef<[u8]> + Send + Sync>;
60
61#[derive(Clone)]
64pub struct Font {
65 data: FontBytes,
66 index: u32,
67 id: FontId,
68}
69
70impl fmt::Debug for Font {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.debug_struct("Font")
74 .field("id", &self.id)
75 .field("index", &self.index)
76 .field("bytes", &self.bytes().len())
77 .finish()
78 }
79}
80
81impl Font {
82 pub fn new(data: impl Into<Arc<[u8]>>) -> Result<Self, FontError> {
88 let data: Arc<[u8]> = data.into();
89 Self::from_shared(Arc::new(data), 0)
90 }
91
92 pub fn from_shared(data: FontBytes, index: u32) -> Result<Self, FontError> {
100 FontRef::from_index((*data).as_ref(), index).map_err(|_| FontError::Parse)?;
101 let id = FontId(NEXT_FONT_ID.fetch_add(1, Ordering::Relaxed));
103 Ok(Self { data, index, id })
104 }
105
106 pub fn bundled_mono() -> Result<Self, FontError> {
113 static BUNDLED: OnceLock<Result<Font, FontError>> = OnceLock::new();
114 BUNDLED
115 .get_or_init(|| Self::from_shared(Arc::new(BUNDLED_MONO), 0))
116 .clone()
117 }
118
119 pub fn id(&self) -> FontId {
121 self.id
122 }
123
124 pub fn index(&self) -> u32 {
126 self.index
127 }
128
129 pub fn bytes(&self) -> &[u8] {
131 (*self.data).as_ref()
132 }
133
134 pub fn shared_bytes(&self) -> FontBytes {
136 Arc::clone(&self.data)
137 }
138
139 pub(crate) fn font_ref(&self) -> Option<FontRef<'_>> {
143 FontRef::from_index(self.bytes(), self.index).ok()
144 }
145
146 pub fn glyph_id(&self, ch: char) -> Option<GlyphId> {
148 let glyph = self.font_ref()?.charmap().map(ch)?;
149 (glyph.to_u32() != 0).then_some(GlyphId(glyph.to_u32()))
151 }
152
153 pub fn advance(&self, glyph: GlyphId, px: f32) -> f32 {
155 self.font_ref()
156 .and_then(|font| {
157 font.glyph_metrics(Size::new(px), LocationRef::default())
158 .advance_width(skrifa::GlyphId::new(glyph.0))
159 })
160 .unwrap_or(0.0)
161 }
162
163 pub fn glyph_extent(&self, glyph: GlyphId, px: f32) -> Option<[f32; 2]> {
167 let font = self.font_ref()?;
168 let metrics = font.glyph_metrics(Size::new(px), LocationRef::default());
169 let bounds = metrics.bounds(skrifa::GlyphId::new(glyph.0))?;
170 Some([
171 (bounds.x_max - bounds.x_min).max(0.0),
172 (bounds.y_max - bounds.y_min).max(0.0),
173 ])
174 }
175
176 pub fn line_metrics(&self, px: f32) -> LineMetrics {
178 let Some(font) = self.font_ref() else {
179 return LineMetrics {
180 ascent: 0.0,
181 descent: 0.0,
182 line_gap: 0.0,
183 };
184 };
185 let metrics = font.metrics(Size::new(px), LocationRef::default());
186 LineMetrics {
187 ascent: metrics.ascent,
188 descent: metrics.descent,
189 line_gap: metrics.leading,
190 }
191 }
192}