xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/skrifa-0.15.5/src/scale.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //! Loading, scaling and hinting of glyph outlines.
2 //!
3 //! Scaling is the process of decoding an outline, applying variation deltas,
4 //! and executing [hinting](https://en.wikipedia.org/wiki/Font_hinting)
5 //! instructions for a glyph of a particular size.
6 //!
7 //! ## It all starts with a context
8 //!
9 //! The scaling process generally requires dynamic memory allocations to hold
10 //! intermediate results. In addition, TrueType hinting requires execution
11 //! of a set of programs to generate state for any instance of a font before
12 //! applying glyph instructions.
13 //!
14 //! To amortize the cost of memory allocations and support caching of hinting
15 //! state, we use the [`Context`] type. This type is opaque and contains
16 //! internal buffers and caches that can be reused by subsequent scaling
17 //! operations.
18 //!
19 //! Contexts exist purely as a performance optimization and management of them
20 //! is up to the user. There are several reasonable strategies of varying
21 //! complexity:
22 //!
23 //! * If performance and heap traffic are not significant concerns, creating
24 //! a context per glyph (or glyph run) works in a pinch.
25 //! * When making use of a single shared glyph cache, this is an ideal place to
26 //! store a context.
27 //! * Multithreaded code can use thread locals or a global pool of contexts.
28 //!
29 //! Regardless of how you manage them, creating a context is trivial:
30 //! ```
31 //! use skrifa::scale::Context;
32 //!
33 //! let mut context = Context::new();
34 //! ```
35 //!
36 //! For simplicity, the examples below will use a local context.
37 //!
38 //! ## Building a scaler
39 //!
40 //! Now that we have a [`Context`], we can use the
41 //! [`new_scaler`](Context::new_scaler) method to generate an instance of the
42 //! [`ScalerBuilder`] type that allows us to configure and build a [`Scaler`].
43 //!
44 //! Assuming you have some `font` (any type that implements
45 //! [`TableProvider`]), this will build a scaler for
46 //! a size of 16px:
47 //!
48 //! ```
49 //! # use skrifa::{scale::*, instance::Size};
50 //! # fn build_scaler(font: read_fonts::FontRef) {
51 //! let mut context = Context::new();
52 //! let mut scaler = context.new_scaler()
53 //!     .size(Size::new(16.0))
54 //!     .build(&font);
55 //! # }
56 //! ```
57 //!
58 //! For variable fonts, the
59 //! [`variation_settings`](ScalerBuilder::variation_settings) method can
60 //! be used to specify user coordinates for selecting an instance:
61 //!
62 //! ```
63 //! # use skrifa::{scale::*, instance::Size};
64 //! # fn build_scaler(font: read_fonts::FontRef) {
65 //! let mut context = Context::new();
66 //! let mut scaler = context.new_scaler()
67 //!     .size(Size::new(16.0))
68 //!     .variation_settings(&[("wght", 720.0), ("wdth", 75.0)])
69 //!     .build(&font);
70 //! # }
71 //! ```
72 //!
73 //! If you already have coordinates in normalized design space, you can specify
74 //! those directly with the
75 //! [`normalized_coords`](ScalerBuilder::normalized_coords) method.
76 //!
77 //! See the [`ScalerBuilder`] type for all available configuration options.
78 //!
79 //! ## Getting an outline
80 //!
81 //! Once we have a configured scaler, extracting an outline is fairly simple.
82 //! The [`Scaler::outline`] method uses a callback approach where the user
83 //! provides an implementation of the [`Pen`] trait and the appropriate methods
84 //! are invoked for each resulting path element of the scaled outline.
85 //!
86 //! Assuming we constructed a scaler as above, let's load a glyph and convert
87 //! it into an SVG path:
88 //!
89 //! ```
90 //! # use skrifa::{scale::*, GlyphId, instance::Size};
91 //! # fn build_scaler(font: read_fonts::FontRef) {
92 //! # let mut context = Context::new();
93 //! # let mut scaler = context.new_scaler()
94 //! #    .size(Size::new(16.0))
95 //! #    .build(&font);
96 //! // Create a type for holding our SVG path.
97 //! #[derive(Default)]
98 //! struct SvgPath(String);
99 //!
100 //! // Implement the Pen trait for this type. This emits the appropriate
101 //! // SVG path commands for each element type.
102 //! impl Pen for SvgPath {
103 //!     fn move_to(&mut self, x: f32, y: f32) {
104 //!         self.0.push_str(&format!("M{x:.1},{y:.1} "));
105 //!     }
106 //!
107 //!     fn line_to(&mut self, x: f32, y: f32) {
108 //!         self.0.push_str(&format!("L{x:.1},{y:.1} "));
109 //!     }
110 //!
111 //!     fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
112 //!         self.0
113 //!             .push_str(&format!("Q{cx0:.1},{cy0:.1} {x:.1},{y:.1} "));
114 //!     }
115 //!
116 //!     fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
117 //!         self.0.push_str(&format!(
118 //!             "C{cx0:.1},{cy0:.1} {cx1:.1},{cy1:.1} {x:.1},{y:.1} "
119 //!         ));
120 //!     }
121 //!
122 //!     fn close(&mut self) {
123 //!         self.0.push_str("z ");
124 //!     }
125 //! }
126 //!
127 //! let mut path = SvgPath::default();
128 //!
129 //! // Scale an outline for glyph 20 and invoke the appropriate methods
130 //! // to build an SVG path.
131 //! scaler.outline(GlyphId::new(20), &mut path);
132 //!
133 //! // Print our pretty new path.
134 //! println!("{}", path.0);
135 //! # }
136 //! ```
137 //!
138 //! The pen based interface is designed to be flexible. Output can be sent
139 //! directly to a software rasterizer for scan conversion, converted to an
140 //! owned path representation (such as a kurbo
141 //! [`BezPath`](https://docs.rs/kurbo/latest/kurbo/struct.BezPath.html)) for
142 //! further analysis and transformation, or fed into other crates like
143 //! [vello](https://github.com/linebender/vello),
144 //! [lyon](https://github.com/nical/lyon) or
145 //! [pathfinder](https://github.com/servo/pathfinder) for GPU rendering.
146 
147 pub use super::outline::{AdjustedMetrics as ScalerMetrics, DrawError as Error};
148 pub use read_fonts::types::Pen;
149 
150 use core::borrow::Borrow;
151 
152 use read_fonts::{
153     types::{Fixed, GlyphId},
154     TableProvider,
155 };
156 
157 use super::{
158     font::UniqueId,
159     instance::{NormalizedCoord, Size},
160     outline,
161     setting::VariationSetting,
162 };
163 
164 /// Result type for errors that may occur when loading glyphs.
165 pub type Result<T> = core::result::Result<T, Error>;
166 
167 /// Modes for hinting.
168 ///
169 /// Only the `glyf` source supports all hinting modes.
170 #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
171 pub enum Hinting {
172     /// Hinting is disabled.
173     #[default]
174     None,
175     /// "Full" hinting mode. May generate rough outlines and poor horizontal
176     /// spacing.
177     Full,
178     /// Light hinting mode. This prevents most movement in the horizontal
179     /// direction with the exception of a per-font backward compatibility
180     /// opt in.
181     Light,
182     /// Same as light, but with additional support for RGB subpixel rendering.
183     LightSubpixel,
184     /// Same as light subpixel, but always prevents adjustment in the
185     /// horizontal direction. This is the default mode.
186     VerticalSubpixel,
187 }
188 
189 /// Context for scaling glyphs.
190 ///
191 /// This type contains temporary memory buffers and various internal caches to
192 /// accelerate the glyph scaling process.
193 ///
194 /// See the [module level documentation](crate::scale#it-all-starts-with-a-context)
195 /// for more detail.
196 #[derive(Clone, Default, Debug)]
197 pub struct Context {
198     /// Storage for normalized variation coordinates.
199     coords: Vec<NormalizedCoord>,
200     /// Storage for variation settings.
201     variations: Vec<VariationSetting>,
202 }
203 
204 impl Context {
205     /// Creates a new glyph scaling context.
new() -> Self206     pub fn new() -> Self {
207         Self::default()
208     }
209 
210     /// Returns a builder for configuring a glyph scaler.
new_scaler(&mut self) -> ScalerBuilder211     pub fn new_scaler(&mut self) -> ScalerBuilder {
212         ScalerBuilder::new(self)
213     }
214 }
215 
216 /// Builder for configuring a glyph scaler.
217 ///
218 /// See the [module level documentation](crate::scale#building-a-scaler)
219 /// for more detail.
220 pub struct ScalerBuilder<'a> {
221     context: &'a mut Context,
222     cache_key: Option<UniqueId>,
223     size: Size,
224     hint: Option<Hinting>,
225 }
226 
227 impl<'a> ScalerBuilder<'a> {
228     /// Creates a new builder for configuring a scaler with the given context.
new(context: &'a mut Context) -> Self229     pub fn new(context: &'a mut Context) -> Self {
230         context.coords.clear();
231         context.variations.clear();
232         Self {
233             context,
234             cache_key: None,
235             size: Size::unscaled(),
236             hint: None,
237         }
238     }
239 
240     /// Sets a unique font identifier for hint state caching. Specifying `None` will
241     /// disable caching.
cache_key(mut self, key: Option<UniqueId>) -> Self242     pub fn cache_key(mut self, key: Option<UniqueId>) -> Self {
243         self.cache_key = key;
244         self
245     }
246 
247     /// Sets the requested font size.
248     ///
249     /// The default value is `Size::unscaled()` and outlines will be generated
250     /// in font units.
size(mut self, size: Size) -> Self251     pub fn size(mut self, size: Size) -> Self {
252         self.size = size;
253         self
254     }
255 
256     /// Sets the hinting mode.
257     ///
258     /// Passing `None` will disable hinting.
hint(mut self, hint: Option<Hinting>) -> Self259     pub fn hint(mut self, hint: Option<Hinting>) -> Self {
260         self.hint = hint;
261         self
262     }
263 
264     /// Specifies a variation with a set of normalized coordinates.
265     ///
266     /// This will clear any variations specified with the variations method.
normalized_coords<I>(self, coords: I) -> Self where I: IntoIterator, I::Item: Borrow<NormalizedCoord>,267     pub fn normalized_coords<I>(self, coords: I) -> Self
268     where
269         I: IntoIterator,
270         I::Item: Borrow<NormalizedCoord>,
271     {
272         self.context.variations.clear();
273         self.context.coords.clear();
274         self.context
275             .coords
276             .extend(coords.into_iter().map(|v| *v.borrow()));
277         self
278     }
279 
280     /// Appends the given sequence of variation settings. This will clear any
281     /// variations specified as normalized coordinates.
282     ///
283     /// This methods accepts any type which can be converted into an iterator
284     /// that yields a sequence of values that are convertible to
285     /// [`VariationSetting`]. Various conversions from tuples are provided.
286     ///
287     /// The following are all equivalent:
288     ///
289     /// ```
290     /// # use skrifa::{scale::*, setting::VariationSetting, Tag};
291     /// # let mut context = Context::new();
292     /// # let builder = context.new_scaler();
293     /// // slice of VariationSetting
294     /// builder.variation_settings(&[
295     ///     VariationSetting::new(Tag::new(b"wgth"), 720.0),
296     ///     VariationSetting::new(Tag::new(b"wdth"), 50.0),
297     /// ])
298     /// # ; let builder = context.new_scaler();
299     /// // slice of (Tag, f32)
300     /// builder.variation_settings(&[(Tag::new(b"wght"), 720.0), (Tag::new(b"wdth"), 50.0)])
301     /// # ; let builder = context.new_scaler();
302     /// // slice of (&str, f32)
303     /// builder.variation_settings(&[("wght", 720.0), ("wdth", 50.0)])
304     /// # ;
305     ///
306     /// ```
307     ///
308     /// Iterators that yield the above types are also accepted.
variation_settings<I>(self, settings: I) -> Self where I: IntoIterator, I::Item: Into<VariationSetting>,309     pub fn variation_settings<I>(self, settings: I) -> Self
310     where
311         I: IntoIterator,
312         I::Item: Into<VariationSetting>,
313     {
314         self.context.coords.clear();
315         self.context
316             .variations
317             .extend(settings.into_iter().map(|v| v.into()));
318         self
319     }
320 
321     /// Builds a scaler using the currently configured settings
322     /// and the specified font.
build(mut self, font: &impl TableProvider<'a>) -> Scaler<'a>323     pub fn build(mut self, font: &impl TableProvider<'a>) -> Scaler<'a> {
324         self.resolve_variations(font);
325         let coords = &self.context.coords[..];
326         let outlines = outline::OutlineGlyphCollection::new(font);
327         Scaler {
328             outlines,
329             size: self.size,
330             coords,
331         }
332     }
333 
resolve_variations(&mut self, font: &impl TableProvider<'a>)334     fn resolve_variations(&mut self, font: &impl TableProvider<'a>) {
335         if self.context.variations.is_empty() {
336             return; // nop
337         }
338         let Ok(fvar) = font.fvar() else {
339             return; // nop
340         };
341         let Ok(axes) = fvar.axes() else {
342             return; // nop
343         };
344         let avar_mappings = font.avar().ok().map(|avar| avar.axis_segment_maps());
345         let axis_count = fvar.axis_count() as usize;
346         self.context.coords.clear();
347         self.context
348             .coords
349             .resize(axis_count, NormalizedCoord::default());
350         for variation in &self.context.variations {
351             // To permit non-linear interpolation, iterate over all axes to ensure we match
352             // multiple axes with the same tag:
353             // https://github.com/PeterConstable/OT_Drafts/blob/master/NLI/UnderstandingNLI.md
354             // We accept quadratic behavior here to avoid dynamic allocation and with the assumption
355             // that fonts contain a relatively small number of axes.
356             for (i, axis) in axes
357                 .iter()
358                 .enumerate()
359                 .filter(|(_, axis)| axis.axis_tag() == variation.selector)
360             {
361                 let coord = axis.normalize(Fixed::from_f64(variation.value as f64));
362                 let coord = avar_mappings
363                     .as_ref()
364                     .and_then(|mappings| mappings.get(i).transpose().ok())
365                     .flatten()
366                     .map(|mapping| mapping.apply(coord))
367                     .unwrap_or(coord);
368                 self.context.coords[i] = coord.to_f2dot14();
369             }
370         }
371     }
372 }
373 
374 /// Glyph scaler for a specific font and configuration.
375 ///
376 /// See the [module level documentation](crate::scale#getting-an-outline)
377 /// for more detail.
378 pub struct Scaler<'a> {
379     outlines: outline::OutlineGlyphCollection<'a>,
380     size: Size,
381     coords: &'a [NormalizedCoord],
382 }
383 
384 impl<'a> Scaler<'a> {
385     /// Returns the current set of normalized coordinates in use by the scaler.
normalized_coords(&self) -> &[NormalizedCoord]386     pub fn normalized_coords(&self) -> &[NormalizedCoord] {
387         self.coords
388     }
389 
390     /// Returns true if the scaler has a source for simple outlines.
has_outlines(&self) -> bool391     pub fn has_outlines(&self) -> bool {
392         self.outlines.format().is_some()
393     }
394 
395     /// Loads a simple outline for the specified glyph identifier and invokes the functions
396     /// in the given pen for the sequence of path commands that define the outline.
outline(&mut self, glyph_id: GlyphId, pen: &mut impl Pen) -> Result<ScalerMetrics>397     pub fn outline(&mut self, glyph_id: GlyphId, pen: &mut impl Pen) -> Result<ScalerMetrics> {
398         let outline = self
399             .outlines
400             .get(glyph_id)
401             .ok_or(Error::GlyphNotFound(glyph_id))?;
402         outline.draw((self.size, self.coords), pen)
403     }
404 }
405 
406 #[cfg(test)]
407 mod tests {
408     use super::{Context, Size};
409     use read_fonts::{scaler_test, types::GlyphId, FontRef, TableProvider};
410 
411     #[test]
vazirmatin_var()412     fn vazirmatin_var() {
413         compare_glyphs(
414             font_test_data::VAZIRMATN_VAR,
415             font_test_data::VAZIRMATN_VAR_GLYPHS,
416         );
417     }
418 
419     #[test]
cantarell_vf()420     fn cantarell_vf() {
421         compare_glyphs(
422             font_test_data::CANTARELL_VF_TRIMMED,
423             font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
424         );
425     }
426 
427     #[test]
noto_serif_display()428     fn noto_serif_display() {
429         compare_glyphs(
430             font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
431             font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
432         );
433     }
434 
435     #[test]
overlap_flags()436     fn overlap_flags() {
437         let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
438         let mut cx = Context::new();
439         let mut path = scaler_test::Path::default();
440         let mut scaler = cx.new_scaler().build(&font);
441         let glyph_count = font.maxp().unwrap().num_glyphs();
442         // GID 2 is a composite glyph with the overlap bit on a component
443         // GID 3 is a simple glyph with the overlap bit on the first flag
444         let expected_gids_with_overlap = vec![2, 3];
445         assert_eq!(
446             expected_gids_with_overlap,
447             (0..glyph_count)
448                 .filter(|gid| scaler
449                     .outline(GlyphId::new(*gid), &mut path)
450                     .unwrap()
451                     .has_overlaps)
452                 .collect::<Vec<_>>()
453         );
454     }
455 
compare_glyphs(font_data: &[u8], expected_outlines: &str)456     fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
457         let font = FontRef::new(font_data).unwrap();
458         let outlines = scaler_test::parse_glyph_outlines(expected_outlines);
459         let mut cx = Context::new();
460         let mut path = scaler_test::Path::default();
461         for expected_outline in &outlines {
462             if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
463                 continue;
464             }
465             path.elements.clear();
466             let size = if expected_outline.size != 0.0 {
467                 Size::new(expected_outline.size)
468             } else {
469                 Size::unscaled()
470             };
471             let mut scaler = cx
472                 .new_scaler()
473                 .size(size)
474                 .normalized_coords(&expected_outline.coords)
475                 .build(&font);
476             scaler
477                 .outline(expected_outline.glyph_id, &mut path)
478                 .unwrap();
479             if path.elements != expected_outline.path {
480                 panic!(
481                     "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
482                     expected_outline.glyph_id,
483                     expected_outline.size,
484                     expected_outline.coords,
485                     &path.elements,
486                     &expected_outline.path
487                 );
488             }
489         }
490     }
491 }
492