Skip to main content

skrifa/scaler/
mod.rs

1//! Scaled and hinted glyphs from a [`Font`].
2//!
3//! The crate root re-exports the types.
4
5mod error;
6mod hinting;
7mod postscript;
8mod truetype;
9
10#[cfg(test)]
11mod tests;
12
13pub use crate::outline::glyf::{DropoutControl, RasterPolicy};
14pub use error::ScaleError;
15use hinting::EngineRequest;
16pub use hinting::{HintEngine, HintOptions, RasterTarget};
17pub use raw::model::glyph::outline::{DrawMemory, DrawnMetrics, OutlineSource, PathSink};
18pub use raw::model::glyph::{GlyphSource, GlyphSourceOrder};
19
20use crate::{
21    font::Font,
22    instance::Size,
23    outline::{autohint, pen::PathStyle, AdjustedMetrics},
24};
25use alloc::boxed::Box;
26#[cfg(feature = "libm")]
27#[allow(unused_imports)]
28use core_maths::CoreFloat;
29use raw::{
30    model::glyph::outline::{DrawOptions, OutlineGlyph, OutlineGlyphs, PathContourStart},
31    model::metrics::{ScaleF32, ScaledGlyphMetrics, ScaledMetrics, ScaledStyleMetrics},
32    ps::cff::CffFontRef,
33    types::{Fixed, GlyphId},
34};
35use yoke::Yoke;
36
37// Drawing takes `&self`, so a scaler is meant to be shared between threads.
38const _: () = {
39    fn shared<T: Send + Sync>() {}
40    let _ = shared::<ScalerFont>;
41};
42
43impl From<AdjustedMetrics> for DrawnMetrics {
44    fn from(metrics: AdjustedMetrics) -> Self {
45        let mut computed = Self::default();
46        computed.h_advance = metrics.advance_width;
47        computed.x_bearing = metrics.lsb;
48        computed.has_overlaps = metrics.has_overlaps;
49        computed
50    }
51}
52
53/// Draws glyphs from a [`Font`] at a size, with optional hinting.
54///
55/// Drawing takes `&self`, so a scaler is cheap to draw from and safe to
56/// share. Working memory comes from the caller or from short lived scratch.
57/// [`reconfigure`](Self::reconfigure) sets up a scaler for another font,
58/// size, or hinting and reuses the existing memory. This lets a cache of
59/// scalers reuse its entries.
60pub struct ScalerFont {
61    /// The font, with its outlines resolved once rather than for every draw.
62    outlines: Yoke<OutlineGlyphs<'static>, Box<Font>>,
63    /// The order the outline sources were resolved in, which glyph extents
64    /// follow too.
65    order: GlyphSourceOrder,
66    /// Pixels per em, or `None` for font units.
67    ppem: Option<f32>,
68    hint_options: Option<HintOptions>,
69    state: State,
70}
71
72/// A font and the order in which its outline sources are tried.
73///
74/// A [`Font`] converts to a source with [`GlyphSourceOrder::DEFAULT`], so the
75/// [`ScalerFont`] constructors accept a font directly.
76#[derive(Clone)]
77pub struct ScalerSource {
78    font: Font,
79    order: GlyphSourceOrder,
80}
81
82impl ScalerSource {
83    /// Creates a source that draws `font` from its outline sources in
84    /// `order`.
85    ///
86    /// See [`Font::outline_glyphs_with`] for how the order selects them.
87    pub fn new(font: Font, order: GlyphSourceOrder) -> Self {
88        Self { font, order }
89    }
90}
91
92impl core::fmt::Debug for ScalerSource {
93    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
94        f.debug_struct("ScalerSource")
95            .field("order", &self.order)
96            .finish_non_exhaustive()
97    }
98}
99
100impl From<Font> for ScalerSource {
101    fn from(font: Font) -> Self {
102        Self::new(font, GlyphSourceOrder::DEFAULT)
103    }
104}
105
106impl core::fmt::Debug for ScalerFont {
107    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108        f.debug_struct("ScalerFont")
109            .field("outline_source", &self.outline_source())
110            .field("ppem", &self.ppem)
111            .field("hint_options", &self.hint_options)
112            .field("hint_engine", &self.hint_engine())
113            .finish_non_exhaustive()
114    }
115}
116
117/// The hinting engine configured for the font and size.
118enum State {
119    /// No hinting. Hinting was not requested, there is no size, or the font
120    /// has no hints.
121    Unhinted,
122    /// The setup programs failed, so every draw fails too.
123    /// Boxed because it is rare and would otherwise double the size of every
124    /// state.
125    Failed(Box<ScaleError>),
126    TrueType(Box<truetype::TrueTypeHinter>),
127    PostScript(postscript::PostScriptHinter),
128    Auto(autohint::Instance),
129}
130
131impl ScalerFont {
132    /// Creates a scaler that draws `font` unhinted.
133    ///
134    /// `ppem` is the size in pixels per em, or `None` for font units.
135    ///
136    /// A TrueType font that needs its instructions to draw correct contours is
137    /// hinted anyway when there is a size. It is hinted with
138    /// [`HintOptions::interpreter`], which [`hint_options`](Self::hint_options)
139    /// then returns. Use [`force_unhinted`](Self::force_unhinted) to draw
140    /// such a font unhinted.
141    pub fn unhinted(font: impl Into<ScalerSource>, ppem: Option<f32>) -> Self {
142        let source = font.into();
143        let tricky = ppem.is_some() && source.font.is_hint_reliant();
144        let mut scaler = Self {
145            order: source.order,
146            outlines: resolve_outlines(source),
147            ppem,
148            hint_options: tricky.then(HintOptions::interpreter),
149            state: State::Unhinted,
150        };
151        scaler.configure();
152        scaler
153    }
154
155    /// Creates a scaler that draws `font` unhinted, even a font that needs
156    /// its instructions to draw correct contours.
157    ///
158    /// `ppem` is the size in pixels per em, or `None` for font units.
159    pub fn force_unhinted(font: impl Into<ScalerSource>, ppem: Option<f32>) -> Self {
160        let source = font.into();
161        Self {
162            order: source.order,
163            outlines: resolve_outlines(source),
164            ppem,
165            hint_options: None,
166            state: State::Unhinted,
167        }
168    }
169
170    /// Creates a scaler that draws `font` at `ppem` pixels per em, hinted as
171    /// `hint_options` asks.
172    ///
173    /// Hinting fits an outline to a pixel grid, so it always requires a size.
174    pub fn hinted(font: impl Into<ScalerSource>, ppem: f32, hint_options: HintOptions) -> Self {
175        let source = font.into();
176        let mut scaler = Self {
177            order: source.order,
178            outlines: resolve_outlines(source),
179            ppem: Some(ppem),
180            hint_options: Some(hint_options),
181            state: State::Unhinted,
182        };
183        scaler.configure();
184        scaler
185    }
186
187    /// Reconfigures the scaler to draw `font` at `ppem` pixels per em, hinted
188    /// as `hint_options` asks.
189    ///
190    /// The memory from the previous setup is reused. This lets an entry in a
191    /// cache of hinted scalers change its font, size, and hinting without
192    /// creating a new scaler. Setup reruns the font's programs, so avoid
193    /// calling this on a hot path. An unhinted scaler is cheap to create, so
194    /// there is nothing to gain from reconfiguring one; this always leaves
195    /// the scaler hinted.
196    pub fn reconfigure(
197        &mut self,
198        font: impl Into<ScalerSource>,
199        ppem: f32,
200        hint_options: HintOptions,
201    ) {
202        let source = font.into();
203        self.order = source.order;
204        self.outlines = resolve_outlines(source);
205        self.ppem = Some(ppem);
206        self.hint_options = Some(hint_options);
207        self.configure();
208    }
209
210    /// Returns the font, set to its variation location.
211    pub fn font(&self) -> &Font {
212        self.outlines.backing_cart()
213    }
214
215    /// Returns the table that provides the outlines.
216    ///
217    /// This is [`OutlineSource::Varc`] if `VARC` composites are drawn. The
218    /// composites cover only some glyphs, and the rest come from the
219    /// [`underlying_outline_source`](Self::underlying_outline_source).
220    /// Returns `None` if the font has no outlines from the scaler's sources.
221    pub fn outline_source(&self) -> Option<OutlineSource> {
222        self.outlines.get().source()
223    }
224
225    /// Returns the table that draws the glyphs `VARC` does not cover and the
226    /// components of those it does.
227    ///
228    /// This is the same as [`outline_source`](Self::outline_source) unless
229    /// `VARC` composites are drawn, and is never [`OutlineSource::Varc`].
230    /// Returns `None` if the font has no outlines from the scaler's sources.
231    pub fn underlying_outline_source(&self) -> Option<OutlineSource> {
232        self.outlines.get().underlying_source()
233    }
234
235    /// Returns the size in pixels per em, or `None` for font units.
236    pub fn ppem(&self) -> Option<f32> {
237        self.ppem
238    }
239
240    /// Returns the size glyphs are hinted at, in pixels per em.
241    ///
242    /// This is [`ppem`](Self::ppem), except for a TrueType font whose `head`
243    /// table asks for whole pixel sizes. The interpreter then hints at the
244    /// size rounded to a whole pixel, and draws at it too when the font turns
245    /// hinting off.
246    pub fn hinted_ppem(&self) -> Option<f32> {
247        match &self.state {
248            State::TrueType(hinter) => hinter.ppem().or(self.ppem),
249            _ => self.ppem,
250        }
251    }
252
253    /// Returns the hinting engine the scaler runs, or `None` if it does not
254    /// hint.
255    ///
256    /// A scaler does not hint without hinting options or a size, or where
257    /// setup failed, which [`hinting_error`](Self::hinting_error) reports. A
258    /// TrueType font can turn its hinting off at some sizes, so the engine
259    /// can be selected where [`is_hinted`](Self::is_hinted) is false.
260    pub fn hint_engine(&self) -> Option<HintEngine> {
261        match &self.state {
262            State::TrueType(_) => Some(HintEngine::TrueType),
263            State::PostScript(_) => Some(HintEngine::PostScript),
264            State::Auto(_) => Some(HintEngine::Auto),
265            State::Unhinted | State::Failed(_) => None,
266        }
267    }
268
269    /// Returns the hinting configuration, if any.
270    pub fn hint_options(&self) -> Option<HintOptions> {
271        self.hint_options
272    }
273
274    /// Returns true if glyphs are hinted at this size.
275    ///
276    /// Returns false if no hinting is configured, if the size is in font
277    /// units, or if the font has nothing to hint with. Also returns false if
278    /// the font disables hinting at this size through its control value
279    /// program or `gasp` table.
280    ///
281    /// A `VARC` composite is drawn unhinted unless the automatic hinter is
282    /// in use, even where this returns true.
283    pub fn is_hinted(&self) -> bool {
284        match &self.state {
285            State::TrueType(hinter) => hinter.is_enabled(),
286            State::PostScript(_) | State::Auto(_) => true,
287            State::Unhinted | State::Failed(_) => false,
288        }
289    }
290
291    /// Returns the error that prevented hinting setup, if any.
292    ///
293    /// Every draw fails with this error until the size or hinting changes.
294    pub fn hinting_error(&self) -> Option<&ScaleError> {
295        match &self.state {
296            State::Failed(error) => Some(error),
297            _ => None,
298        }
299    }
300
301    /// Returns the smoothing and grid fitting the font requests at this size.
302    ///
303    /// Returns `None` unless the TrueType interpreter hints the font. With
304    /// [classic ClearType](HintOptions::with_classic_cleartype), this is the
305    /// default policy rather than the font's.
306    pub fn raster_policy(&self) -> Option<RasterPolicy> {
307        match &self.state {
308            State::TrueType(hinter) => Some(hinter.raster_policy()),
309            _ => None,
310        }
311    }
312
313    /// Returns the metrics describing the font as a whole, in pixels at
314    /// [`ppem`](Self::ppem), or in font units without a size.
315    ///
316    /// These are scaled linearly; hinting does not change them.
317    pub fn metrics(&self) -> ScaledMetrics<'_, ScaleF32> {
318        let metrics = self.font().metrics();
319        metrics.scaled_f32(self.ppem.unwrap_or(metrics.units_per_em as f32))
320    }
321
322    /// Returns the font-wide style measurements, in pixels at
323    /// [`ppem`](Self::ppem), or in font units without a size.
324    ///
325    /// These are scaled linearly; hinting does not change them.
326    pub fn style_metrics(&self) -> ScaledStyleMetrics<'_, ScaleF32> {
327        let style = self.font().style_metrics();
328        style.scaled_f32(self.ppem.unwrap_or(style.units_per_em as f32))
329    }
330
331    /// Returns the glyph metrics, in pixels at [`ppem`](Self::ppem), or in
332    /// font units without a size.
333    ///
334    /// These are scaled linearly; hinting does not change them. For a hinted
335    /// glyph's advance, draw its outline: [`ScalerOutline::draw`] returns it
336    /// in [`DrawnMetrics::h_advance`].
337    ///
338    /// Glyph extents are measured from the sources the scaler draws from, in
339    /// the same order.
340    pub fn linear_glyph_metrics(&self) -> ScaledGlyphMetrics<'_, 'static, ScaleF32> {
341        let font = self.font();
342        let ppem = self.ppem.unwrap_or(font.units_per_em() as f32);
343        font.glyph_metrics()
344            .with_extents_source_order(self.order)
345            .scaled_f32(ppem)
346    }
347
348    /// Returns the outline of `glyph`, resolved for drawing.
349    ///
350    /// Returns `None` if the font has no outline for the glyph.
351    pub fn outline(&self, glyph: GlyphId) -> Option<ScalerOutline<'_>> {
352        let outline = self.outlines.get().get(glyph)?;
353        Some(ScalerOutline {
354            scaler: self,
355            outline,
356        })
357    }
358
359    /// Sets the state up for the font, size and hinting.
360    fn configure(&mut self) {
361        // Reuse the memory from the last configuration where the engine is
362        // the same.
363        let previous = core::mem::replace(&mut self.state, State::Unhinted);
364        let (Some(hinting), Some(ppem)) = (&self.hint_options, self.ppem) else {
365            return;
366        };
367        self.state = match EngineChoice::new(self.font(), self.outlines.get(), hinting) {
368            EngineChoice::TrueType => {
369                let mut hinter = match previous {
370                    State::TrueType(hinter) => hinter,
371                    _ => Box::default(),
372                };
373                match hinter.reconfigure(self.font(), ppem, hinting) {
374                    Ok(()) => State::TrueType(hinter),
375                    Err(error) => State::Failed(Box::new(error)),
376                }
377            }
378            EngineChoice::PostScript(cff) => {
379                let mut hinter = match previous {
380                    State::PostScript(hinter) => hinter,
381                    _ => Default::default(),
382                };
383                hinter.reconfigure(cff, self.font().normalized_coords(), ppem);
384                State::PostScript(hinter)
385            }
386            // The font keeps what the automatic hinter derives from it, so
387            // every scaler of the font at this location shares it.
388            EngineChoice::Auto => State::Auto(autohint::Instance::from_state(
389                autohint::LocatedState::of_outlines(self.font(), *self.outlines.get()),
390                hinting.outline_target(),
391            )),
392            EngineChoice::None => State::Unhinted,
393        };
394    }
395
396    /// Draws `outline` unhinted at `ppem`.
397    ///
398    /// This is the scaler's size, except when the TrueType interpreter runs
399    /// at a rounded size.
400    fn draw_unhinted(
401        &self,
402        outline: &OutlineGlyph<'_>,
403        ppem: Option<f32>,
404        memory: Option<&mut [u8]>,
405        sink: &mut dyn PathSink,
406    ) -> Result<DrawnMetrics, ScaleError> {
407        let glyph = outline.glyph();
408        let options = draw_options(ppem);
409        let supplied = memory.as_ref().map_or(0, |memory| memory.len());
410        let memory = memory.map_or(DrawMemory::Scratch, DrawMemory::Buffer);
411        let metrics = outline.draw(&options, memory, sink).map_err(|error| {
412            ScaleError::from_outline(error, outline.required_memory_size(), supplied)
413        })?;
414        let mut h_advance = metrics.h_advance;
415        // A charstring that states no width, in a subfont with no default,
416        // falls back to the metrics tables, as FreeType does.
417        if let (None, OutlineGlyph::Cff(charstring)) = (h_advance, outline) {
418            // In whole units, as FreeType rounds them.
419            let advance = self.font().glyph_metrics().h_advance_exact(glyph).round();
420            let advance = Fixed::from_i32(advance.to_i32());
421            let transform = charstring
422                .cff_font()
423                .transform(charstring.subfont(), options.ppem);
424            h_advance = Some(transform.transform_h_metric(advance).to_f32());
425        }
426        // A charstring can state a width below zero. Report none, as when
427        // hinting, and leave the caller to its linear metrics.
428        if let OutlineGlyph::Cff(_) = outline {
429            h_advance = h_advance.filter(|advance| *advance >= 0.0);
430        }
431        // When hinting is asked for, FreeType rounds the advance even where
432        // nothing ran, which keeps a font's unhinted glyphs in step with its
433        // hinted ones. A composite states no advance to round.
434        if self.hint_options.is_some() && !matches!(outline, OutlineGlyph::Varc(_)) {
435            h_advance = h_advance.map(|advance| advance.round());
436        }
437        let mut metrics = metrics;
438        metrics.h_advance = h_advance;
439        Ok(metrics)
440    }
441}
442
443/// The engine chosen to hint a font.
444enum EngineChoice<'a> {
445    /// The TrueType interpreter.
446    TrueType,
447    /// The PostScript hinter for these charstrings.
448    PostScript(&'a CffFontRef<'a>),
449    /// The automatic hinter.
450    Auto,
451    /// No engine. Type 1 fonts and fonts without outlines are not hinted.
452    None,
453}
454
455impl<'a> EngineChoice<'a> {
456    /// Chooses the engine that `hinting` requests for `outlines`, the
457    /// outlines of `font` that the scaler draws.
458    ///
459    /// Requesting the interpreter runs the font's own instructions, whichever
460    /// kind it contains. Requesting the FreeType fallback runs the TrueType
461    /// interpreter only when the font's programs are worth running, and
462    /// always runs a PostScript font's own hints. A TrueType font that needs
463    /// its instructions for correct contours always runs them.
464    fn new(font: &'a Font, outlines: &OutlineGlyphs<'a>, hinting: &HintOptions) -> Self {
465        // The engine suits the outlines a composite is drawn from. Only the
466        // automatic hinter hints a `VARC` composite itself; the others draw
467        // it unhinted, since hints fitted to the grid would not survive the
468        // components' transforms.
469        let source = outlines.underlying_source();
470        // A font that needs its instructions to draw correct contours is
471        // always run through them, as FreeType runs one.
472        if source == Some(OutlineSource::Glyf) && font.is_hint_reliant() {
473            return Self::TrueType;
474        }
475        let instructions = match hinting.engine() {
476            EngineRequest::Interpreter => true,
477            EngineRequest::Auto => false,
478            EngineRequest::AutoFallback => match source {
479                Some(OutlineSource::Glyf) => truetype::prefer_interpreter(font.tables()),
480                _ => true,
481            },
482        };
483        if !instructions {
484            return Self::Auto;
485        }
486        match source {
487            Some(OutlineSource::Glyf) => Self::TrueType,
488            Some(OutlineSource::Cff2 | OutlineSource::Cff) => {
489                outlines.cff_font().map_or(Self::None, Self::PostScript)
490            }
491            _ => Self::None,
492        }
493    }
494}
495
496/// An outline from a [`ScalerFont`], resolved for drawing.
497///
498/// Holds the outline in its source format. A caller can size its own
499/// working memory and then draw without looking up the glyph again.
500#[derive(Clone)]
501pub struct ScalerOutline<'a> {
502    scaler: &'a ScalerFont,
503    outline: OutlineGlyph<'a>,
504}
505
506impl core::fmt::Debug for ScalerOutline<'_> {
507    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
508        f.debug_struct("ScalerOutline")
509            .field("glyph", &self.glyph())
510            .field("source", &self.source())
511            .finish()
512    }
513}
514
515impl ScalerOutline<'_> {
516    /// Returns the glyph identifier.
517    pub fn glyph(&self) -> GlyphId {
518        self.outline.glyph()
519    }
520
521    /// Returns the table that contains the outline.
522    pub fn source(&self) -> OutlineSource {
523        self.outline.source()
524    }
525
526    /// Returns the size in bytes of the working memory needed by
527    /// [`draw`](Self::draw).
528    ///
529    /// Returns zero for the automatic hinter, which manages its own memory.
530    pub fn required_memory_size(&self) -> usize {
531        match (&self.scaler.state, &self.outline) {
532            (State::Auto(_), _) => 0,
533            (State::TrueType(hinter), OutlineGlyph::Glyf(glyf)) if hinter.is_enabled() => {
534                hinter.required_memory_size(glyf)
535            }
536            _ => self.outline.required_memory_size(),
537        }
538    }
539
540    /// Draws the outline into `sink` with the scaler's size and hinting.
541    ///
542    /// With [`DrawMemory::Scratch`], working memory comes from short lived
543    /// scratch, which may be allocated on the heap for a large glyph program.
544    /// Supplied `memory` must hold at least
545    /// [`required_memory_size`](Self::required_memory_size) bytes, or the
546    /// draw fails with [`ScaleError::InsufficientMemory`].
547    pub fn draw(
548        &self,
549        memory: DrawMemory<'_>,
550        sink: &mut impl PathSink,
551    ) -> Result<DrawnMetrics, ScaleError> {
552        let memory = match memory {
553            DrawMemory::Buffer(buffer) => Some(buffer),
554            _ => None,
555        };
556        self.draw_dyn(memory, sink)
557    }
558
559    /// Dispatches a draw through a trait object so the engines are compiled
560    /// once rather than once per sink type.
561    fn draw_dyn(
562        &self,
563        memory: Option<&mut [u8]>,
564        sink: &mut dyn PathSink,
565    ) -> Result<DrawnMetrics, ScaleError> {
566        let scaler = self.scaler;
567        match (&scaler.state, &self.outline) {
568            (State::Failed(error), _) => Err(ScaleError::clone(error)),
569            (State::Auto(instance), _) => {
570                let context = autohint::FontContext::for_glyph(
571                    scaler.font(),
572                    *scaler.outlines.get(),
573                    &self.outline,
574                );
575                let size = scaler.ppem.map_or_else(Size::unscaled, Size::new);
576                let mut sink = sink;
577                let mut metrics = DrawnMetrics::from(
578                    instance
579                        .draw(&context, size, self.glyph(), PathStyle::FreeType, &mut sink)
580                        .map_err(|error| {
581                            let supplied = memory.as_ref().map_or(0, |memory| memory.len());
582                            ScaleError::from_draw_error(error, 0, supplied)
583                        })?,
584                );
585                // A composite states no advance, whatever hints it.
586                if matches!(self.outline, OutlineGlyph::Varc(_)) {
587                    metrics.h_advance = None;
588                }
589                Ok(metrics)
590            }
591            (State::TrueType(hinter), OutlineGlyph::Glyf(glyf)) if hinter.is_enabled() => {
592                hinter.draw(glyf, memory, sink)
593            }
594            (State::PostScript(hinter), OutlineGlyph::Cff(charstring)) => {
595                hinter.draw(scaler.font(), charstring, sink)
596            }
597            (State::TrueType(hinter), OutlineGlyph::Glyf(_)) => {
598                let ppem = hinter.program_unhinted_ppem().or(scaler.ppem);
599                scaler.draw_unhinted(&self.outline, ppem, memory, sink)
600            }
601            _ => scaler.draw_unhinted(&self.outline, scaler.ppem, memory, sink),
602        }
603    }
604}
605
606/// Keeps the font with its outlines, resolved once for every draw.
607fn resolve_outlines(source: ScalerSource) -> Yoke<OutlineGlyphs<'static>, Box<Font>> {
608    let order = source.order;
609    Yoke::attach_to_cart(Box::new(source.font), |font| {
610        font.outline_glyphs_with(order)
611    })
612}
613
614/// Returns the read-fonts draw options for a size, with FreeType's contour
615/// start points.
616fn draw_options(ppem: Option<f32>) -> DrawOptions {
617    DrawOptions {
618        ppem,
619        contour_start: PathContourStart::ScanBackward,
620    }
621}