1use crate::error::MathError;
17use crate::layout::Engine;
18use crate::mbox::{Layout, PathContour, PathSeg};
19use crate::node::Span;
20
21pub fn resolve_paths(engine: &Engine, layout: &Layout) -> Result<Vec<PathContour>, MathError> {
29 let mut out = Vec::new();
30 for glyph in &layout.glyphs {
31 let Some(font) = engine.faces().font(glyph.face) else {
32 return Err(MathError::UnmappedChar {
33 ch: glyph.ch,
34 span: glyph.span,
35 });
36 };
37 let outline = font
38 .glyph_outline(glyph.gid)
39 .map_err(|_| MathError::UnmappedChar {
40 ch: glyph.ch,
41 span: glyph.span,
42 })?;
43 let upm = f64::from(font.units_per_em.max(1));
44 let s = glyph.size / upm;
45 for contour in &outline.contours {
46 let mut segments = Vec::with_capacity(contour.segments.len());
47 let start = (glyph.x + contour.start.x * s, glyph.y + contour.start.y * s);
48 for seg in &contour.segments {
49 segments.push(match seg {
50 fmd_font::outline::Segment::Line { to } => PathSeg::Line {
51 to: (glyph.x + to.x * s, glyph.y + to.y * s),
52 },
53 fmd_font::outline::Segment::Quad { ctrl, to } => PathSeg::Quad {
54 ctrl: (glyph.x + ctrl.x * s, glyph.y + ctrl.y * s),
55 to: (glyph.x + to.x * s, glyph.y + to.y * s),
56 },
57 });
58 }
59 out.push(PathContour { start, segments });
60 }
61 }
62 for rule in &layout.rules {
63 let x0 = rule.x;
64 let y0 = rule.y;
65 let x1 = rule.x + rule.width;
66 let y1 = rule.y + rule.height;
67 out.push(PathContour {
68 start: (x0, y0),
69 segments: vec![
70 PathSeg::Line { to: (x1, y0) },
71 PathSeg::Line { to: (x1, y1) },
72 PathSeg::Line { to: (x0, y1) },
73 PathSeg::Line { to: (x0, y0) },
74 ],
75 });
76 }
77 for path in &layout.paths {
78 out.extend(path.contours.iter().cloned());
79 }
80 Ok(out)
81}
82
83#[must_use]
87pub fn canonical_dump(contours: &[PathContour]) -> String {
88 use core::fmt::Write as _;
89 let mut out = String::new();
90 for (i, contour) in contours.iter().enumerate() {
91 let _ = writeln!(
92 out,
93 "contour {} start {:.6} {:.6}",
94 i, contour.start.0, contour.start.1
95 );
96 for seg in &contour.segments {
97 match seg {
98 PathSeg::Line { to } => {
99 let _ = writeln!(out, " line {:.6} {:.6}", to.0, to.1);
100 }
101 PathSeg::Quad { ctrl, to } => {
102 let _ = writeln!(
103 out,
104 " quad {:.6} {:.6} {:.6} {:.6}",
105 ctrl.0, ctrl.1, to.0, to.1
106 );
107 }
108 }
109 }
110 }
111 out
112}
113
114#[must_use]
117pub fn layout_dump(layout: &Layout) -> String {
118 use core::fmt::Write as _;
119 let mut out = String::new();
120 let _ = writeln!(
121 out,
122 "layout w {:.6} h {:.6} d {:.6}",
123 layout.width, layout.height, layout.depth
124 );
125 for g in &layout.glyphs {
126 let _ = writeln!(
127 out,
128 "glyph face {} gid {} ch U+{:04X} x {:.6} y {:.6} size {:.6} span {}..{}",
129 g.face.0, g.gid, g.ch as u32, g.x, g.y, g.size, g.span.start, g.span.end
130 );
131 }
132 for r in &layout.rules {
133 let _ = writeln!(
134 out,
135 "rule x {:.6} y {:.6} w {:.6} h {:.6} span {}..{}",
136 r.x, r.y, r.width, r.height, r.span.start, r.span.end
137 );
138 }
139 for p in &layout.paths {
140 let _ = writeln!(
141 out,
142 "path contours {} span {}..{}",
143 p.contours.len(),
144 p.span.start,
145 p.span.end
146 );
147 }
148 out
149}
150
151#[must_use]
154pub fn spans_cover(layout: &Layout, source_len: usize) -> bool {
155 let ok = |s: &Span| s.end <= source_len && s.start <= s.end;
156 layout.glyphs.iter().all(|g| ok(&g.span))
157 && layout.rules.iter().all(|r| ok(&r.span))
158 && layout.paths.iter().all(|p| ok(&p.span))
159}