1use std::collections::HashMap;
24use std::path::PathBuf;
25use ttf_parser::OutlineBuilder;
26
27#[derive(Clone, Debug)]
30enum Seg {
31 Line([f32; 2]),
32 Quad([f32; 2], [f32; 2]), Cubic([f32; 2], [f32; 2], [f32; 2]) }
35
36#[derive(Clone, Debug, Default)]
37struct Contour { start: [f32; 2], segs: Vec<Seg> }
38
39#[derive(Clone, Debug, Default)]
40struct Glyph { contours: Vec<Contour>, advance: f32 }
41
42pub struct GlyphOutline {
46 pub polylines: Vec<Vec<[f32; 2]>>,
47 pub advance: f32,
48}
49
50#[derive(Default)]
53struct Collector {
54 contours: Vec<Contour>,
55 cur: Option<Contour>,
56 cp: [f32; 2],
57}
58
59impl Collector {
60 fn finish_cur(&mut self) {
61 if let Some(c) = self.cur.take() {
62 if !c.segs.is_empty() { self.contours.push(c); }
63 }
64 }
65}
66
67impl OutlineBuilder for Collector {
68 fn move_to(&mut self, x: f32, y: f32) {
69 self.finish_cur();
70 self.cp = [x, y];
71 self.cur = Some(Contour { start: [x, y], segs: Vec::new() });
72 }
73 fn line_to(&mut self, x: f32, y: f32) {
74 if let Some(c) = &mut self.cur { c.segs.push(Seg::Line([x, y])); }
75 self.cp = [x, y];
76 }
77 fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
78 if let Some(c) = &mut self.cur { c.segs.push(Seg::Quad([x1, y1], [x, y])); }
79 self.cp = [x, y];
80 }
81 fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
82 if let Some(c) = &mut self.cur { c.segs.push(Seg::Cubic([x1, y1], [x2, y2], [x, y])); }
83 self.cp = [x, y];
84 }
85 fn close(&mut self) { self.finish_cur(); }
86}
87
88fn unit(d: [f32; 2]) -> Option<[f32; 2]> {
91 let l = (d[0] * d[0] + d[1] * d[1]).sqrt();
92 if l < 1e-9 { None } else { Some([d[0] / l, d[1] / l]) }
93}
94
95fn compress_contour(start: [f32; 2], segs: &[Seg]) -> Vec<Seg> {
98 let mut out: Vec<Seg> = Vec::with_capacity(segs.len());
99 let mut cp = start; let mut line_a: Option<[f32; 2]> = None; for seg in segs {
102 match seg {
103 Seg::Line(p) => {
104 if let (Some(a), Some(Seg::Line(_))) = (line_a, out.last()) {
105 let d1 = unit([cp[0] - a[0], cp[1] - a[1]]);
106 let d2 = unit([p[0] - cp[0], p[1] - cp[1]]);
107 if let (Some(d1), Some(d2)) = (d1, d2) {
108 let cross = (d1[0] * d2[1] - d1[1] * d2[0]).abs();
109 let dot = d1[0] * d2[0] + d1[1] * d2[1];
110 if cross < 2.0e-3 && dot > 0.0 {
111 *out.last_mut().unwrap() = Seg::Line(*p); cp = *p;
113 continue;
114 }
115 }
116 }
117 out.push(Seg::Line(*p));
118 line_a = Some(cp);
119 cp = *p;
120 }
121 Seg::Quad(c, p) => { out.push(Seg::Quad(*c, *p)); cp = *p; line_a = None; }
122 Seg::Cubic(a, b, p) => { out.push(Seg::Cubic(*a, *b, *p)); cp = *p; line_a = None; }
123 }
124 }
125 out
126}
127
128fn flat_quad(p0: [f32; 2], c: [f32; 2], p1: [f32; 2], tol: f32, out: &mut Vec<[f32; 2]>) {
131 let dx = p1[0] - p0[0]; let dy = p1[1] - p0[1];
133 let d = ((c[0] - p0[0]) * dy - (c[1] - p0[1]) * dx).abs();
134 let chord2 = dx * dx + dy * dy;
135 if d * d <= tol * tol * chord2 || chord2 < 1e-12 {
136 out.push(p1);
137 return;
138 }
139 let m = |a: [f32; 2], b: [f32; 2]| [(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5];
140 let p01 = m(p0, c); let p12 = m(c, p1); let mid = m(p01, p12);
141 flat_quad(p0, p01, mid, tol, out);
142 flat_quad(mid, p12, p1, tol, out);
143}
144
145fn flat_cubic(p0: [f32; 2], c1: [f32; 2], c2: [f32; 2], p1: [f32; 2], tol: f32, out: &mut Vec<[f32; 2]>) {
146 let dx = p1[0] - p0[0]; let dy = p1[1] - p0[1];
147 let d1 = ((c1[0] - p0[0]) * dy - (c1[1] - p0[1]) * dx).abs();
148 let d2 = ((c2[0] - p0[0]) * dy - (c2[1] - p0[1]) * dx).abs();
149 let chord2 = dx * dx + dy * dy;
150 if (d1 + d2) * (d1 + d2) <= tol * tol * chord2 || chord2 < 1e-12 {
151 out.push(p1);
152 return;
153 }
154 let m = |a: [f32; 2], b: [f32; 2]| [(a[0] + b[0]) * 0.5, (a[1] + b[1]) * 0.5];
155 let p01 = m(p0, c1); let p12 = m(c1, c2); let p23 = m(c2, p1);
156 let p012 = m(p01, p12); let p123 = m(p12, p23); let mid = m(p012, p123);
157 flat_cubic(p0, p01, p012, mid, tol, out);
158 flat_cubic(mid, p123, p23, p1, tol, out);
159}
160
161pub struct VectorFont {
164 bytes: Vec<u8>,
165 name: String,
166 upm: f32,
167 ascent: f32, descent: f32, weight: Option<f32>,
172 cache_dir: PathBuf,
173 glyphs: HashMap<char, Glyph>,
174}
175
176impl VectorFont {
177 pub fn from_path(path: &str) -> Result<Self, String> {
179 Self::from_path_weight(path, None)
180 }
181
182 pub fn from_path_weight(path: &str, weight: Option<f32>) -> Result<Self, String> {
185 let bytes = std::fs::read(path).map_err(|e| format!("{path}: {e}"))?;
186 let name = std::path::Path::new(path)
187 .file_stem().map(|s| s.to_string_lossy().into_owned())
188 .unwrap_or_else(|| "font".into());
189 Self::from_bytes(bytes, &name, weight)
190 }
191
192 pub fn from_bytes(bytes: Vec<u8>, name: &str, weight: Option<f32>) -> Result<Self, String> {
193 let face = ttf_parser::Face::parse(&bytes, 0).map_err(|e| format!("{e:?}"))?;
194 let upm = face.units_per_em() as f32;
195 let ascent = face.ascender() as f32 / upm;
196 let descent = face.descender() as f32 / upm;
197 let dir = match weight {
198 Some(w) => format!("{name}@{}", w as i32),
199 None => name.to_string(),
200 };
201 let cache_dir = PathBuf::from("cache").join("fonts").join(dir);
202 Ok(Self {
203 bytes,
204 name: name.to_string(),
205 upm, ascent, descent,
206 weight,
207 cache_dir,
208 glyphs: HashMap::new(),
209 })
210 }
211
212 pub fn ascent(&self) -> f32 { self.ascent }
213 pub fn descent(&self) -> f32 { self.descent }
214
215 fn ensure(&mut self, ch: char) {
218 if self.glyphs.contains_key(&ch) { return; }
219 let file = self.cache_dir.join(format!("{}.ling", ch as u32));
220 if let Ok(text) = std::fs::read_to_string(&file) {
221 if let Some(g) = parse_glyph_ling(&text) {
222 self.glyphs.insert(ch, g);
223 return;
224 }
225 }
226 let g = self.extract(ch);
227 let _ = std::fs::create_dir_all(&self.cache_dir);
228 let _ = std::fs::write(&file, serialize_glyph_ling(&self.name, ch, &g));
229 self.glyphs.insert(ch, g);
230 }
231
232 fn extract(&self, ch: char) -> Glyph {
234 let mut face = match ttf_parser::Face::parse(&self.bytes, 0) {
235 Ok(f) => f,
236 Err(_) => return Glyph { contours: vec![], advance: 0.5 },
237 };
238 if let Some(w) = self.weight {
240 let _ = face.set_variation(ttf_parser::Tag::from_bytes(b"wght"), w);
241 }
242 let gid = match face.glyph_index(ch) {
243 Some(g) => g,
244 None => return Glyph { contours: vec![], advance: 0.5 },
245 };
246 let advance = face.glyph_hor_advance(gid).map(|a| a as f32 / self.upm).unwrap_or(0.5);
247
248 let mut col = Collector::default();
249 face.outline_glyph(gid, &mut col);
250 col.finish_cur();
251
252 let upm = self.upm;
253 let n = |p: [f32; 2]| [p[0] / upm, p[1] / upm];
254 let contours = col.contours.into_iter().map(|c| {
255 let start = n(c.start);
256 let segs: Vec<Seg> = c.segs.iter().map(|s| match s {
257 Seg::Line(p) => Seg::Line(n(*p)),
258 Seg::Quad(a, p) => Seg::Quad(n(*a), n(*p)),
259 Seg::Cubic(a, b, p) => Seg::Cubic(n(*a), n(*b), n(*p)),
260 }).collect();
261 let segs = compress_contour(start, &segs);
262 Contour { start, segs }
263 }).collect();
264
265 Glyph { contours, advance }
266 }
267
268 pub fn advance(&mut self, ch: char) -> f32 {
270 self.ensure(ch);
271 self.glyphs[&ch].advance
272 }
273
274 pub fn measure(&mut self, text: &str, px: f32) -> f32 {
276 text.chars().map(|c| self.advance(c)).sum::<f32>() * px
277 }
278
279 pub fn glyph_outline(&mut self, ch: char, tol_em: f32) -> GlyphOutline {
282 self.ensure(ch);
283 let g = &self.glyphs[&ch];
284 let tol = tol_em.max(1e-5);
285 let mut polylines = Vec::with_capacity(g.contours.len());
286 for c in &g.contours {
287 let mut pl = Vec::new();
288 let mut cur = c.start;
289 pl.push(cur);
290 for s in &c.segs {
291 match s {
292 Seg::Line(p) => { pl.push(*p); cur = *p; }
293 Seg::Quad(ctrl, p) => { flat_quad(cur, *ctrl, *p, tol, &mut pl); cur = *p; }
294 Seg::Cubic(a, b, p) => { flat_cubic(cur, *a, *b, *p, tol, &mut pl); cur = *p; }
295 }
296 }
297 if pl.len() > 1 { pl.push(c.start); }
299 polylines.push(pl);
300 }
301 GlyphOutline { polylines, advance: g.advance }
302 }
303}
304
305fn serialize_glyph_ling(font: &str, ch: char, g: &Glyph) -> String {
308 let mut s = String::new();
309 s.push_str(&format!(
310 "# ling glyph — font={font} cp={} char={} adv={:.4}\n",
311 ch as u32, ch, g.advance
312 ));
313 for c in &g.contours {
314 s.push_str(&format!("M {:.4} {:.4}\n", c.start[0], c.start[1]));
315 for seg in &c.segs {
316 match seg {
317 Seg::Line(p) => s.push_str(&format!("L {:.4} {:.4}\n", p[0], p[1])),
318 Seg::Quad(a, p) =>
319 s.push_str(&format!("Q {:.4} {:.4} {:.4} {:.4}\n", a[0], a[1], p[0], p[1])),
320 Seg::Cubic(a, b, p) =>
321 s.push_str(&format!("C {:.4} {:.4} {:.4} {:.4} {:.4} {:.4}\n",
322 a[0], a[1], b[0], b[1], p[0], p[1])),
323 }
324 }
325 s.push_str("Z\n");
326 }
327 s
328}
329
330fn parse_glyph_ling(text: &str) -> Option<Glyph> {
331 let mut advance = 0.5f32;
332 let mut contours: Vec<Contour> = Vec::new();
333 let mut cur: Option<Contour> = None;
334 for line in text.lines() {
335 let line = line.trim();
336 if line.is_empty() { continue; }
337 if let Some(rest) = line.strip_prefix('#') {
338 if let Some(i) = rest.find("adv=") {
339 if let Ok(v) = rest[i + 4..].split_whitespace().next().unwrap_or("").parse::<f32>() {
340 advance = v;
341 }
342 }
343 continue;
344 }
345 let mut it = line.split_whitespace();
346 let op = it.next()?;
347 let nums: Vec<f32> = it.filter_map(|t| t.parse::<f32>().ok()).collect();
348 match op {
349 "M" => {
350 if let Some(c) = cur.take() { contours.push(c); }
351 cur = Some(Contour { start: [*nums.first()?, *nums.get(1)?], segs: Vec::new() });
352 }
353 "L" => { if let Some(c) = &mut cur { c.segs.push(Seg::Line([*nums.first()?, *nums.get(1)?])); } }
354 "Q" => { if let Some(c) = &mut cur {
355 c.segs.push(Seg::Quad([*nums.first()?, *nums.get(1)?], [*nums.get(2)?, *nums.get(3)?])); } }
356 "C" => { if let Some(c) = &mut cur {
357 c.segs.push(Seg::Cubic([*nums.first()?, *nums.get(1)?], [*nums.get(2)?, *nums.get(3)?], [*nums.get(4)?, *nums.get(5)?])); } }
358 "Z" => { if let Some(c) = cur.take() { contours.push(c); } }
359 _ => {}
360 }
361 }
362 if let Some(c) = cur.take() { contours.push(c); }
363 Some(Glyph { contours, advance })
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 #[test]
371 fn collinear_lines_merge() {
372 let segs = vec![Seg::Line([0.5, 0.0]), Seg::Line([1.0, 0.0]), Seg::Line([1.0, 1.0])];
373 let out = compress_contour([0.0, 0.0], &segs);
374 assert_eq!(out.len(), 2);
376 match out[0] { Seg::Line(p) => assert_eq!(p, [1.0, 0.0]), _ => panic!() }
377 }
378
379 #[test]
380 fn glyph_ling_roundtrips() {
381 let g = Glyph {
382 advance: 0.6,
383 contours: vec![Contour {
384 start: [0.1, 0.0],
385 segs: vec![Seg::Line([0.4, 0.7]), Seg::Quad([0.5, 0.8], [0.6, 0.7]), Seg::Line([0.9, 0.0])],
386 }],
387 };
388 let text = serialize_glyph_ling("Test", 'A', &g);
389 let back = parse_glyph_ling(&text).unwrap();
390 assert!((back.advance - 0.6).abs() < 1e-3);
391 assert_eq!(back.contours.len(), 1);
392 assert_eq!(back.contours[0].segs.len(), 3);
393 assert!(matches!(back.contours[0].segs[1], Seg::Quad(..)));
395 }
396}