ab_glyph/
outlined.rs

1#[cfg(all(feature = "libm", not(feature = "std")))]
2use crate::nostd_float::FloatExt;
3use crate::{point, Glyph, Point, PxScaleFactor};
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6
7/// A "raw" collection of outline curves for a glyph, unscaled & unpositioned.
8#[derive(Clone, Debug)]
9pub struct Outline {
10    /// Unscaled bounding box.
11    pub bounds: Rect,
12    /// Unscaled & unpositioned outline curves.
13    pub curves: Vec<OutlineCurve>,
14}
15
16impl Outline {
17    /// Convert unscaled bounds into pixel bounds at a given scale & position.
18    ///
19    /// See [`OutlinedGlyph::px_bounds`].
20    pub fn px_bounds(&self, scale_factor: PxScaleFactor, position: Point) -> Rect {
21        let Rect { min, max } = self.bounds;
22
23        // Use subpixel fraction in floor/ceil rounding to elimate rounding error
24        // from identical subpixel positions
25        let (x_trunc, x_fract) = (position.x.trunc(), position.x.fract());
26        let (y_trunc, y_fract) = (position.y.trunc(), position.y.fract());
27
28        Rect {
29            min: point(
30                (min.x * scale_factor.horizontal + x_fract).floor() + x_trunc,
31                (min.y * -scale_factor.vertical + y_fract).floor() + y_trunc,
32            ),
33            max: point(
34                (max.x * scale_factor.horizontal + x_fract).ceil() + x_trunc,
35                (max.y * -scale_factor.vertical + y_fract).ceil() + y_trunc,
36            ),
37        }
38    }
39}
40
41/// A glyph that has been outlined at a scale & position.
42#[derive(Clone, Debug)]
43pub struct OutlinedGlyph {
44    glyph: Glyph,
45    // Pixel scale bounds.
46    px_bounds: Rect,
47    // Scale factor
48    scale_factor: PxScaleFactor,
49    // Raw outline
50    outline: Outline,
51}
52
53impl OutlinedGlyph {
54    /// Constructs an `OutlinedGlyph` from the source `Glyph`, pixel bounds
55    /// & relatively positioned outline curves.
56    #[inline]
57    pub fn new(glyph: Glyph, outline: Outline, scale_factor: PxScaleFactor) -> Self {
58        // work this out now as it'll usually be used more than once
59        let px_bounds = outline.px_bounds(scale_factor, glyph.position);
60
61        Self {
62            glyph,
63            px_bounds,
64            scale_factor,
65            outline,
66        }
67    }
68
69    /// Glyph info.
70    #[inline]
71    pub fn glyph(&self) -> &Glyph {
72        &self.glyph
73    }
74
75    #[deprecated = "Renamed to `px_bounds`"]
76    #[doc(hidden)]
77    pub fn bounds(&self) -> Rect {
78        self.px_bounds()
79    }
80
81    /// Conservative whole number pixel bounding box for this glyph outline.
82    /// The returned rect is exactly large enough to [`Self::draw`] into.
83    ///
84    /// The rect holds bounding coordinates in the same coordinate space as the [`Glyph::position`].
85    ///
86    /// Note: These bounds depend on the glyph outline. That outline is *not* necessarily bound
87    ///       by the layout/`glyph_bounds()` bounds.
88    /// * The min.x bound may be greater or smaller than the [`Glyph::position`] x.
89    ///   E.g. if a glyph at position x=0 has an outline going off to the left a bit, min.x will be negative.
90    /// * The max.x bound may be greater/smaller than the `position.x + h_advance`.
91    /// * The min.y bound may be greater/smaller than the `position.y - ascent`.
92    /// * The max.y bound may be greater/smaller than the `position.y - descent`.
93    ///
94    /// Pixel bounds coordinates should not be used for layout logic.
95    #[inline]
96    pub fn px_bounds(&self) -> Rect {
97        self.px_bounds
98    }
99
100    /// Draw this glyph outline using a pixel & coverage handling function.
101    ///
102    /// The callback will be called for each `(x, y)` pixel coordinate inside the bounds
103    /// with a coverage value indicating how much the glyph covered that pixel.
104    ///
105    /// A coverage value of `0.0` means the pixel is totally uncoverred by the glyph.
106    /// A value of `1.0` or greater means fully covered.
107    pub fn draw<O: FnMut(u32, u32, f32)>(&self, o: O) {
108        use ab_glyph_rasterizer::Rasterizer;
109        let h_factor = self.scale_factor.horizontal;
110        let v_factor = -self.scale_factor.vertical;
111        let offset = self.glyph.position - self.px_bounds.min;
112        let (w, h) = (
113            self.px_bounds.width() as usize,
114            self.px_bounds.height() as usize,
115        );
116
117        let scale_up = |&Point { x, y }| point(x * h_factor, y * v_factor);
118
119        self.outline
120            .curves
121            .iter()
122            .fold(Rasterizer::new(w, h), |mut rasterizer, curve| match curve {
123                OutlineCurve::Line(p0, p1) => {
124                    // eprintln!("r.draw_line({:?}, {:?});",
125                    //     scale_up(p0) + offset, scale_up(p1) + offset);
126                    rasterizer.draw_line(scale_up(p0) + offset, scale_up(p1) + offset);
127                    rasterizer
128                }
129                OutlineCurve::Quad(p0, p1, p2) => {
130                    // eprintln!("r.draw_quad({:?}, {:?}, {:?});",
131                    //     scale_up(p0) + offset, scale_up(p1) + offset, scale_up(p2) + offset);
132                    rasterizer.draw_quad(
133                        scale_up(p0) + offset,
134                        scale_up(p1) + offset,
135                        scale_up(p2) + offset,
136                    );
137                    rasterizer
138                }
139                OutlineCurve::Cubic(p0, p1, p2, p3) => {
140                    // eprintln!("r.draw_cubic({:?}, {:?}, {:?}, {:?});",
141                    //     scale_up(p0) + offset, scale_up(p1) + offset, scale_up(p2) + offset, scale_up(p3) + offset);
142                    rasterizer.draw_cubic(
143                        scale_up(p0) + offset,
144                        scale_up(p1) + offset,
145                        scale_up(p2) + offset,
146                        scale_up(p3) + offset,
147                    );
148                    rasterizer
149                }
150            })
151            .for_each_pixel_2d(o);
152    }
153}
154
155impl AsRef<Glyph> for OutlinedGlyph {
156    #[inline]
157    fn as_ref(&self) -> &Glyph {
158        self.glyph()
159    }
160}
161
162/// Glyph outline primitives.
163#[derive(Clone, Debug)]
164pub enum OutlineCurve {
165    /// Straight line from `.0` to `.1`.
166    Line(Point, Point),
167    /// Quadratic Bézier curve from `.0` to `.2` using `.1` as the control.
168    Quad(Point, Point, Point),
169    /// Cubic Bézier curve from `.0` to `.3` using `.1` as the control at the beginning of the
170    /// curve and `.2` at the end of the curve.
171    Cubic(Point, Point, Point, Point),
172}
173
174/// A rectangle, with top-left corner at `min`, and bottom-right corner at `max`.
175#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd)]
176pub struct Rect {
177    pub min: Point,
178    pub max: Point,
179}
180
181impl Rect {
182    #[inline]
183    pub fn width(&self) -> f32 {
184        self.max.x - self.min.x
185    }
186
187    #[inline]
188    pub fn height(&self) -> f32 {
189        self.max.y - self.min.y
190    }
191}