1use yog_data::Schema;
10use yog_graphics::Op;
11
12#[derive(Schema, Debug, Clone, PartialEq, Default)]
15pub struct Font {
16 pub units_per_em: u32,
19 pub ascent: i32,
21 pub descent: i32,
23 #[data(default)]
25 pub line_gap: i32,
26 pub glyphs: Vec<Glyph>,
28 pub chars: Vec<CharGlyph>,
30 #[data(default)]
32 pub ligatures: Vec<Ligature>,
33 #[data(default)]
35 pub machines: Vec<Machine>,
36 #[data(default)]
38 pub kerning: Vec<Kern>,
39 pub fit: Option<Fit>,
42}
43
44#[derive(Schema, Debug, Clone, PartialEq, Default)]
46pub struct Glyph {
47 #[data(default)]
49 pub name: String,
50 #[data(default)]
52 pub advance: i32,
53 #[data(default)]
55 pub ops: Vec<Op>,
56 pub entry: Option<[i32; 2]>,
60 pub exit: Option<[i32; 2]>,
62}
63
64#[derive(Schema, Debug, Clone, Copy, PartialEq)]
66pub struct CharGlyph {
67 pub char: u32,
68 pub glyph: u32,
69}
70
71#[derive(Schema, Debug, Clone, PartialEq)]
73pub struct Ligature {
74 pub glyphs: Vec<u32>,
75 pub glyph: u32,
76}
77
78#[derive(Schema, Debug, Clone, PartialEq, Default)]
84pub struct Machine {
85 pub classes: Vec<GlyphClass>,
87 pub transitions: Vec<Transition>,
88}
89
90#[derive(Schema, Debug, Clone, PartialEq)]
91pub struct GlyphClass {
92 pub class: u32,
93 pub glyphs: Vec<u32>,
94}
95
96#[derive(Schema, Debug, Clone, Copy, PartialEq)]
99pub struct Transition {
100 pub state: u32,
101 pub class: u32,
102 pub next: u32,
103 pub glyph: Option<u32>,
104}
105
106#[derive(Schema, Debug, Clone, Copy, PartialEq)]
108pub struct Kern {
109 pub left: u32,
110 pub right: u32,
111 pub x: i32,
112}
113
114#[derive(Schema, Debug, Clone, Copy, PartialEq)]
117pub struct Fit {
118 pub height: f32,
119 pub center: f32,
120}
121
122impl Font {
123 pub fn validate(&self) -> Result<(), Vec<String>> {
126 let mut errors = Vec::new();
127 if self.units_per_em == 0 {
128 errors.push("units_per_em must be greater than zero".to_string());
129 }
130 if self.glyphs.is_empty() {
131 errors.push("a font must have at least one glyph".to_string());
132 }
133 self.validate_chars(&mut errors);
134 self.validate_ligatures(&mut errors);
135 for (i, machine) in self.machines.iter().enumerate() {
136 self.validate_machine(i, machine, &mut errors);
137 }
138 self.validate_kerning(&mut errors);
139 if !self.chars.is_sorted_by_key(|c| c.char)
140 || !self.kerning.is_sorted_by_key(|k| (k.left, k.right))
141 {
142 errors.push("chars and kerning must be sorted (Font::normalize)".to_string());
143 }
144 if let Some(fit) = self.fit
145 && (!fit.height.is_finite() || fit.height <= 0.0 || !fit.center.is_finite())
146 {
147 errors.push("fit.height must be positive and fit values must be finite".to_string());
148 }
149 if errors.is_empty() {
150 Ok(())
151 } else {
152 Err(errors)
153 }
154 }
155
156 fn has_glyph(&self, glyph: u32) -> bool {
158 (glyph as usize) < self.glyphs.len()
159 }
160
161 fn validate_chars(&self, errors: &mut Vec<String>) {
162 for (i, m) in self.chars.iter().enumerate() {
163 if char::from_u32(m.char).is_none() {
164 errors.push(format!("chars[{i}].char is not a Unicode scalar value"));
165 }
166 if !self.has_glyph(m.glyph) {
167 errors.push(format!(
168 "chars[{i}].glyph {} does not name a glyph",
169 m.glyph
170 ));
171 }
172 }
173 }
174
175 fn validate_ligatures(&self, errors: &mut Vec<String>) {
176 for (i, ligature) in self.ligatures.iter().enumerate() {
177 if ligature.glyphs.len() < 2 {
178 errors.push(format!("ligatures[{i}] must contain at least two glyphs"));
179 }
180 for &glyph in ligature
181 .glyphs
182 .iter()
183 .chain(std::iter::once(&ligature.glyph))
184 {
185 if !self.has_glyph(glyph) {
186 errors.push(format!("ligatures[{i}] references missing glyph {glyph}"));
187 }
188 }
189 }
190 }
191
192 fn validate_machine(&self, i: usize, machine: &Machine, errors: &mut Vec<String>) {
195 let mut classes = std::collections::HashSet::new();
196 let mut members = std::collections::HashSet::new();
197 for class in &machine.classes {
198 if !classes.insert(class.class) {
199 errors.push(format!("machines[{i}] repeats class {}", class.class));
200 }
201 for &glyph in &class.glyphs {
202 if !self.has_glyph(glyph) {
203 errors.push(format!(
204 "machines[{i}] class {} references missing glyph {glyph}",
205 class.class
206 ));
207 }
208 if !members.insert(glyph) {
209 errors.push(format!(
210 "machines[{i}] assigns glyph {glyph} to more than one class"
211 ));
212 }
213 }
214 }
215 let mut transitions = std::collections::HashSet::new();
216 for t in &machine.transitions {
217 if !classes.contains(&t.class) {
218 errors.push(format!(
219 "machines[{i}] transition references missing class {}",
220 t.class
221 ));
222 }
223 if !transitions.insert((t.state, t.class)) {
224 errors.push(format!(
225 "machines[{i}] repeats transition from state {} for class {}",
226 t.state, t.class
227 ));
228 }
229 if let Some(glyph) = t.glyph
230 && !self.has_glyph(glyph)
231 {
232 errors.push(format!(
233 "machines[{i}] transition references missing glyph {glyph}"
234 ));
235 }
236 }
237 }
238
239 fn validate_kerning(&self, errors: &mut Vec<String>) {
240 let mut kerning = std::collections::HashSet::new();
241 for kern in &self.kerning {
242 if !self.has_glyph(kern.left) || !self.has_glyph(kern.right) {
243 errors.push(format!(
244 "kerning pair ({}, {}) references a missing glyph",
245 kern.left, kern.right
246 ));
247 }
248 if !kerning.insert((kern.left, kern.right)) {
249 errors.push(format!(
250 "kerning pair ({}, {}) is repeated",
251 kern.left, kern.right
252 ));
253 }
254 }
255 }
256
257 pub fn from_bytes(bytes: &[u8]) -> Result<Font, String> {
259 let mut font = yog_data::read_bytes::<Font>(bytes).map_err(|e| format!("{e:?}"))?;
260 font.normalize();
261 font.validate().map_err(|errors| errors.join("; "))?;
262 Ok(font)
263 }
264
265 pub fn normalize(&mut self) {
270 self.chars.sort_unstable_by_key(|c| c.char);
271 self.kerning.sort_unstable_by_key(|k| (k.left, k.right));
272 self.ligatures
273 .sort_by_key(|l| (l.glyphs.first().copied(), std::cmp::Reverse(l.glyphs.len())));
274 for m in &mut self.machines {
275 for class in &mut m.classes {
276 class.glyphs.sort_unstable();
277 }
278 m.transitions.sort_unstable_by_key(|t| (t.state, t.class));
279 }
280 }
281
282 pub fn to_bytes(&self) -> Vec<u8> {
284 yog_data::binary::encode(&self.write())
285 }
286
287 pub fn glyph_of(&self, c: char) -> Option<u32> {
289 let c = c as u32;
290 let at = self.chars.binary_search_by_key(&c, |m| m.char).ok()?;
291 Some(self.chars[at].glyph)
292 }
293
294 pub fn kern(&self, left: u32, right: u32) -> i32 {
296 self.kerning
297 .binary_search_by_key(&(left, right), |k| (k.left, k.right))
298 .map_or(0, |at| self.kerning[at].x)
299 }
300
301 pub fn ligatures_from(&self, first: u32) -> &[Ligature] {
303 let from = self
304 .ligatures
305 .partition_point(|l| l.glyphs.first().is_some_and(|&g| g < first));
306 let to = self
307 .ligatures
308 .partition_point(|l| l.glyphs.first().is_some_and(|&g| g <= first));
309 &self.ligatures[from..to]
310 }
311}