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}