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