xref: /aosp_15_r20/external/cronet/third_party/rust/chromium_crates_io/vendor/skrifa-0.15.5/src/outline/mod.rs (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 //! Loading, scaling and hinting of glyph outlines.
2 //!
3 //! This module provides support for retrieving (optionally scaled and hinted)
4 //! glyph outlines in the form of vector paths.
5 //!
6 //! # Drawing a glyph
7 //!
8 //! Generating SVG style path data for a character (this assumes a local
9 //! variable `font` of type [`FontRef`](crate::FontRef)):
10 //!
11 //! ```rust
12 //! use skrifa::{
13 //!     instance::{LocationRef, Size},
14 //!     outline::{DrawSettings, OutlinePen},
15 //!     FontRef, MetadataProvider,
16 //! };
17 //!
18 //! # fn wrapper(font: FontRef) {
19 //! // First, grab the set of outline glyphs from the font.
20 //! let outlines = font.outline_glyphs();
21 //!
22 //! // Find the glyph identifier for our character.
23 //! let glyph_id = font.charmap().map('Q').unwrap();
24 //!
25 //! // Grab the outline glyph.
26 //! let glyph = outlines.get(glyph_id).unwrap();
27 //!
28 //! // Define how we want the glyph to be drawn. This creates
29 //! // settings for an instance without hinting at a size of
30 //! // 16px with no variations applied.
31 //! let settings = DrawSettings::unhinted(Size::new(16.0), LocationRef::default());
32 //!
33 //! // Alternatively, we can apply variations like so:
34 //! let var_location = font.axes().location(&[("wght", 650.0), ("wdth", 100.0)]);
35 //! let settings = DrawSettings::unhinted(Size::new(16.0), &var_location);
36 //!
37 //! // At this point, we need a "sink" to receive the resulting path. This
38 //! // is done by creating an implementation of the OutlinePen trait.
39 //!
40 //! // Let's make one that generates SVG path data.
41 //! #[derive(Default)]
42 //! struct SvgPath(String);
43 //!
44 //! // Implement the OutlinePen trait for this type. This emits the appropriate
45 //! // SVG path commands for each element type.
46 //! impl OutlinePen for SvgPath {
47 //!     fn move_to(&mut self, x: f32, y: f32) {
48 //!         self.0.push_str(&format!("M{x:.1},{y:.1} "));
49 //!     }
50 //!
51 //!     fn line_to(&mut self, x: f32, y: f32) {
52 //!         self.0.push_str(&format!("L{x:.1},{y:.1} "));
53 //!     }
54 //!
55 //!     fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
56 //!         self.0
57 //!             .push_str(&format!("Q{cx0:.1},{cy0:.1} {x:.1},{y:.1} "));
58 //!     }
59 //!
60 //!     fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
61 //!         self.0.push_str(&format!(
62 //!             "C{cx0:.1},{cy0:.1} {cx1:.1},{cy1:.1} {x:.1},{y:.1} "
63 //!         ));
64 //!     }
65 //!
66 //!     fn close(&mut self) {
67 //!         self.0.push_str("Z ");
68 //!     }
69 //! }
70 //! // Now, construct an instance of our pen.
71 //! let mut svg_path = SvgPath::default();
72 //!
73 //! // And draw the glyph!
74 //! glyph.draw(settings, &mut svg_path).unwrap();
75 //!
76 //! // See what we've drawn.
77 //! println!("{}", svg_path.0);
78 //! # }
79 //! ```
80 
81 mod cff;
82 mod embedded_hinting;
83 mod glyf;
84 
85 pub mod error;
86 
87 use read_fonts::{types::GlyphId, TableProvider};
88 
89 pub use embedded_hinting::{EmbeddedHinting, EmbeddedHintingInstance};
90 #[doc(inline)]
91 pub use error::DrawError;
92 
93 pub use read_fonts::types::Pen as OutlinePen;
94 
95 use super::{
96     instance::{LocationRef, NormalizedCoord, Size},
97     GLYF_COMPOSITE_RECURSION_LIMIT,
98 };
99 
100 /// Source format for an outline glyph.
101 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
102 pub enum OutlineGlyphFormat {
103     /// TrueType outlines sourced from the `glyf` table.
104     Glyf,
105     /// PostScript outlines sourced from the `CFF` table.
106     Cff,
107     /// PostScript outlines sourced from the `CFF2` table.
108     Cff2,
109 }
110 
111 /// Specifies the hinting strategy for memory size calculations.
112 #[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
113 pub enum Hinting {
114     /// Hinting is disabled.
115     #[default]
116     None,
117     /// Application of hints that are embedded in the font.
118     ///
119     /// For TrueType, these are bytecode instructions associated with each
120     /// glyph outline. For PostScript (CFF/CFF2), these are stem hints
121     /// encoded in the character string.
122     Embedded,
123 }
124 
125 /// Information and adjusted metrics generated while drawing an outline glyph.
126 ///
127 /// When applying hints to a TrueType glyph, the outline may be shifted in
128 /// the horizontal direction, affecting the left side bearing and advance width
129 /// of the glyph. This captures those metrics.
130 #[derive(Copy, Clone, Default, Debug)]
131 pub struct AdjustedMetrics {
132     /// True if the underlying glyph contains flags indicating the
133     /// presence of overlapping contours or components.
134     pub has_overlaps: bool,
135     /// If present, an adjusted left side bearing value generated by the
136     /// scaler.
137     ///
138     /// This is equivalent to the `horiBearingX` value in
139     /// [`FT_Glyph_Metrics`](https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_glyph_metrics).
140     pub lsb: Option<f32>,
141     /// If present, an adjusted advance width value generated by the
142     /// scaler.
143     ///
144     /// This is equivalent to the `advance.x` value in
145     /// [`FT_GlyphSlotRec`](https://freetype.org/freetype2/docs/reference/ft2-glyph_retrieval.html#ft_glyphslotrec).
146     pub advance_width: Option<f32>,
147 }
148 
149 /// Options that define how a [glyph](OutlineGlyph) is drawn to a
150 /// [pen](OutlinePen).
151 pub struct DrawSettings<'a> {
152     instance: DrawInstance<'a>,
153     memory: Option<&'a mut [u8]>,
154 }
155 
156 impl<'a> DrawSettings<'a> {
157     /// Creates settings for an unhinted draw operation with the given size and
158     /// location in variation space.
unhinted(size: Size, location: impl Into<LocationRef<'a>>) -> Self159     pub fn unhinted(size: Size, location: impl Into<LocationRef<'a>>) -> Self {
160         Self {
161             instance: DrawInstance::Unhinted(size, location.into()),
162             memory: None,
163         }
164     }
165 
166     /// Creates settings for a hinted draw operation using embedded hinting.
167     ///
168     /// The font size, location in variation space and hinting mode are
169     /// defined by the current configuration of the given hinting instance.
embedded_hinting(instance: &'a EmbeddedHintingInstance) -> Self170     pub fn embedded_hinting(instance: &'a EmbeddedHintingInstance) -> Self {
171         Self {
172             instance: DrawInstance::EmbeddedHinted(instance),
173             memory: None,
174         }
175     }
176 
177     /// Builder method to associate a user memory buffer to be used for
178     /// temporary allocations during drawing.
179     ///
180     /// The required size of this buffer can be computed using the
181     /// [`OutlineGlyph::draw_memory_size`] method.
182     ///
183     /// If not provided, any necessary memory will be allocated internally.
with_memory(mut self, memory: Option<&'a mut [u8]>) -> Self184     pub fn with_memory(mut self, memory: Option<&'a mut [u8]>) -> Self {
185         self.memory = memory;
186         self
187     }
188 }
189 
190 enum DrawInstance<'a> {
191     Unhinted(Size, LocationRef<'a>),
192     EmbeddedHinted(&'a EmbeddedHintingInstance),
193 }
194 
195 impl<'a, L> From<(Size, L)> for DrawSettings<'a>
196 where
197     L: Into<LocationRef<'a>>,
198 {
from(value: (Size, L)) -> Self199     fn from(value: (Size, L)) -> Self {
200         DrawSettings::unhinted(value.0, value.1.into())
201     }
202 }
203 
204 impl From<Size> for DrawSettings<'_> {
from(value: Size) -> Self205     fn from(value: Size) -> Self {
206         DrawSettings::unhinted(value, LocationRef::default())
207     }
208 }
209 
210 impl<'a> From<&'a EmbeddedHintingInstance> for DrawSettings<'a> {
from(value: &'a EmbeddedHintingInstance) -> Self211     fn from(value: &'a EmbeddedHintingInstance) -> Self {
212         DrawSettings::embedded_hinting(value)
213     }
214 }
215 
216 /// A scalable glyph outline.
217 ///
218 /// This can be sourced from the [`glyf`](https://learn.microsoft.com/en-us/typography/opentype/spec/glyf),
219 /// [`CFF`](https://learn.microsoft.com/en-us/typography/opentype/spec/cff) or
220 /// [`CFF2`](https://learn.microsoft.com/en-us/typography/opentype/spec/cff2)
221 /// tables. Use the [`format`](OutlineGlyph::format) method to determine which
222 /// was chosen for this glyph.
223 #[derive(Clone)]
224 pub struct OutlineGlyph<'a> {
225     kind: OutlineKind<'a>,
226 }
227 
228 impl<'a> OutlineGlyph<'a> {
229     /// Returns the underlying source format for this outline.
format(&self) -> OutlineGlyphFormat230     pub fn format(&self) -> OutlineGlyphFormat {
231         match &self.kind {
232             OutlineKind::Glyf(..) => OutlineGlyphFormat::Glyf,
233             OutlineKind::Cff(cff, ..) => {
234                 if cff.is_cff2() {
235                     OutlineGlyphFormat::Cff2
236                 } else {
237                     OutlineGlyphFormat::Cff
238                 }
239             }
240         }
241     }
242 
243     /// Returns a value indicating if the outline may contain overlapping
244     /// contours or components.
245     ///
246     /// For CFF outlines, returns `None` since this information is unavailable.
has_overlaps(&self) -> Option<bool>247     pub fn has_overlaps(&self) -> Option<bool> {
248         match &self.kind {
249             OutlineKind::Glyf(_, outline) => Some(outline.has_overlaps),
250             _ => None,
251         }
252     }
253 
254     /// Returns a value indicating whether the outline has hinting
255     /// instructions.
256     ///
257     /// For CFF outlines, returns `None` since this is unknown prior
258     /// to loading the outline.
has_hinting(&self) -> Option<bool>259     pub fn has_hinting(&self) -> Option<bool> {
260         match &self.kind {
261             OutlineKind::Glyf(_, outline) => Some(outline.has_hinting),
262             _ => None,
263         }
264     }
265 
266     /// Returns the size (in bytes) of the temporary memory required to draw
267     /// this outline.
268     ///
269     /// This is used to compute the size of the memory buffer required for the
270     /// [`DrawSettings::with_memory`] method.
271     ///
272     /// The `hinting` parameter determines which hinting method, if any, will
273     /// be used for drawing which has an effect on memory requirements.
274     ///
275     /// The appropriate hinting types are as follows:
276     ///
277     /// | For draw settings                  | Use hinting           |
278     /// |------------------------------------|-----------------------|
279     /// | [`DrawSettings::unhinted`]         | [`Hinting::None`]     |
280     /// | [`DrawSettings::embedded_hinting`] | [`Hinting::Embedded`] |
draw_memory_size(&self, hinting: Hinting) -> usize281     pub fn draw_memory_size(&self, hinting: Hinting) -> usize {
282         match &self.kind {
283             OutlineKind::Glyf(_, outline) => outline.required_buffer_size(hinting),
284             _ => 0,
285         }
286     }
287 
288     /// Draws the outline glyph with the given settings and emits the resulting
289     /// path commands to the specified pen.
draw<'s>( &self, settings: impl Into<DrawSettings<'a>>, pen: &mut impl OutlinePen, ) -> Result<AdjustedMetrics, DrawError>290     pub fn draw<'s>(
291         &self,
292         settings: impl Into<DrawSettings<'a>>,
293         pen: &mut impl OutlinePen,
294     ) -> Result<AdjustedMetrics, DrawError> {
295         let settings = settings.into();
296         match settings.instance {
297             DrawInstance::Unhinted(size, location) => {
298                 self.draw_unhinted(size, location, settings.memory, pen)
299             }
300             DrawInstance::EmbeddedHinted(hinter) => hinter.draw(self, settings.memory, pen),
301         }
302     }
303 
draw_unhinted( &self, size: Size, location: impl Into<LocationRef<'a>>, memory: Option<&mut [u8]>, pen: &mut impl OutlinePen, ) -> Result<AdjustedMetrics, DrawError>304     fn draw_unhinted(
305         &self,
306         size: Size,
307         location: impl Into<LocationRef<'a>>,
308         memory: Option<&mut [u8]>,
309         pen: &mut impl OutlinePen,
310     ) -> Result<AdjustedMetrics, DrawError> {
311         let ppem = size.ppem();
312         let coords = location.into().coords();
313         match &self.kind {
314             OutlineKind::Glyf(glyf, outline) => {
315                 with_glyf_memory(outline, Hinting::None, memory, |buf| {
316                     let mem = outline
317                         .memory_from_buffer(buf, Hinting::None)
318                         .ok_or(DrawError::InsufficientMemory)?;
319                     let scaled_outline = glyf.draw(mem, outline, ppem, coords)?;
320                     scaled_outline.to_path(pen)?;
321                     Ok(AdjustedMetrics {
322                         has_overlaps: outline.has_overlaps,
323                         lsb: Some(scaled_outline.adjusted_lsb().to_f32()),
324                         advance_width: Some(scaled_outline.adjusted_advance_width().to_f32()),
325                     })
326                 })
327             }
328             OutlineKind::Cff(cff, glyph_id, subfont_ix) => {
329                 let subfont = cff.subfont(*subfont_ix, ppem, coords)?;
330                 cff.draw(&subfont, *glyph_id, coords, false, pen)?;
331                 Ok(AdjustedMetrics::default())
332             }
333         }
334     }
335 }
336 
337 #[derive(Clone)]
338 enum OutlineKind<'a> {
339     Glyf(glyf::Outlines<'a>, glyf::Outline<'a>),
340     // Third field is subfont index
341     Cff(cff::Outlines<'a>, GlyphId, u32),
342 }
343 
344 /// Collection of scalable glyph outlines.
345 #[derive(Clone)]
346 pub struct OutlineGlyphCollection<'a> {
347     kind: OutlineCollectionKind<'a>,
348 }
349 
350 impl<'a> OutlineGlyphCollection<'a> {
351     /// Creates a new outline collection for the given font.
new(font: &impl TableProvider<'a>) -> Self352     pub fn new(font: &impl TableProvider<'a>) -> Self {
353         let kind = if let Some(glyf) = glyf::Outlines::new(font) {
354             OutlineCollectionKind::Glyf(glyf)
355         } else if let Ok(cff) = cff::Outlines::new(font) {
356             OutlineCollectionKind::Cff(cff)
357         } else {
358             OutlineCollectionKind::None
359         };
360         Self { kind }
361     }
362 
363     /// Creates a new outline collection for the given font and outline
364     /// format.
365     ///
366     /// Returns `None` if the font does not contain outlines in the requested
367     /// format.
with_format(font: &impl TableProvider<'a>, format: OutlineGlyphFormat) -> Option<Self>368     pub fn with_format(font: &impl TableProvider<'a>, format: OutlineGlyphFormat) -> Option<Self> {
369         let kind = match format {
370             OutlineGlyphFormat::Glyf => OutlineCollectionKind::Glyf(glyf::Outlines::new(font)?),
371             OutlineGlyphFormat::Cff => {
372                 let upem = font.head().ok()?.units_per_em();
373                 OutlineCollectionKind::Cff(cff::Outlines::from_cff(font.cff().ok()?, 0, upem).ok()?)
374             }
375             OutlineGlyphFormat::Cff2 => {
376                 let upem = font.head().ok()?.units_per_em();
377                 OutlineCollectionKind::Cff(cff::Outlines::from_cff2(font.cff2().ok()?, upem).ok()?)
378             }
379         };
380         Some(Self { kind })
381     }
382 
383     /// Returns the underlying format of the source outline tables.
format(&self) -> Option<OutlineGlyphFormat>384     pub fn format(&self) -> Option<OutlineGlyphFormat> {
385         match &self.kind {
386             OutlineCollectionKind::Glyf(..) => Some(OutlineGlyphFormat::Glyf),
387             OutlineCollectionKind::Cff(cff) => cff
388                 .is_cff2()
389                 .then_some(OutlineGlyphFormat::Cff2)
390                 .or(Some(OutlineGlyphFormat::Cff)),
391             _ => None,
392         }
393     }
394 
395     /// Returns the outline for the given glyph identifier.
get(&self, glyph_id: GlyphId) -> Option<OutlineGlyph<'a>>396     pub fn get(&self, glyph_id: GlyphId) -> Option<OutlineGlyph<'a>> {
397         match &self.kind {
398             OutlineCollectionKind::None => None,
399             OutlineCollectionKind::Glyf(glyf) => Some(OutlineGlyph {
400                 kind: OutlineKind::Glyf(glyf.clone(), glyf.outline(glyph_id).ok()?),
401             }),
402             OutlineCollectionKind::Cff(cff) => Some(OutlineGlyph {
403                 kind: OutlineKind::Cff(cff.clone(), glyph_id, cff.subfont_index(glyph_id)),
404             }),
405         }
406     }
407 
408     /// Returns an iterator over all of the outline glyphs in the collection.
iter(&self) -> impl Iterator<Item = (GlyphId, OutlineGlyph<'a>)> + 'a + Clone409     pub fn iter(&self) -> impl Iterator<Item = (GlyphId, OutlineGlyph<'a>)> + 'a + Clone {
410         let len = match &self.kind {
411             OutlineCollectionKind::Glyf(glyf) => glyf.glyph_count(),
412             OutlineCollectionKind::Cff(cff) => cff.glyph_count(),
413             _ => 0,
414         } as u16;
415         let copy = self.clone();
416         (0..len).filter_map(move |gid| {
417             let gid = GlyphId::new(gid);
418             let glyph = copy.get(gid)?;
419             Some((gid, glyph))
420         })
421     }
422 }
423 
424 #[derive(Clone)]
425 enum OutlineCollectionKind<'a> {
426     None,
427     Glyf(glyf::Outlines<'a>),
428     Cff(cff::Outlines<'a>),
429 }
430 
431 /// Arbitrarily chosen smallish size for stack allocation to avoid the heap
432 /// when possible while drawing glyf outlines.
433 ///
434 /// Upcoming work on TrueType hinting will likely adjust this to use bucketed
435 /// sizes based on actual data captured from fonts.
436 const GLYF_DRAW_STACK_BUFFER_SIZE: usize = 4096;
437 
438 /// Invokes the callback with a memory buffer suitable for drawing
439 /// the given TrueType outline.
with_glyf_memory<R>( outline: &glyf::Outline, hinting: Hinting, memory: Option<&mut [u8]>, mut f: impl FnMut(&mut [u8]) -> R, ) -> R440 pub(super) fn with_glyf_memory<R>(
441     outline: &glyf::Outline,
442     hinting: Hinting,
443     memory: Option<&mut [u8]>,
444     mut f: impl FnMut(&mut [u8]) -> R,
445 ) -> R {
446     // Wrap in a function and prevent inlining to avoid stack allocation
447     // and zeroing if we don't take this code path.
448     #[inline(never)]
449     fn stack_mem<R>(mut f: impl FnMut(&mut [u8]) -> R) -> R {
450         f(&mut [0u8; GLYF_DRAW_STACK_BUFFER_SIZE])
451     }
452     match memory {
453         Some(buf) => f(buf),
454         None => {
455             let buf_size = outline.required_buffer_size(hinting);
456             if buf_size <= GLYF_DRAW_STACK_BUFFER_SIZE {
457                 stack_mem(f)
458             } else {
459                 f(&mut vec![0u8; buf_size])
460             }
461         }
462     }
463 }
464 
465 #[cfg(test)]
466 mod tests {
467     use super::*;
468     use crate::MetadataProvider;
469     use read_fonts::{scaler_test, types::GlyphId, FontRef, TableProvider};
470 
471     #[test]
outline_glyph_formats()472     fn outline_glyph_formats() {
473         let font_format_pairs = [
474             (font_test_data::VAZIRMATN_VAR, OutlineGlyphFormat::Glyf),
475             (
476                 font_test_data::CANTARELL_VF_TRIMMED,
477                 OutlineGlyphFormat::Cff2,
478             ),
479             (
480                 font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
481                 OutlineGlyphFormat::Cff,
482             ),
483             (font_test_data::COLRV0V1_VARIABLE, OutlineGlyphFormat::Glyf),
484         ];
485         for (font_data, format) in font_format_pairs {
486             assert_eq!(
487                 FontRef::new(font_data).unwrap().outline_glyphs().format(),
488                 Some(format)
489             );
490         }
491     }
492 
493     #[test]
vazirmatin_var()494     fn vazirmatin_var() {
495         compare_glyphs(
496             font_test_data::VAZIRMATN_VAR,
497             font_test_data::VAZIRMATN_VAR_GLYPHS,
498         );
499     }
500 
501     #[test]
cantarell_vf()502     fn cantarell_vf() {
503         compare_glyphs(
504             font_test_data::CANTARELL_VF_TRIMMED,
505             font_test_data::CANTARELL_VF_TRIMMED_GLYPHS,
506         );
507     }
508 
509     #[test]
noto_serif_display()510     fn noto_serif_display() {
511         compare_glyphs(
512             font_test_data::NOTO_SERIF_DISPLAY_TRIMMED,
513             font_test_data::NOTO_SERIF_DISPLAY_TRIMMED_GLYPHS,
514         );
515     }
516 
517     #[test]
overlap_flags()518     fn overlap_flags() {
519         let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
520         let outlines = font.outline_glyphs();
521         let glyph_count = font.maxp().unwrap().num_glyphs();
522         // GID 2 is a composite glyph with the overlap bit on a component
523         // GID 3 is a simple glyph with the overlap bit on the first flag
524         let expected_gids_with_overlap = vec![2, 3];
525         assert_eq!(
526             expected_gids_with_overlap,
527             (0..glyph_count)
528                 .filter(
529                     |gid| outlines.get(GlyphId::new(*gid)).unwrap().has_overlaps() == Some(true)
530                 )
531                 .collect::<Vec<_>>()
532         );
533     }
534 
compare_glyphs(font_data: &[u8], expected_outlines: &str)535     fn compare_glyphs(font_data: &[u8], expected_outlines: &str) {
536         let font = FontRef::new(font_data).unwrap();
537         let expected_outlines = scaler_test::parse_glyph_outlines(expected_outlines);
538         let mut path = scaler_test::Path::default();
539         for expected_outline in &expected_outlines {
540             if expected_outline.size == 0.0 && !expected_outline.coords.is_empty() {
541                 continue;
542             }
543             let size = if expected_outline.size != 0.0 {
544                 Size::new(expected_outline.size)
545             } else {
546                 Size::unscaled()
547             };
548             path.elements.clear();
549             font.outline_glyphs()
550                 .get(expected_outline.glyph_id)
551                 .unwrap()
552                 .draw(
553                     DrawSettings::unhinted(size, expected_outline.coords.as_slice()),
554                     &mut path,
555                 )
556                 .unwrap();
557             if path.elements != expected_outline.path {
558                 panic!(
559                     "mismatch in glyph path for id {} (size: {}, coords: {:?}): path: {:?} expected_path: {:?}",
560                     expected_outline.glyph_id,
561                     expected_outline.size,
562                     expected_outline.coords,
563                     &path.elements,
564                     &expected_outline.path
565                 );
566             }
567         }
568     }
569 }
570