Skip to main content

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}