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