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