ttf_utils/
lib.rs

1//! `ttf-parser` utils.
2
3/// A bounding box.
4#[derive(Debug, Default, Clone, Copy, PartialEq)]
5pub struct BBox {
6    /// Minimum X coordinate.
7    pub x_min: f32,
8    /// Minimum Y coordinate.
9    pub y_min: f32,
10    /// Maximum X coordinate.
11    pub x_max: f32,
12    /// Maximum Y coordinate.
13    pub y_max: f32,
14}
15
16impl BBox {
17    /// Returns the bbox width.
18    #[inline]
19    pub fn width(&self) -> f32 {
20        self.x_max - self.x_min
21    }
22
23    /// Returns the bbox height.
24    #[inline]
25    pub fn height(&self) -> f32 {
26        self.y_max - self.y_min
27    }
28
29    /// Extend the bbox.
30    #[inline]
31    pub fn extend_by(&mut self, x: f32, y: f32) {
32        self.x_min = self.x_min.min(x);
33        self.y_min = self.y_min.min(y);
34        self.x_max = self.x_max.max(x);
35        self.y_max = self.y_max.max(y);
36    }
37}
38
39/// A glyph outline.
40#[derive(Debug, Clone)]
41pub struct Outline {
42    bbox: std::cell::Cell<Option<BBox>>,
43    cff: bool,
44    contours: Vec<Contour>,
45}
46
47impl Outline {
48    /// Returns a new outline or `None` when the glyph has no outline or on error.
49    pub fn new(face: &ttf_parser::Face, glyph_id: ttf_parser::GlyphId) -> Option<Self> {
50        let mut outline = Outline {
51            bbox: std::cell::Cell::new(None),
52            cff: face.has_table(ttf_parser::TableName::CompactFontFormat)
53                || face.has_table(ttf_parser::TableName::CompactFontFormat2),
54            contours: Vec::new(),
55        };
56        let mut outline_builder = OutlineBuilder::new(&mut outline);
57        let _ = face.outline_glyph(glyph_id, &mut outline_builder)?;
58        Some(outline)
59    }
60
61    /// Returns the outline bounding box.
62    pub fn bbox(&self) -> BBox {
63        if let Some(bbox) = self.bbox.get() {
64            bbox
65        } else {
66            let mut bbox = BBox::default();
67            for (i, p) in self.contours.iter().flat_map(|c| &c.points).enumerate() {
68                if i == 0 {
69                    bbox.x_min = p.x;
70                    bbox.y_min = p.y;
71                    bbox.x_max = p.x;
72                    bbox.y_max = p.y;
73                } else {
74                    bbox.extend_by(p.x, p.y);
75                }
76            }
77
78            self.bbox.set(Some(bbox));
79            bbox
80        }
81    }
82
83    /// Embolden the outline.
84    pub fn embolden(&mut self, strength: f32) {
85        self.bbox.set(None);
86        for c in &mut self.contours {
87            let num_points = c.points.len();
88            if num_points == 0 {
89                continue;
90            }
91
92            let closed = num_points > 1 && c.points.last() == c.points.first();
93            let last = if closed {
94                num_points - 2
95            } else {
96                num_points - 1
97            };
98
99            let mut in_pt = Point::default();
100            let mut in_len = 0f32;
101
102            let mut anchor_pt = Point::default();
103            let mut anchor_len = 0f32;
104
105            let mut i = last;
106            let mut j = 0;
107            let mut k: Option<usize> = None;
108            while i != j && Some(i) != k {
109                let (out_pt, out_len) = if Some(j) != k {
110                    let x = c.points[j].x - c.points[i].x;
111                    let y = c.points[j].y - c.points[i].y;
112                    let len = (x * x + y * y).sqrt();
113                    if len != 0.0 {
114                        (Point::new(x / len, y / len), len)
115                    } else {
116                        j = if j < last { j + 1 } else { 0 };
117                        continue;
118                    }
119                } else {
120                    (anchor_pt, anchor_len)
121                };
122
123                if in_len != 0.0 {
124                    if k.is_none() {
125                        k = Some(i);
126                        anchor_pt = in_pt;
127                        anchor_len = in_len;
128                    }
129
130                    let d = (in_pt.x * out_pt.x) + (in_pt.y * out_pt.y);
131                    let shift_pt = if d > -0.9375 {
132                        let d = d + 1.0;
133                        let mut q = out_pt.x * in_pt.y - out_pt.y * in_pt.x;
134                        if !self.cff {
135                            q = -q;
136                        }
137
138                        let len = in_len.min(out_len);
139                        let (x, y) = if self.cff {
140                            (in_pt.y + out_pt.y, -(in_pt.x + out_pt.x))
141                        } else {
142                            (-(in_pt.y + out_pt.y), in_pt.x + out_pt.x)
143                        };
144                        if (strength * q) <= (len * d) {
145                            Point::new(x * strength / d, y * strength / d)
146                        } else {
147                            Point::new(x * len / q, y * len / q)
148                        }
149                    } else {
150                        Point::default()
151                    };
152
153                    while i != j {
154                        let pt = &mut c.points[i];
155                        pt.x += strength + shift_pt.x;
156                        pt.y += strength + shift_pt.y;
157                        i = if i < last { i + 1 } else { 0 };
158                    }
159                } else {
160                    i = j;
161                }
162
163                in_pt = out_pt;
164                in_len = out_len;
165                j = if j < last { j + 1 } else { 0 };
166            }
167
168            if closed {
169                let first = &c.points[0];
170                c.points[num_points - 1] = *first;
171            }
172        }
173    }
174
175    /// Slant the outline.
176    pub fn oblique(&mut self, x_skew: f32) {
177        self.bbox.set(None);
178        for c in &mut self.contours {
179            for p in &mut c.points {
180                if p.y != 0.0 {
181                    p.x += p.y * x_skew;
182                }
183            }
184        }
185    }
186
187    /// Emit the outline segments.
188    pub fn emit(&self, builder: &mut dyn ttf_parser::OutlineBuilder) {
189        let mut points = self.contours.iter().flat_map(|c| &c.points);
190        for v in self.contours.iter().flat_map(|c| &c.verbs) {
191            match v {
192                PathVerb::MoveTo => {
193                    let p = points.next().unwrap();
194                    builder.move_to(p.x, p.y);
195                }
196                PathVerb::LineTo => {
197                    let p = points.next().unwrap();
198                    builder.line_to(p.x, p.y);
199                }
200                PathVerb::QuadTo => {
201                    let p1 = points.next().unwrap();
202                    let p = points.next().unwrap();
203                    builder.quad_to(p1.x, p1.y, p.x, p.y);
204                }
205                PathVerb::CurveTo => {
206                    let p1 = points.next().unwrap();
207                    let p2 = points.next().unwrap();
208                    let p = points.next().unwrap();
209                    builder.curve_to(p1.x, p1.y, p2.x, p2.y, p.x, p.y);
210                }
211                PathVerb::Close => {
212                    builder.close();
213                }
214            }
215        }
216    }
217}
218
219#[derive(Debug, Default, Clone)]
220struct Contour {
221    verbs: Vec<PathVerb>,
222    points: Vec<Point>,
223}
224
225#[derive(Debug, Clone, Copy)]
226enum PathVerb {
227    MoveTo,
228    LineTo,
229    QuadTo,
230    CurveTo,
231    Close,
232}
233
234#[derive(Debug, Default, Clone, Copy, PartialEq)]
235struct Point {
236    x: f32,
237    y: f32,
238}
239
240impl Point {
241    #[inline]
242    fn new(x: f32, y: f32) -> Self {
243        Self { x, y }
244    }
245}
246
247struct OutlineBuilder<'a> {
248    outline: &'a mut Outline,
249    current_contour: usize,
250}
251
252impl<'a> OutlineBuilder<'a> {
253    #[inline]
254    fn new(outline: &'a mut Outline) -> Self {
255        Self {
256            outline,
257            current_contour: 1,
258        }
259    }
260
261    #[inline]
262    fn current_contour(&mut self) -> &mut Contour {
263        if self.current_contour > self.outline.contours.len() {
264            self.outline.contours.push(Contour::default());
265        }
266
267        &mut self.outline.contours[self.current_contour - 1]
268    }
269}
270
271impl<'a> ttf_parser::OutlineBuilder for OutlineBuilder<'a> {
272    fn move_to(&mut self, x: f32, y: f32) {
273        let c = self.current_contour();
274        c.verbs.push(PathVerb::MoveTo);
275        c.points.push(Point::new(x, y));
276    }
277
278    fn line_to(&mut self, x: f32, y: f32) {
279        let c = self.current_contour();
280        c.verbs.push(PathVerb::LineTo);
281        c.points.push(Point::new(x, y));
282    }
283
284    fn quad_to(&mut self, x1: f32, y1: f32, x: f32, y: f32) {
285        let c = self.current_contour();
286        c.verbs.push(PathVerb::QuadTo);
287        c.points.push(Point::new(x1, y1));
288        c.points.push(Point::new(x, y));
289    }
290
291    fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x: f32, y: f32) {
292        let c = self.current_contour();
293        c.verbs.push(PathVerb::CurveTo);
294        c.points.push(Point::new(x1, y1));
295        c.points.push(Point::new(x2, y2));
296        c.points.push(Point::new(x, y));
297    }
298
299    fn close(&mut self) {
300        self.current_contour().verbs.push(PathVerb::Close);
301        self.current_contour += 1;
302    }
303}