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 let Some(fit) = self.fit
140 && (!fit.height.is_finite() || fit.height <= 0.0 || !fit.center.is_finite())
141 {
142 errors.push("fit.height must be positive and fit values must be finite".to_string());
143 }
144 if errors.is_empty() {
145 Ok(())
146 } else {
147 Err(errors)
148 }
149 }
150
151 fn has_glyph(&self, glyph: u32) -> bool {
153 (glyph as usize) < self.glyphs.len()
154 }
155
156 fn validate_chars(&self, errors: &mut Vec<String>) {
157 for (i, m) in self.chars.iter().enumerate() {
158 if char::from_u32(m.char).is_none() {
159 errors.push(format!("chars[{i}].char is not a Unicode scalar value"));
160 }
161 if !self.has_glyph(m.glyph) {
162 errors.push(format!(
163 "chars[{i}].glyph {} does not name a glyph",
164 m.glyph
165 ));
166 }
167 }
168 }
169
170 fn validate_ligatures(&self, errors: &mut Vec<String>) {
171 for (i, ligature) in self.ligatures.iter().enumerate() {
172 if ligature.glyphs.len() < 2 {
173 errors.push(format!("ligatures[{i}] must contain at least two glyphs"));
174 }
175 for &glyph in ligature
176 .glyphs
177 .iter()
178 .chain(std::iter::once(&ligature.glyph))
179 {
180 if !self.has_glyph(glyph) {
181 errors.push(format!("ligatures[{i}] references missing glyph {glyph}"));
182 }
183 }
184 }
185 }
186
187 fn validate_machine(&self, i: usize, machine: &Machine, errors: &mut Vec<String>) {
190 let mut classes = std::collections::HashSet::new();
191 let mut members = std::collections::HashSet::new();
192 for class in &machine.classes {
193 if !classes.insert(class.class) {
194 errors.push(format!("machines[{i}] repeats class {}", class.class));
195 }
196 for &glyph in &class.glyphs {
197 if !self.has_glyph(glyph) {
198 errors.push(format!(
199 "machines[{i}] class {} references missing glyph {glyph}",
200 class.class
201 ));
202 }
203 if !members.insert(glyph) {
204 errors.push(format!(
205 "machines[{i}] assigns glyph {glyph} to more than one class"
206 ));
207 }
208 }
209 }
210 let mut transitions = std::collections::HashSet::new();
211 for t in &machine.transitions {
212 if !classes.contains(&t.class) {
213 errors.push(format!(
214 "machines[{i}] transition references missing class {}",
215 t.class
216 ));
217 }
218 if !transitions.insert((t.state, t.class)) {
219 errors.push(format!(
220 "machines[{i}] repeats transition from state {} for class {}",
221 t.state, t.class
222 ));
223 }
224 if let Some(glyph) = t.glyph
225 && !self.has_glyph(glyph)
226 {
227 errors.push(format!(
228 "machines[{i}] transition references missing glyph {glyph}"
229 ));
230 }
231 }
232 }
233
234 fn validate_kerning(&self, errors: &mut Vec<String>) {
235 let mut kerning = std::collections::HashSet::new();
236 for kern in &self.kerning {
237 if !self.has_glyph(kern.left) || !self.has_glyph(kern.right) {
238 errors.push(format!(
239 "kerning pair ({}, {}) references a missing glyph",
240 kern.left, kern.right
241 ));
242 }
243 if !kerning.insert((kern.left, kern.right)) {
244 errors.push(format!(
245 "kerning pair ({}, {}) is repeated",
246 kern.left, kern.right
247 ));
248 }
249 }
250 }
251
252 pub fn from_bytes(bytes: &[u8]) -> Result<Font, String> {
254 let font = yog_data::read_bytes::<Font>(bytes).map_err(|e| format!("{e:?}"))?;
255 font.validate().map_err(|errors| errors.join("; "))?;
256 Ok(font)
257 }
258
259 pub fn to_bytes(&self) -> Vec<u8> {
261 yog_data::binary::encode(&self.write())
262 }
263
264 pub fn glyph_of(&self, c: char) -> Option<u32> {
266 let c = c as u32;
267 self.chars.iter().find(|m| m.char == c).map(|m| m.glyph)
268 }
269}