read_fonts/model/glyph/outline.rs
1//! Scalable glyph outlines.
2//!
3//! [`OutlineGlyphs`] loads outlines from the source selected by a
4//! [`GlyphSourceOrder`]. Each [`OutlineGlyph`] identifies its source: a
5//! [`glyf::GlyfGlyph`], [`cff::CffGlyph`], [`type1::Type1Glyph`] or
6//! [`varc::VarcGlyph`]. Each of these provides access to the source's own
7//! types, which are needed for hinting. These are the `glyf` loader in
8//! [`tables::glyf::outline`] and the charstrings in [`ps`].
9//!
10//! Outlines are drawn as paths into a [`PathSink`]. A `Vec` of
11//! [`PathElement`] collects a path, [`ControlBoundsSink`] measures it,
12//! [`TransformSink`] transforms it, [`SvgSink`] writes it as SVG path data,
13//! and [`NullSink`] discards it.
14//!
15//! [`GlyphSourceOrder`]: crate::model::glyph::GlyphSourceOrder
16//! [`tables::glyf::outline`]: crate::tables::glyf::outline
17//! [`ps`]: crate::ps
18
19pub mod cff;
20pub mod glyf;
21mod glyphs;
22mod path;
23pub mod type1;
24mod unscaled;
25pub mod varc;
26
27#[doc(inline)]
28pub use crate::tables::glyf::outline::PathContourStart;
29#[doc(inline)]
30pub use glyphs::{OutlineGlyph, OutlineGlyphs};
31#[doc(inline)]
32pub use path::{ControlBoundsSink, NullSink, PathElement, PathSink, SvgSink, TransformSink};
33pub(crate) use unscaled::PointsFromPath;
34#[doc(inline)]
35pub use unscaled::UnscaledOutlineSink;
36
37use super::GlyphSource;
38
39/// A table that provides outlines.
40#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
41pub enum OutlineSource {
42 /// Variable composites in `VARC`.
43 ///
44 /// The components are drawn from one of the other outline sources.
45 Varc,
46 /// TrueType outlines in `glyf`.
47 Glyf,
48 /// Variable PostScript outlines in `CFF2`.
49 Cff2,
50 /// PostScript outlines in a `CFF` table or a standalone CFF font.
51 Cff,
52 /// Charstrings of a Type 1 font.
53 Type1,
54}
55
56impl From<OutlineSource> for GlyphSource {
57 fn from(source: OutlineSource) -> Self {
58 match source {
59 OutlineSource::Varc => Self::Varc,
60 OutlineSource::Glyf => Self::Glyf,
61 OutlineSource::Cff2 => Self::Cff2,
62 OutlineSource::Cff => Self::Cff,
63 OutlineSource::Type1 => Self::Type1,
64 }
65 }
66}
67
68impl TryFrom<GlyphSource> for OutlineSource {
69 /// The original source, which contains no outlines.
70 type Error = GlyphSource;
71
72 fn try_from(source: GlyphSource) -> Result<Self, GlyphSource> {
73 Ok(match source {
74 GlyphSource::Varc => Self::Varc,
75 GlyphSource::Glyf => Self::Glyf,
76 GlyphSource::Cff2 => Self::Cff2,
77 GlyphSource::Cff => Self::Cff,
78 GlyphSource::Type1 => Self::Type1,
79 _ => return Err(source),
80 })
81 }
82}
83
84/// How an outline is drawn.
85///
86/// A `glyf` outline is scaled in 26.6 fixed point. Without
87/// a size, it is drawn in font units, with an implied on-curve point between
88/// two off-curve points keeping its half unit.
89///
90/// The variation location comes from the font. Hinting is done through the
91/// source's own types.
92#[derive(Copy, Clone, Default, PartialEq, Debug)]
93pub struct DrawOptions {
94 /// Pixels per em to draw at, or `None` for font units.
95 pub ppem: Option<f32>,
96 /// Where a `glyf` contour starts when its first point is off-curve.
97 pub contour_start: PathContourStart,
98}
99
100impl DrawOptions {
101 /// Creates options that draw in font units with the default contour start.
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 /// Sets the size in pixels per em, or `None` for font units.
107 pub fn with_ppem(self, ppem: Option<f32>) -> Self {
108 Self { ppem, ..self }
109 }
110
111 /// Sets where a `glyf` contour starts when its first point is off-curve.
112 pub fn with_contour_start(self, contour_start: PathContourStart) -> Self {
113 Self {
114 contour_start,
115 ..self
116 }
117 }
118}
119
120/// Working memory for drawing an outline.
121///
122/// A `glyf` outline and a `VARC` composite need working memory to draw; a
123/// charstring needs none. The size an outline needs is given by its
124/// `required_memory_size`.
125#[derive(Debug, Default)]
126pub enum DrawMemory<'a> {
127 /// Short-lived scratch memory of the size the outline needs, which may be
128 /// allocated on the heap for a large glyph.
129 #[default]
130 Scratch,
131 /// Memory supplied by the caller, of at least the size the outline
132 /// needs.
133 Buffer(&'a mut [u8]),
134}
135
136impl<'a> DrawMemory<'a> {
137 /// Returns the caller's memory, or `None` for scratch memory.
138 pub(crate) fn into_buffer(self) -> Option<&'a mut [u8]> {
139 match self {
140 Self::Scratch => None,
141 Self::Buffer(buffer) => Some(buffer),
142 }
143 }
144}
145
146impl<'a> From<&'a mut [u8]> for DrawMemory<'a> {
147 fn from(buffer: &'a mut [u8]) -> Self {
148 Self::Buffer(buffer)
149 }
150}
151
152/// What drawing an outline reports about it.
153///
154/// Values are in the units the outline was drawn in: pixels at a size, font
155/// units otherwise. Hinting may adjust the advance and bearing.
156#[non_exhaustive]
157#[derive(Copy, Clone, Default, PartialEq, Debug)]
158pub struct DrawnMetrics {
159 /// The horizontal advance, if the outline provides one.
160 ///
161 /// A `glyf` outline provides it through its phantom points, with
162 /// variations applied. A charstring provides it directly. A `VARC`
163 /// composite has none.
164 pub h_advance: Option<f32>,
165 /// The left side bearing, if the outline provides one.
166 ///
167 /// Only a `glyf` outline provides it, through its phantom points. A
168 /// hinter that does not move phantom points, such as an automatic
169 /// hinter, may not report it.
170 pub x_bearing: Option<f32>,
171 /// True if the glyph is flagged as having overlapping contours or
172 /// components.
173 ///
174 /// Only `glyf` has these flags.
175 pub has_overlaps: bool,
176 /// The dropout control mode requested by the glyph program.
177 ///
178 /// This tells a bilevel rasterizer how to treat features thinner than a
179 /// pixel. It is [`DropoutControl::Simple`], the TrueType default, when
180 /// no TrueType hinter ran. When one ran, it is the mode the font's
181 /// programs left, and [`DropoutControl::Ignore`] if they never turned
182 /// dropout control on.
183 pub dropout_control: DropoutControl,
184}
185
186/// The dropout control mode for a bilevel rasterizer.
187///
188/// Dropout control defines how to treat features too thin to register on the
189/// pixel grid. A TrueType glyph program selects the mode with the `SCANCTRL`
190/// and `SCANTYPE` instructions, and may select a different mode for each
191/// glyph. It affects only rasterization to one bit per pixel. An antialiasing
192/// rasterizer uses coverage and does not need it.
193///
194/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#scan-conversion-control>.
195#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
196pub enum DropoutControl {
197 /// Fills only pixels whose centers are covered. Thin features may vanish.
198 Ignore,
199 /// Restores a dropped feature that crosses a scan line, ignoring stubs.
200 ///
201 /// This is the default when a glyph program does not set a mode.
202 #[default]
203 Simple,
204 /// Like [`Self::Simple`], but also restores stubs.
205 SimpleWithStubs,
206 /// Restores a dropped feature at the best position chosen by the scan
207 /// converter rather than a fixed one, ignoring stubs.
208 Smart,
209 /// Like [`Self::Smart`], but also restores stubs.
210 SmartWithStubs,
211}
212
213impl DropoutControl {
214 /// Returns true if dropped features should be restored.
215 pub fn is_enabled(self) -> bool {
216 self != Self::Ignore
217 }
218
219 /// Returns true if the scan converter should choose the restore position.
220 pub fn is_smart(self) -> bool {
221 matches!(self, Self::Smart | Self::SmartWithStubs)
222 }
223
224 /// Returns true if stubs should be restored along with crossing features.
225 pub fn includes_stubs(self) -> bool {
226 matches!(self, Self::SimpleWithStubs | Self::SmartWithStubs)
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::DropoutControl;
233
234 #[test]
235 fn dropout_predicates() {
236 assert!(!DropoutControl::Ignore.is_enabled());
237 assert!(DropoutControl::Simple.is_enabled());
238 assert!(!DropoutControl::Simple.is_smart());
239 assert!(DropoutControl::SmartWithStubs.is_smart());
240 assert!(DropoutControl::SmartWithStubs.includes_stubs());
241 assert!(!DropoutControl::Smart.includes_stubs());
242 assert_eq!(DropoutControl::default(), DropoutControl::Simple);
243 }
244}