skrifa/outline/glyf/outline.rs
1//! TrueType outline types.
2
3use std::mem::size_of;
4
5use super::super::{
6 path::{to_path, ToPathError},
7 pen::PathStyle,
8 DrawError, Hinting, OutlinePen,
9};
10use raw::tables::glyf::PointCoord;
11use read_fonts::{
12 tables::glyf::{Glyph, PointFlags},
13 types::{F26Dot6, Fixed, GlyphId, Point},
14};
15
16/// Maximum number of points we support in a single outline including
17/// composites.
18///
19/// TrueType uses a 16 bit integer to store contour end points so
20/// we must keep the total count within this value.
21///
22/// The maxp <https://learn.microsoft.com/en-us/typography/opentype/spec/maxp>
23/// table encodes `maxCompositePoints` as a `uint16` so the spec enforces
24/// this limit.
25const MAX_POINTS: usize = u16::MAX as usize;
26
27/// Represents the information necessary to scale a glyph outline.
28///
29/// Contains a reference to the glyph data itself as well as metrics that
30/// can be used to compute the memory requirements for scaling the glyph.
31#[derive(Clone, Default)]
32pub struct Outline<'a> {
33 pub glyph_id: GlyphId,
34 /// The associated top-level glyph for the outline.
35 pub glyph: Option<Glyph<'a>>,
36 /// Sum of the point counts of all simple glyphs in an outline.
37 pub points: usize,
38 /// Sum of the contour counts of all simple glyphs in an outline.
39 pub contours: usize,
40 /// Maximum number of points in a single simple glyph.
41 pub max_simple_points: usize,
42 /// "Other" points are the unscaled or original scaled points.
43 ///
44 /// The size of these buffer is the same and this value tracks the size
45 /// for one (not both) of the buffers. This is the maximum of
46 /// `max_simple_points` and the total number of points for all component
47 /// glyphs in a single composite glyph.
48 pub max_other_points: usize,
49 /// Maximum size of the component delta stack.
50 ///
51 /// For composite glyphs in variable fonts, delta values are computed
52 /// for each component. This tracks the maximum stack depth necessary
53 /// to store those values during processing.
54 pub max_component_delta_stack: usize,
55 /// Number of entries in the hinting value stack.
56 pub max_stack: usize,
57 /// Number of CVT entries for copy-on-write support.
58 pub cvt_count: usize,
59 /// Number of storage area entries for copy-on-write support.
60 pub storage_count: usize,
61 /// Maximum number of points in the twilight zone for hinting.
62 pub max_twilight_points: usize,
63 /// True if any component of a glyph has bytecode instructions.
64 pub has_hinting: bool,
65 /// True if the glyph requires variation delta processing.
66 pub has_variations: bool,
67 /// True if the glyph contains any simple or compound overlap flags.
68 pub has_overlaps: bool,
69}
70
71impl Outline<'_> {
72 /// Returns the minimum size in bytes required to scale an outline based
73 /// on the computed sizes.
74 pub fn required_buffer_size(&self, hinting: Hinting) -> usize {
75 let mut size = 0;
76 let hinting = self.has_hinting && hinting == Hinting::Embedded;
77 // Scaled, unscaled and (for hinting) original scaled points
78 size += self.points * size_of::<Point<F26Dot6>>();
79 // Unscaled and (if hinted) original scaled points
80 size += self.max_other_points * size_of::<Point<i32>>() * if hinting { 2 } else { 1 };
81 // Contour end points
82 size += self.contours * size_of::<u16>();
83 // Point flags
84 size += self.points * size_of::<PointFlags>();
85 if self.has_variations {
86 // Interpolation buffer for delta IUP
87 size += self.max_simple_points * size_of::<Point<Fixed>>();
88 // Delta buffer for points
89 size += self.max_simple_points * size_of::<Point<Fixed>>();
90 // Delta buffer for composite components
91 size += self.max_component_delta_stack * size_of::<Point<Fixed>>();
92 }
93 if hinting {
94 // Hinting value stack
95 size += self.max_stack * size_of::<i32>();
96 // CVT and storage area copy-on-write buffers
97 size += (self.cvt_count + self.storage_count) * size_of::<i32>();
98 // Twilight zone storage. Two point buffers plus one point flags buffer
99 size += self.max_twilight_points
100 * (size_of::<Point<F26Dot6>>() * 2 + size_of::<PointFlags>());
101 }
102 if size != 0 {
103 // If we're given a buffer that is not aligned, we'll need to
104 // adjust, so add our maximum alignment requirement in bytes.
105 size += std::mem::align_of::<i32>();
106 }
107 size
108 }
109
110 pub(super) fn ensure_point_count_limit(&self) -> Result<(), DrawError> {
111 if self.points > MAX_POINTS {
112 Err(DrawError::TooManyPoints(self.glyph_id))
113 } else {
114 Ok(())
115 }
116 }
117}
118
119#[derive(Debug)]
120pub struct ScaledOutline<'a, C>
121where
122 C: PointCoord,
123{
124 pub points: &'a mut [Point<C>],
125 pub flags: &'a mut [PointFlags],
126 pub contours: &'a mut [u16],
127 pub phantom_points: [Point<C>; 4],
128 pub hdmx_width: Option<u8>,
129}
130
131impl<'a, C> ScaledOutline<'a, C>
132where
133 C: PointCoord,
134{
135 pub(crate) fn new(
136 points: &'a mut [Point<C>],
137 phantom_points: [Point<C>; 4],
138 flags: &'a mut [PointFlags],
139 contours: &'a mut [u16],
140 hdmx_width: Option<u8>,
141 ) -> Self {
142 let x_shift = phantom_points[0].x;
143 if x_shift != C::zeroed() {
144 for point in points.iter_mut() {
145 point.x = point.x - x_shift;
146 }
147 }
148 Self {
149 points,
150 flags,
151 contours,
152 phantom_points,
153 hdmx_width,
154 }
155 }
156
157 pub fn adjusted_lsb(&self) -> C {
158 self.phantom_points[0].x
159 }
160
161 pub fn adjusted_advance_width(&self) -> C {
162 // Prefer widths from hdmx, otherwise take difference between first
163 // two phantom points
164 // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L1996>
165 if let Some(hdmx_width) = self.hdmx_width {
166 C::from_i32(hdmx_width as i32)
167 } else {
168 self.phantom_points[1].x - self.phantom_points[0].x
169 }
170 }
171
172 pub fn to_path(
173 &self,
174 path_style: PathStyle,
175 pen: &mut impl OutlinePen,
176 ) -> Result<(), ToPathError> {
177 to_path(self.points, self.flags, self.contours, path_style, pen)
178 }
179}