ttf_parser_utils/
lib.rs

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