1
#![deny(missing_docs)]
2

            
3
//! Access glyphs outlines. Requires the `outline` cargo feature (enabled by default).
4
//!
5
//! This module is used to access the outlines of glyphs as a series of foundational drawing
6
//! instruction callbacks on implementors of the `OutlineSink` trait. Outlines from `glyf` and
7
//! `CFF` tables can be accessed.
8
//!
9
//! ### Example
10
//!
11
//! This is a fairly complete example of mapping some glyphs and then visiting their outlines with
12
//! support for TrueType and CFF fonts. It accumulates the drawing operations into a `String`.
13
//! In a real application you'd probably make calls to a graphics library instead.
14
//!
15
//! ```
16
//! use std::fmt::Write;
17
//!
18
//! use allsorts::binary::read::ReadScope;
19
//! use allsorts::cff::outline::CFFOutlines;
20
//! use allsorts::cff::CFF;
21
//! use allsorts::font::{GlyphTableFlag, MatchingPresentation};
22
//! use allsorts::font_data::FontData;
23
//! use allsorts::gsub::RawGlyph;
24
//! use allsorts::outline::{OutlineBuilder, OutlineSink};
25
//! use allsorts::pathfinder_geometry::line_segment::LineSegment2F;
26
//! use allsorts::pathfinder_geometry::vector::Vector2F;
27
//! use allsorts::tables::glyf::{GlyfVisitorContext, LocaGlyf};
28
//! use allsorts::tables::loca::{owned, LocaTable};
29
//! use allsorts::tables::{FontTableProvider, SfntVersion};
30
//! use allsorts::{tag, Font};
31
//!
32
//! struct DebugVisitor {
33
//!     outlines: String,
34
//! }
35
//!
36
//! impl OutlineSink for DebugVisitor {
37
//!     fn move_to(&mut self, to: Vector2F) {
38
//!         writeln!(&mut self.outlines, "move_to({}, {})", to.x(), to.y()).unwrap();
39
//!     }
40
//!
41
//!     fn line_to(&mut self, to: Vector2F) {
42
//!         writeln!(&mut self.outlines, "line_to({}, {})", to.x(), to.y()).unwrap();
43
//!     }
44
//!
45
//!     fn quadratic_curve_to(&mut self, control: Vector2F, to: Vector2F) {
46
//!         writeln!(
47
//!             &mut self.outlines,
48
//!             "quad_to({}, {}, {}, {})",
49
//!             control.x(),
50
//!             control.y(),
51
//!             to.x(),
52
//!             to.y()
53
//!         )
54
//!         .unwrap();
55
//!     }
56
//!
57
//!     fn cubic_curve_to(&mut self, control: LineSegment2F, to: Vector2F) {
58
//!         writeln!(
59
//!             &mut self.outlines,
60
//!             "curve_to({}, {}, {}, {}, {}, {})",
61
//!             control.from_x(),
62
//!             control.from_y(),
63
//!             control.to_x(),
64
//!             control.to_y(),
65
//!             to.x(),
66
//!             to.y()
67
//!         )
68
//!         .unwrap();
69
//!     }
70
//!
71
//!     fn close(&mut self) {
72
//!         writeln!(&mut self.outlines, "close()").unwrap();
73
//!     }
74
//! }
75
//!
76
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
77
//!     let script = tag::LATN;
78
//!     let buffer = std::fs::read("tests/fonts/opentype/Klei.otf")?;
79
//!     let scope = ReadScope::new(&buffer);
80
//!     let font_file = scope.read::<FontData<'_>>()?;
81
//!     let provider = font_file.table_provider(0)?;
82
//!     let mut font = Font::new(provider)?;
83
//!     let mut sink = DebugVisitor {
84
//!         outlines: String::new(),
85
//!     };
86
//!
87
//!     // Map text to glyphs
88
//!     let glyphs = font.map_glyphs("+", script, MatchingPresentation::NotRequired);
89
//!
90
//!     // Visit the outlines of each glyph. Read tables depending on the type of font
91
//!     if font.glyph_table_flags.contains(GlyphTableFlag::CFF)
92
//!         && font.font_table_provider.sfnt_version() == tag::OTTO
93
//!     {
94
//!         let cff_data = font.font_table_provider.read_table_data(tag::CFF)?;
95
//!         let cff = ReadScope::new(&cff_data).read::<CFF<'_>>()?;
96
//!         let mut cff_outlines = CFFOutlines { table: &cff };
97
//!         sink.glyphs_to_path(&mut cff_outlines, &glyphs)?;
98
//!     } else if font.glyph_table_flags.contains(GlyphTableFlag::GLYF) {
99
//!         let loca_data = font.font_table_provider.read_table_data(tag::LOCA)?;
100
//!         let loca = ReadScope::new(&loca_data).read_dep::<LocaTable<'_>>((
101
//!             font.maxp_table.num_glyphs,
102
//!             font.head_table.index_to_loc_format,
103
//!         ))?;
104
//!         let glyf_data = font
105
//!             .font_table_provider
106
//!             .read_table_data(tag::GLYF)
107
//!             .map(Box::from)?;
108
//!         let mut loca_glyf = LocaGlyf::loaded(owned::LocaTable::from(&loca), glyf_data);
109
//!         let mut ctx = GlyfVisitorContext::new(&mut loca_glyf, None);
110
//!         sink.glyphs_to_path(&mut ctx, &glyphs)?;
111
//!     } else {
112
//!         return Err("no glyf or CFF table".into());
113
//!     }
114
//!
115
//!     let expected = "move_to(225, 152)
116
//! line_to(225, 269)
117
//! curve_to(225, 274, 228, 276, 232, 276)
118
//! line_to(341, 276)
119
//! curve_to(346, 276, 347, 285, 347, 295)
120
//! curve_to(347, 307, 345, 320, 341, 320)
121
//! line_to(232, 320)
122
//! curve_to(226, 320, 226, 325, 226, 328)
123
//! line_to(226, 432)
124
//! curve_to(220, 435, 214, 437, 206, 437)
125
//! curve_to(198, 437, 190, 435, 181, 432)
126
//! line_to(181, 329)
127
//! curve_to(181, 326, 180, 320, 172, 320)
128
//! line_to(68, 320)
129
//! curve_to(62, 320, 59, 311, 59, 300)
130
//! curve_to(59, 289, 62, 278, 68, 276)
131
//! line_to(174, 276)
132
//! curve_to(179, 276, 181, 271, 181, 267)
133
//! line_to(181, 152)
134
//! curve_to(181, 147, 193, 144, 204, 144)
135
//! curve_to(215, 144, 225, 147, 225, 152)
136
//! close()
137
//! ";
138
//!     assert_eq!(sink.outlines, expected);
139
//!     Ok(())
140
//! }
141
//!
142
//! impl DebugVisitor {
143
//!     pub fn glyphs_to_path<T>(
144
//!         &mut self,
145
//!         builder: &mut T,
146
//!         glyphs: &[RawGlyph<()>],
147
//!     ) -> Result<(), Box<dyn std::error::Error>>
148
//!     where
149
//!         T: OutlineBuilder,
150
//!         <T as OutlineBuilder>::Error: 'static,
151
//!     {
152
//!         for glyph in glyphs {
153
//!             builder.visit(glyph.glyph_index, None, self)?;
154
//!         }
155
//!
156
//!         Ok(())
157
//!     }
158
//! }
159
//! ```
160

            
161
use std::cmp::Ordering;
162

            
163
use pathfinder_geometry::vector::Vector2F;
164
use pathfinder_geometry::{line_segment::LineSegment2F, rect::RectF};
165
use tinyvec::{array_vec, ArrayVec};
166

            
167
use crate::tables::glyf::{BoundingBox, Point as GlyfPoint};
168
use crate::tables::variable_fonts::OwnedTuple;
169
use crate::TryNumFrom;
170

            
171
#[derive(Clone, Copy, Debug)]
172
pub(crate) struct BBox {
173
    rect: Option<RectF>,
174
}
175

            
176
/// Trait for visiting a glyph outline and delivering drawing commands to an `OutlineSink`.
177
pub trait OutlineBuilder {
178
    /// The error type returned by the `visit` method.
179
    type Error: std::error::Error;
180
    /// The output type returned by the `visit` method.
181
    type Output;
182

            
183
    /// Visit the glyph outlines in `self`.
184
    fn visit<S: OutlineSink>(
185
        &mut self,
186
        glyph_index: u16,
187
        tuple: Option<&OwnedTuple>,
188
        sink: &mut S,
189
    ) -> Result<Self::Output, Self::Error>;
190
}
191

            
192
// `OutlineSink` is from font-kit, font-kit/src/outline.rs:
193
//
194
// Copyright © 2020 The Pathfinder Project Developers.
195
//
196
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
197
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
198
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
199
// option. This file may not be copied, modified, or distributed
200
// except according to those terms.
201

            
202
/// A trait for visiting a glyph outline
203
pub trait OutlineSink {
204
    /// Moves the pen to a point.
205
    fn move_to(&mut self, to: Vector2F);
206
    /// Draws a line to a point.
207
    fn line_to(&mut self, to: Vector2F);
208
    /// Draws a quadratic Bézier curve to a point.
209
    fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F);
210
    /// Draws a cubic Bézier curve to a point.
211
    fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F);
212
    /// Closes the path, returning to the first point in it.
213
    fn close(&mut self);
214
}
215

            
216
/// An [OutlineSink] that computes the bounding box of a glyph.
217
pub struct BoundingBoxSink {
218
    prev_point: Vector2F,
219
    bbox: BBox,
220
}
221

            
222
impl BBox {
223
    pub(crate) fn new() -> Self {
224
        BBox { rect: None }
225
    }
226

            
227
    pub(crate) fn is_default(&self) -> bool {
228
        self.rect.is_none()
229
    }
230

            
231
    pub(crate) fn extend_by_point(&mut self, point: Vector2F) {
232
        // Extend the existing rect or initialise it with an empty rect containing
233
        // only `point`.
234
        self.rect = self
235
            .rect
236
            .map(|rect| rect.union_point(point))
237
            .or_else(|| Some(RectF::from_points(point, point)))
238
    }
239

            
240
    pub(crate) fn to_bounding_box(&self) -> Option<BoundingBox> {
241
        match self.rect.map(RectF::round_out) {
242
            Some(rect) => Some(BoundingBox {
243
                x_min: i16::try_num_from(rect.min_x())?,
244
                y_min: i16::try_num_from(rect.min_y())?,
245
                x_max: i16::try_num_from(rect.max_x())?,
246
                y_max: i16::try_num_from(rect.max_y())?,
247
            }),
248
            None => None,
249
        }
250
    }
251
}
252

            
253
impl BoundingBoxSink {
254
    /// Construct a new `BoundingBoxSink` for use with [OutlineBuilder].
255
    pub fn new() -> Self {
256
        BoundingBoxSink {
257
            prev_point: Vector2F::zero(),
258
            bbox: BBox::new(),
259
        }
260
    }
261

            
262
    pub(crate) fn bbox(&self) -> BBox {
263
        self.bbox
264
    }
265

            
266
    /// Returns the calculated bounding box of the glyph outline.
267
    pub fn to_bounding_box(&self) -> Option<BoundingBox> {
268
        self.bbox.to_bounding_box()
269
    }
270
}
271

            
272
impl OutlineSink for BoundingBoxSink {
273
    fn move_to(&mut self, to: Vector2F) {
274
        self.bbox.extend_by_point(to);
275
        self.prev_point = to;
276
    }
277

            
278
    fn line_to(&mut self, to: Vector2F) {
279
        self.bbox.extend_by_point(to);
280
        self.prev_point = to;
281
    }
282

            
283
    fn quadratic_curve_to(&mut self, ctrl: Vector2F, to: Vector2F) {
284
        // https://iquilezles.org/articles/bezierbbox/
285
        let p0 = self.prev_point;
286
        let p1 = ctrl;
287
        let p2 = to;
288

            
289
        // If the box around the start point and end point contains the control point,
290
        // then that is the bounding box of the curve. Otherwise we need to find the
291
        // extrema of the curve.
292
        if !RectF::from_points(p0.min(p2), p0.max(p2)).contains_point(p1) {
293
            // Calculate where derivative is zero
294
            let denominator = p0 - (p1 * 2.0) + p2;
295
            if denominator.x() != 0.0 && denominator.y() != 0.0 {
296
                let t = ((p0 - p1) / denominator).clamp(Vector2F::splat(0.0), Vector2F::splat(1.0));
297

            
298
                // Feed that back into the bezier formula to get the point on the curve
299
                let s = Vector2F::splat(1.0) - t;
300
                let q = s * s * p0 + (s * 2.0) * t * p1 + t * t * p2;
301
                self.bbox.extend_by_point(q);
302
            }
303
        }
304

            
305
        self.bbox.extend_by_point(to);
306
        self.prev_point = to;
307
    }
308

            
309
    fn cubic_curve_to(&mut self, ctrl: LineSegment2F, to: Vector2F) {
310
        let from = self.prev_point;
311

            
312
        // If the box around the start point and end point contains both control points,
313
        // then that is the bounding box of the curve. Otherwise we need to find the
314
        // extrema of the curve.
315
        let rect = RectF::from_points(from.min(to), from.max(to));
316
        if !(rect.contains_point(ctrl.from()) && rect.contains_point(ctrl.to())) {
317
            let (x_roots, y_roots) = bezier_roots(from, ctrl.from(), ctrl.to(), to);
318
            let coefficients = BezierCoefficients::new(from, ctrl.from(), ctrl.to(), to);
319
            for t in x_roots.iter().chain(y_roots.iter()).copied() {
320
                let point = coefficients.bezier_point(t);
321
                self.bbox.extend_by_point(point);
322
            }
323
        }
324

            
325
        self.bbox.extend_by_point(to);
326
        self.prev_point = to;
327
    }
328

            
329
    fn close(&mut self) {
330
        // Nothing to do. Close returns the starting point, which is already contained in the bbox.
331
    }
332
}
333

            
334
// References:
335
//
336
// The Math Behind Bezier Cubic Splines
337
// http://www.tinaja.com/glib/cubemath.pdf
338
//
339
// Warping Text To Bézier curves
340
// http://www.planetclegg.com/projects/WarpingTextToSplines.html
341

            
342
/// A-H values in equation space
343
#[derive(Debug, Copy, Clone)]
344
struct BezierCoefficients {
345
    pub ae: Vector2F,
346
    pub bf: Vector2F,
347
    pub cg: Vector2F,
348
    pub dh: Vector2F,
349
}
350

            
351
impl BezierCoefficients {
352
    pub fn new(p0: Vector2F, p1: Vector2F, p2: Vector2F, p3: Vector2F) -> Self {
353
        let ae = p3 - p2 * 3.0 + p1 * 3.0 - p0;
354
        let bf = p2 * 3.0 - p1 * 6.0 + p0 * 3.0;
355
        let cg = p1 * 3.0 - p0 * 3.0;
356
        let dh = p0;
357

            
358
        Self { ae, bf, cg, dh }
359
    }
360

            
361
    #[cfg(test)]
362
    fn as_array(&self) -> [f32; 8] {
363
        [
364
            self.ae.x(),
365
            self.bf.x(),
366
            self.cg.x(),
367
            self.dh.x(),
368
            self.ae.y(),
369
            self.bf.y(),
370
            self.cg.y(),
371
            self.dh.y(),
372
        ]
373
    }
374

            
375
    fn bezier_point(&self, t: f32) -> Vector2F {
376
        self.ae * t.powi(3) + self.bf * t.powi(2) + self.cg * t + self.dh
377
    }
378
}
379

            
380
// Finding extremities: root finding
381
// https://pomax.github.io/bezierinfo/#extremities
382
fn bezier_roots(
383
    p1: Vector2F,
384
    p2: Vector2F,
385
    p3: Vector2F,
386
    p4: Vector2F,
387
) -> (ArrayVec<[f32; 2]>, ArrayVec<[f32; 2]>) {
388
    let x_roots = bezier_component_roots(p1.x(), p2.x(), p3.x(), p4.x());
389
    let y_roots = bezier_component_roots(p1.y(), p2.y(), p3.y(), p4.y());
390

            
391
    (x_roots, y_roots)
392
}
393

            
394
fn bezier_component_roots(p1: f32, p2: f32, p3: f32, p4: f32) -> ArrayVec<[f32; 2]> {
395
    let a = 3.0 * (-p1 + 3.0 * p2 - 3.0 * p3 + p4);
396
    let b = 6.0 * (p1 - 2.0 * p2 + p3);
397
    let c = 3.0 * (p2 - p1);
398

            
399
    let roots = if a == 0.0 {
400
        //  𝑓ʹ(𝑡) = 𝑎𝑡² + 𝑏𝑡 + 𝑐
401
        //      0 = 𝑏𝑡 + 𝑐
402
        //      𝑡 = -𝑐/𝑏
403
        if b == 0.0 {
404
            ArrayVec::new()
405
        } else {
406
            let t = -c / b;
407
            // If B is some very small value but not quite zero, T can end up as
408
            // a very large value risking overflow. Address if needed.
409
            array_vec!([f32; 2] => t)
410
        }
411
    } else {
412
        solve_quadratic(a, b, c)
413
    };
414

            
415
    roots
416
        .into_iter()
417
        .filter(|root| (0.0..=1.0).contains(root))
418
        .collect()
419
}
420

            
421
// https://en.wikipedia.org/wiki/Quadratic_formula
422
// https://apps.dtic.mil/sti/tr/pdf/AD0639052.pdf
423
// https://s3.amazonaws.com/nrbook.com/book_C210_pdf/chap10c.pdf
424
// 𝑎𝑥² + 𝑏𝑥 + 𝑐 = 0
425
fn solve_quadratic(a: f32, b: f32, c: f32) -> ArrayVec<[f32; 2]> {
426
    // The quantity Δ = b² − 4ac is known as the discriminant of the quadratic equation.
427
    let discriminant = b * b - 4.0 * a * c;
428
    match discriminant.total_cmp(&0.0) {
429
        // when Δ < 0, the equation has no real roots
430
        Ordering::Less => ArrayVec::new(),
431
        // when Δ = 0, the equation has one repeated real root
432
        Ordering::Equal => array_vec!([f32; 2] => -0.5 * b / a),
433
        // when Δ > 0, the equation has two distinct real roots
434
        Ordering::Greater => {
435
            let sqrt_d = discriminant.sqrt();
436

            
437
            // Avoid catastrophic cancellation
438
            // https://en.wikipedia.org/wiki/Quadratic_formula#Numerical_calculation
439
            // https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
440
            match b.total_cmp(&0.0) {
441
                Ordering::Less => {
442
                    let q = -0.5 * (b - sqrt_d);
443
                    ArrayVec::from([q / a, c / q])
444
                }
445
                Ordering::Equal => {
446
                    let root = -0.5 * sqrt_d / a;
447
                    ArrayVec::from([root, -root])
448
                }
449
                Ordering::Greater => {
450
                    let q = -0.5 * (b + sqrt_d);
451
                    ArrayVec::from([q / a, c / q])
452
                }
453
            }
454
        }
455
    }
456
}
457

            
458
impl From<GlyfPoint> for Vector2F {
459
3013146
    fn from(point: GlyfPoint) -> Self {
460
3013146
        Vector2F::new(f32::from(point.0), f32::from(point.1))
461
3013146
    }
462
}
463

            
464
#[cfg(test)]
465
mod tests {
466
    use pathfinder_geometry::vector::vec2f;
467

            
468
    use crate::assert_close;
469

            
470
    use super::*;
471

            
472
    // First two test cases from:
473
    // https://apps.dtic.mil/sti/tr/pdf/AD0639052.pdf §6 p. 10
474
    #[test]
475
    fn test_solve_quadratic1() {
476
        let res = solve_quadratic(6.0, 5.0, -4.0);
477
        let &[root1, root2] = res.as_slice() else {
478
            panic!("Expected two roots");
479
        };
480
        assert_close!(root1, -1.33333333);
481
        assert_close!(root2, 0.5);
482
    }
483

            
484
    #[test]
485
    fn test_solve_quadratic2() {
486
        let res = solve_quadratic(1.0, 100000.0, 1.0);
487
        let &[root1, root2] = res.as_slice() else {
488
            panic!("Expected two roots");
489
        };
490
        assert_close!(root1, -100000.0);
491
        assert_close!(root2, -0.00001);
492
    }
493

            
494
    #[test]
495
    fn bezier_coefficients() {
496
        let actual = BezierCoefficients::new(
497
            vec2f(110., 150.),
498
            vec2f(25., 190.),
499
            vec2f(210., 250.),
500
            vec2f(210., 30.),
501
        );
502
        let expected = [-455.0_f32, 810.0, -255.0, 110.0, -300.0, 60.0, 120.0, 150.0];
503
        for (&a, &b) in actual.as_array().iter().zip(expected.iter()) {
504
            assert_close!(a, b);
505
        }
506
    }
507

            
508
    #[test]
509
    fn quadratic_bbox() {
510
        let mut sink = BoundingBoxSink::new();
511
        let start = vec2f(82.148931, 87.063829);
512
        sink.move_to(start);
513
        let ctrl = vec2f(91.627661, 5.968085099999996);
514
        let to = vec2f(103.56383, 64.595743);
515
        sink.quadratic_curve_to(ctrl, to);
516
        let bbox = sink.bbox.rect.unwrap();
517

            
518
        assert_close!(bbox.origin_x(), 82.148931);
519
        assert_close!(bbox.origin_y(), 39.995697);
520
        assert_close!(bbox.width(), 103.56383 - 82.148931);
521
        assert_close!(bbox.height(), 87.063829 - 39.995697);
522
    }
523
}