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}