swash/scale/mod.rs
1/*!
2Scaling, hinting and rasterization of visual glyph representations.
3
4Scaling is the process of generating an appropriately sized visual
5representation of a glyph. The scaler can produce rendered glyph
6[images](Image) from outlines, layered color outlines and embedded
7bitmaps. Alternatively, you can request raw, optionally hinted
8[outlines](Outline) that can then be further processed by [zeno] or
9fed into other crates like [lyon](https://github.com/nical/lyon) or
10[pathfinder](https://github.com/servo/pathfinder) for tessellation and
11GPU rendering.
12
13# Building the scaler
14
15All scaling in this crate takes place within the purview of a
16[`ScaleContext`]. This opaque struct manages internal LRU caches and scratch
17buffers that are necessary for the scaling process. Generally, you'll
18want to keep an instance with your glyph cache, or if doing multithreaded
19glyph rasterization, one instance per thread.
20
21The only method available on the context is [`builder`](ScaleContext::builder)
22which takes a type that can be converted into a [`FontRef`] as an argument
23and produces a [`ScalerBuilder`] that provides options for configuring and
24building a [`Scaler`].
25
26Here, we'll create a context and build a scaler for a size of 14px with
27hinting enabled:
28```
29# use swash::{FontRef, CacheKey, scale::*};
30# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
31// let font = ...;
32let mut context = ScaleContext::new();
33let mut scaler = context.builder(font)
34 .size(14.)
35 .hint(true)
36 .build();
37```
38
39You can specify variation settings by calling the [`variations`](ScalerBuilder::variations)
40method with an iterator that yields a sequence of values that are convertible
41to [`Setting<f32>`]. Tuples of (&str, f32) will work in a pinch. For example,
42you can request a variation of the weight axis like this:
43```
44# use swash::{FontRef, CacheKey, scale::*};
45# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
46// let font = ...;
47let mut context = ScaleContext::new();
48let mut scaler = context.builder(font)
49 .size(14.)
50 .hint(true)
51 .variations(&[("wght", 520.5)])
52 .build();
53```
54
55Alternatively, you can specify variations using the
56[`normalized_coords`](ScalerBuilder::normalized_coords) method which takes an iterator
57that yields [`NormalizedCoord`]s (a type alias for `i16` which is a fixed point value
58in 2.14 format). This method is faster than specifying variations by tag and value, but
59the difference is likely negligible outside of microbenchmarks. The real advantage
60is that a sequence of `i16` is more compact and easier to fold into a key in a glyph
61cache. You can compute these normalized coordinates by using the
62[`Variation::normalize`](crate::Variation::normalize) method for each available axis in
63the font. The best strategy, however, is to simply capture these during shaping with
64the [`Shaper::normalized_coords`](crate::shape::Shaper::normalized_coords) method which
65will have already computed them for you.
66
67See [`ScalerBuilder`] for available options and default values.
68
69# Outlines and bitmaps
70
71The [`Scaler`] struct essentially provides direct access to the outlines and embedded
72bitmaps that are available in the font. In the case of outlines, it can produce the
73raw outline in font units or an optionally hinted, scaled outline. For example, to
74extract the raw outline for the letter 'Q':
75```
76# use swash::{FontRef, CacheKey, scale::*};
77# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
78// let font = ...;
79let mut context = ScaleContext::new();
80let mut scaler = context.builder(font).build();
81let glyph_id = font.charmap().map('Q');
82let outline = scaler.scale_outline(glyph_id);
83```
84
85For the same, but hinted at 12px:
86```
87# use swash::{FontRef, CacheKey, scale::*};
88# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
89// let font = ...;
90let mut context = ScaleContext::new();
91let mut scaler = context.builder(font)
92 .hint(true)
93 .size(12.)
94 .build();
95let glyph_id = font.charmap().map('Q');
96let outline = scaler.scale_outline(glyph_id);
97```
98The [`scale_outline`](Scaler::scale_outline) method returns an [`Outline`] wrapped
99in an option. It will return `None` if an outline was not available or if there was
100an error during the scaling process. Note that
101[`scale_color_outline`](Scaler::scale_color_outline) can be used to access layered
102color outlines such as those included in the Microsoft _Segoe UI Emoji_ font. Finally,
103the `_into` variants of these methods ([`scale_outline_into`](Scaler::scale_outline_into)
104and [`scale_color_outline_into`](Scaler::scale_color_outline_into)) will return
105their results in a previously allocated outline avoiding the extra allocations.
106
107Similar to outlines, bitmaps can be retrieved with the [`scale_bitmap`](Scaler::scale_bitmap)
108and [`scale_color_bitmap`](Scaler::scale_color_bitmap) for alpha and color bitmaps,
109respectively. These methods return an [`Image`] wrapped in an option. The associated
110`_into` variants are also available.
111
112Unlike outlines, bitmaps are available in [`strike`](crate::BitmapStrike)s of various sizes.
113When requesting a bitmap, you specify the strategy for strike selection using the
114[`StrikeWith`] enum.
115
116For example, if we want the largest available unscaled image for the fire emoji:
117```
118# use swash::{FontRef, CacheKey, scale::*};
119# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
120// let font = ...;
121let mut context = ScaleContext::new();
122let mut scaler = context.builder(font).build();
123let glyph_id = font.charmap().map('🔥');
124let image = scaler.scale_color_bitmap(glyph_id, StrikeWith::LargestSize);
125```
126
127Or, to produce a scaled image for a size of 18px:
128```
129# use swash::{FontRef, CacheKey, scale::*};
130# let font: FontRef = FontRef { data: &[], offset: 0, key: CacheKey::new() };
131// let font = ...;
132let mut context = ScaleContext::new();
133let mut scaler = context.builder(font)
134 .size(18.)
135 .build();
136let glyph_id = font.charmap().map('🔥');
137let image = scaler.scale_color_bitmap(glyph_id, StrikeWith::BestFit);
138```
139This will select the best strike for the requested size and return
140a bitmap that is scaled appropriately for an 18px run of text.
141
142Alpha bitmaps should generally be avoided unless you're rendering small East
143Asian text where these are sometimes still preferred over scalable outlines. In
144this case, you should only use [`StrikeWith::ExactSize`] to select the strike,
145falling back to an outline if a bitmap is unavailable.
146
147# Rendering
148
149In the general case of text rendering, you'll likely not care about the specific
150details of outlines or bitmaps and will simply want an appropriately sized
151image that represents your glyph. For this purpose, you'll want to use the
152[`Render`] struct which is a builder that provides options for rendering an image.
153This struct is constructed with a slice of [`Source`]s in priority order and
154will iterate through them until it finds one that satisfies the request. Typically,
155you'll want to use the following order:
156```
157# use swash::scale::*;
158Render::new(&[
159 // Color outline with the first palette
160 Source::ColorOutline(0),
161 // Color bitmap with best fit selection mode
162 Source::ColorBitmap(StrikeWith::BestFit),
163 // Standard scalable outline
164 Source::Outline,
165]);
166```
167
168The [`Render`] struct offers several options that control rasterization of
169outlines such as [`format`](Render::format) for selecting a subpixel rendering mode,
170[`offset`](Render::offset) for applying fractional positioning, and others. See the
171struct documentation for detail.
172
173After selecting your options, call the [`render`](Render::render) method, passing your
174configured [`Scaler`] and the requested glyph identifier to produce an [`Image`].
175Let's put it all together by writing a simple function that will render subpixel glyphs
176with fractional positioning:
177```
178# use swash::{scale::{*, image::Image}, FontRef, GlyphId};
179fn render_glyph(
180 context: &mut ScaleContext,
181 font: &FontRef,
182 size: f32,
183 hint: bool,
184 glyph_id: GlyphId,
185 x: f32,
186 y: f32,
187) -> Option<Image> {
188 use zeno::{Format, Vector};
189 // Build the scaler
190 let mut scaler = context.builder(*font).size(size).hint(hint).build();
191 // Compute the fractional offset-- you'll likely want to quantize this
192 // in a real renderer
193 let offset = Vector::new(x.fract(), y.fract());
194 // Select our source order
195 Render::new(&[
196 Source::ColorOutline(0),
197 Source::ColorBitmap(StrikeWith::BestFit),
198 Source::Outline,
199 ])
200 // Select a subpixel format
201 .format(Format::Subpixel)
202 // Apply the fractional offset
203 .offset(offset)
204 // Render the image
205 .render(&mut scaler, glyph_id)
206}
207```
208Note that rendering also takes care of correctly scaling, rasterizing and
209compositing layered color outlines for us.
210
211There are other options available for emboldening, transforming with an
212affine matrix, and applying path effects. See the methods on [`Render`] for
213more detail.
214*/
215
216pub mod image;
217pub mod outline;
218
219mod bitmap;
220mod color;
221mod hinting_cache;
222mod proxy;
223
224use hinting_cache::HintingCache;
225use image::*;
226use outline::*;
227use skrifa::{
228 instance::{NormalizedCoord as SkrifaNormalizedCoord, Size as SkrifaSize},
229 outline::OutlineGlyphCollection,
230 GlyphId as SkrifaGlyphId, MetadataProvider,
231};
232
233use super::internal;
234use super::{cache::FontCache, setting::Setting, FontRef, GlyphId, NormalizedCoord};
235use alloc::vec::Vec;
236use core::borrow::Borrow;
237#[cfg(all(feature = "libm", feature = "render"))]
238use core_maths::CoreFloat;
239use proxy::*;
240use zeno::Placement;
241#[cfg(feature = "render")]
242use zeno::{Format, Mask, Origin, Point, Scratch, Style, Transform, Vector};
243
244pub(crate) use bitmap::decode_png;
245
246/// Index of a color palette.
247pub type PaletteIndex = u16;
248
249/// Index of a bitmap strike.
250pub type StrikeIndex = u32;
251
252/// Bitmap strike selection mode.
253#[derive(Copy, Clone, Debug)]
254pub enum StrikeWith {
255 /// Load a bitmap only if the exact size is available.
256 ExactSize,
257 /// Load a bitmap of the best available size.
258 BestFit,
259 /// Loads a bitmap of the largest size available.
260 LargestSize,
261 /// Load a bitmap from the specified strike.
262 Index(StrikeIndex),
263}
264
265/// Glyph sources for the renderer.
266#[derive(Copy, Clone, Debug)]
267pub enum Source {
268 /// Scalable outlines.
269 Outline,
270 /// Layered color scalable outlines.
271 ColorOutline(PaletteIndex),
272 /// Embedded alpha bitmaps.
273 Bitmap(StrikeWith),
274 /// Embedded color bitmaps.
275 ColorBitmap(StrikeWith),
276}
277
278impl Default for Source {
279 fn default() -> Self {
280 Self::Outline
281 }
282}
283
284/// Context that manages caches and scratch buffers for scaling.
285///
286/// See the module level [documentation](index.html#building-the-scaler) for detail.
287pub struct ScaleContext {
288 fonts: FontCache<ScalerProxy>,
289 state: State,
290 hinting_cache: HintingCache,
291 coords: Vec<SkrifaNormalizedCoord>,
292}
293
294struct State {
295 scratch0: Vec<u8>,
296 scratch1: Vec<u8>,
297 outline: Outline,
298 #[cfg(feature = "render")]
299 rcx: Scratch,
300}
301
302impl ScaleContext {
303 /// Creates a new scaling context.
304 pub fn new() -> Self {
305 Self::with_max_entries(8)
306 }
307
308 /// Creates a new scaling context with the specified maximum number of
309 /// cache entries.
310 pub fn with_max_entries(max_entries: usize) -> Self {
311 let max_entries = max_entries.clamp(1, 64);
312 Self {
313 fonts: FontCache::new(max_entries),
314 state: State {
315 scratch0: Vec::new(),
316 scratch1: Vec::new(),
317 outline: Outline::new(),
318 #[cfg(feature = "render")]
319 rcx: Scratch::new(),
320 },
321 hinting_cache: HintingCache::default(),
322 coords: Vec::new(),
323 }
324 }
325
326 /// Creates a new builder for constructing a scaler with this context
327 /// and the specified font.
328 pub fn builder<'a>(&'a mut self, font: impl Into<FontRef<'a>>) -> ScalerBuilder<'a> {
329 ScalerBuilder::new(self, font, None)
330 }
331
332 /// Creates a new builder for constructing a scaler with this context,
333 /// specified font and a custom unique identifier.
334 pub fn builder_with_id<'a>(
335 &'a mut self,
336 font: impl Into<FontRef<'a>>,
337 id: [u64; 2],
338 ) -> ScalerBuilder<'a> {
339 ScalerBuilder::new(self, font, Some(id))
340 }
341}
342
343impl Default for ScaleContext {
344 fn default() -> Self {
345 Self::new()
346 }
347}
348
349/// Builder for configuring a scaler.
350pub struct ScalerBuilder<'a> {
351 state: &'a mut State,
352 hinting_cache: &'a mut HintingCache,
353 font: FontRef<'a>,
354 outlines: Option<OutlineGlyphCollection<'a>>,
355 proxy: &'a ScalerProxy,
356 id: [u64; 2],
357 coords: &'a mut Vec<SkrifaNormalizedCoord>,
358 size: f32,
359 hint: bool,
360}
361
362impl<'a> ScalerBuilder<'a> {
363 fn new(
364 context: &'a mut ScaleContext,
365 font: impl Into<FontRef<'a>>,
366 id: Option<[u64; 2]>,
367 ) -> Self {
368 let font = font.into();
369 let (id, proxy) = context.fonts.get(&font, id, ScalerProxy::from_font);
370 let skrifa_font = if font.offset == 0 {
371 skrifa::FontRef::new(font.data).ok()
372 } else {
373 // TODO: make this faster
374 let index = crate::FontDataRef::new(font.data)
375 .and_then(|font_data| font_data.fonts().position(|f| f.offset == font.offset));
376 index.and_then(|index| skrifa::FontRef::from_index(font.data, index as u32).ok())
377 };
378 let outlines = skrifa_font.map(|font_ref| font_ref.outline_glyphs());
379 Self {
380 state: &mut context.state,
381 hinting_cache: &mut context.hinting_cache,
382 font,
383 outlines,
384 proxy,
385 id,
386 coords: &mut context.coords,
387 size: 0.,
388 hint: false,
389 }
390 }
391
392 /// Specifies the font size in pixels per em. The default value is `0` which will produce
393 /// unscaled glyphs in original font units.
394 pub fn size(mut self, ppem: f32) -> Self {
395 self.size = ppem.max(0.);
396 self
397 }
398
399 /// Specifies whether to apply hinting to outlines. The default value is `false`.
400 pub fn hint(mut self, yes: bool) -> Self {
401 self.hint = yes;
402 self
403 }
404
405 /// Adds variation settings to the scaler.
406 pub fn variations<I>(self, settings: I) -> Self
407 where
408 I: IntoIterator,
409 I::Item: Into<Setting<f32>>,
410 {
411 if self.proxy.coord_count != 0 {
412 let vars = self.font.variations();
413 self.coords.resize(vars.len(), Default::default());
414 for setting in settings {
415 let setting = setting.into();
416 for var in vars {
417 if var.tag() == setting.tag {
418 let value = var.normalize(setting.value);
419 if let Some(c) = self.coords.get_mut(var.index()) {
420 *c = SkrifaNormalizedCoord::from_bits(value);
421 }
422 }
423 }
424 }
425 }
426 self
427 }
428
429 /// Specifies the variation settings in terms of normalized coordinates. This will replace
430 /// any previous variation settings.
431 pub fn normalized_coords<I>(self, coords: I) -> Self
432 where
433 I: IntoIterator,
434 I::Item: Borrow<NormalizedCoord>,
435 {
436 self.coords.clear();
437 self.coords.extend(
438 coords
439 .into_iter()
440 .map(|c| SkrifaNormalizedCoord::from_bits(*c.borrow())),
441 );
442 self
443 }
444
445 /// Builds a scaler for the current configuration.
446 pub fn build(self) -> Scaler<'a> {
447 let upem = self.proxy.metrics.units_per_em();
448 let skrifa_size = if self.size != 0.0 && upem != 0 {
449 SkrifaSize::new(self.size)
450 } else {
451 SkrifaSize::unscaled()
452 };
453 let hinting_instance = match (self.hint, &self.outlines) {
454 (true, Some(outlines)) => {
455 let key = hinting_cache::HintingKey {
456 id: self.id,
457 outlines,
458 size: skrifa_size,
459 coords: self.coords,
460 };
461 self.hinting_cache.get(&key)
462 }
463 _ => None,
464 };
465 Scaler {
466 state: self.state,
467 font: self.font,
468 outlines: self.outlines,
469 hinting_instance,
470 proxy: self.proxy,
471 coords: &self.coords[..],
472 size: self.size,
473 skrifa_size,
474 }
475 }
476}
477
478/// Scales outline and bitmap glyphs.
479///
480/// See the module level [documentation](index.html#outlines-and-bitmaps) for detail.
481pub struct Scaler<'a> {
482 state: &'a mut State,
483 font: FontRef<'a>,
484 outlines: Option<OutlineGlyphCollection<'a>>,
485 hinting_instance: Option<&'a skrifa::outline::HintingInstance>,
486 proxy: &'a ScalerProxy,
487 coords: &'a [SkrifaNormalizedCoord],
488 size: f32,
489 skrifa_size: SkrifaSize,
490}
491
492impl<'a> Scaler<'a> {
493 /// Returns true if scalable glyph outlines are available.
494 pub fn has_outlines(&self) -> bool {
495 self.outlines
496 .as_ref()
497 .map(|outlines| outlines.format().is_some())
498 .unwrap_or_default()
499 }
500
501 /// Scales an outline for the specified glyph into the provided outline.
502 pub fn scale_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
503 outline.clear();
504 self.scale_outline_impl(glyph_id, None, Some(outline))
505 }
506
507 /// Scales an outline for the specified glyph.
508 pub fn scale_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
509 let mut outline = Outline::new();
510 if self.scale_outline_into(glyph_id, &mut outline) {
511 Some(outline)
512 } else {
513 None
514 }
515 }
516
517 /// Returns true if scalable color glyph outlines are available.
518 pub fn has_color_outlines(&self) -> bool {
519 self.proxy.color.colr != 0 && self.proxy.color.cpal != 0
520 }
521
522 /// Scales a color outline for the specified glyph into the provided outline.
523 pub fn scale_color_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
524 outline.clear();
525 if !self.has_color_outlines() {
526 return false;
527 }
528 let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
529 Some(layers) => layers,
530 _ => return false,
531 };
532 for i in 0..layers.len() {
533 let layer = match layers.get(i) {
534 Some(layer) => layer,
535 _ => return false,
536 };
537 if !self.scale_outline_impl(layer.glyph_id, layer.color_index, Some(outline)) {
538 return false;
539 }
540 }
541 outline.set_color(true);
542 true
543 }
544
545 /// Scales a color outline for the specified glyph.
546 pub fn scale_color_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
547 let mut outline = Outline::new();
548 if self.scale_color_outline_into(glyph_id, &mut outline) {
549 Some(outline)
550 } else {
551 None
552 }
553 }
554
555 fn scale_outline_impl(
556 &mut self,
557 glyph_id: GlyphId,
558 color_index: Option<u16>,
559 outline: Option<&mut Outline>,
560 ) -> bool {
561 let mut outline = match outline {
562 Some(x) => x,
563 _ => &mut self.state.outline,
564 };
565 if let Some(outlines) = &self.outlines {
566 if let Some(glyph) = outlines.get(SkrifaGlyphId::from(glyph_id)) {
567 outline.begin_layer(color_index);
568 let settings: skrifa::outline::DrawSettings =
569 if let Some(hinting_instance) = &self.hinting_instance {
570 (*hinting_instance).into()
571 } else {
572 (
573 self.skrifa_size,
574 skrifa::instance::LocationRef::new(self.coords),
575 )
576 .into()
577 };
578 if glyph
579 .draw(settings, &mut OutlineWriter(&mut outline))
580 .is_ok()
581 {
582 outline.maybe_close();
583 outline.finish();
584 return true;
585 }
586 }
587 }
588 false
589 }
590
591 // Unused when render feature is disabled.
592 #[allow(dead_code)]
593 fn scale_color_outline_impl(&mut self, glyph_id: GlyphId) -> bool {
594 if !self.has_color_outlines() {
595 return false;
596 }
597 let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
598 Some(layers) => layers,
599 _ => return false,
600 };
601 self.state.outline.clear();
602 for i in 0..layers.len() {
603 let layer = match layers.get(i) {
604 Some(layer) => layer,
605 _ => return false,
606 };
607 if !self.scale_outline_impl(layer.glyph_id, layer.color_index, None) {
608 return false;
609 }
610 }
611 true
612 }
613
614 /// Returns true if alpha bitmaps are available.
615 pub fn has_bitmaps(&self) -> bool {
616 self.proxy.bitmaps.has_alpha()
617 }
618
619 /// Scales a bitmap for the specified glyph and mode into the provided image.
620 pub fn scale_bitmap_into(
621 &mut self,
622 glyph_id: u16,
623 strike: StrikeWith,
624 image: &mut Image,
625 ) -> bool {
626 self.scale_bitmap_impl(glyph_id, false, strike, image) == Some(true)
627 }
628
629 /// Scales a bitmap for the specified glyph and mode.
630 pub fn scale_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
631 let mut image = Image::new();
632 if self.scale_bitmap_into(glyph_id, strike, &mut image) {
633 Some(image)
634 } else {
635 None
636 }
637 }
638
639 /// Returns true if color bitmaps are available.
640 pub fn has_color_bitmaps(&self) -> bool {
641 self.proxy.bitmaps.has_color()
642 }
643
644 /// Scales a color bitmap for the specified glyph and mode into the provided image.
645 pub fn scale_color_bitmap_into(
646 &mut self,
647 glyph_id: u16,
648 strike: StrikeWith,
649 image: &mut Image,
650 ) -> bool {
651 self.scale_bitmap_impl(glyph_id, true, strike, image) == Some(true)
652 }
653
654 /// Scales a color bitmap for the specified glyph and mode.
655 pub fn scale_color_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
656 let mut image = Image::new();
657 if self.scale_color_bitmap_into(glyph_id, strike, &mut image) {
658 Some(image)
659 } else {
660 None
661 }
662 }
663
664 fn scale_bitmap_impl(
665 &mut self,
666 glyph_id: GlyphId,
667 color: bool,
668 strike: StrikeWith,
669 image: &mut Image,
670 ) -> Option<bool> {
671 image.clear();
672 let size = self.size;
673 let mut strikes = if color {
674 self.proxy.bitmaps.materialize_color(&self.font)
675 } else {
676 self.proxy.bitmaps.materialize_alpha(&self.font)
677 };
678 let bitmap = match strike {
679 StrikeWith::ExactSize => {
680 if self.size == 0. {
681 None
682 } else {
683 strikes
684 .find_by_exact_ppem(size as u16, glyph_id)?
685 .get(glyph_id)
686 }
687 }
688 StrikeWith::BestFit => {
689 if self.size == 0. {
690 None
691 } else {
692 strikes
693 .find_by_nearest_ppem(size as u16, glyph_id)?
694 .get(glyph_id)
695 }
696 }
697 StrikeWith::LargestSize => strikes.find_by_largest_ppem(glyph_id)?.get(glyph_id),
698 StrikeWith::Index(i) => strikes
699 .nth(i as usize)
700 .and_then(|strike| strike.get(glyph_id)),
701 }?;
702 if bitmap.ppem == 0 {
703 return None;
704 }
705 let (_, _, bufsize) = bitmap.scaled_size(size);
706 image.data.resize(bufsize, 0);
707 self.state.scratch0.clear();
708 self.state.scratch1.clear();
709 let mut w = bitmap.width;
710 let mut h = bitmap.height;
711 let scale = size / bitmap.ppem as f32;
712 image.placement = if size != 0. && scale != 1. {
713 self.state
714 .scratch0
715 .resize(bitmap.format.buffer_size(w, h), 0);
716 w = (w as f32 * scale) as u32;
717 h = (h as f32 * scale) as u32;
718 image.data.resize(bitmap.format.buffer_size(w, h), 0);
719 if !bitmap.decode(Some(&mut self.state.scratch1), &mut self.state.scratch0) {
720 return None;
721 }
722 if !bitmap::resize(
723 &self.state.scratch0,
724 bitmap.width,
725 bitmap.height,
726 bitmap.format.channels(),
727 &mut image.data,
728 w,
729 h,
730 bitmap::Filter::Mitchell,
731 Some(&mut self.state.scratch1),
732 ) {
733 return None;
734 }
735 let left = (bitmap.left as f32 * scale) as i32;
736 let top = (bitmap.top as f32 * scale) as i32;
737 Placement {
738 left,
739 top,
740 width: w,
741 height: h,
742 }
743 } else {
744 image.data.resize(bitmap.format.buffer_size(w, h), 0);
745 if !bitmap.decode(Some(&mut self.state.scratch1), &mut image.data) {
746 return None;
747 }
748 Placement {
749 left: bitmap.left,
750 top: bitmap.top,
751 width: w,
752 height: h,
753 }
754 };
755 image.source = match color {
756 true => Source::ColorBitmap(strike),
757 false => Source::Bitmap(strike),
758 };
759 image.content = match bitmap.format.channels() {
760 1 => Content::Mask,
761 _ => Content::Color,
762 };
763 // let mut advance = bitmap.advance() as f32;
764 // if options.size != 0. && options.size as u16 != bitmap.ppem() {
765 // advance *= options.size / bitmap.ppem() as f32;
766 // }
767 Some(true)
768 }
769}
770
771/// Builder type for rendering a glyph into an image.
772///
773/// See the module level [documentation](index.html#rendering) for detail.
774#[cfg(feature = "render")]
775pub struct Render<'a> {
776 sources: &'a [Source],
777 format: Format,
778 offset: Point,
779 transform: Option<Transform>,
780 embolden: f32,
781 foreground: [u8; 4],
782 style: Style<'a>,
783}
784
785#[cfg(feature = "render")]
786impl<'a> Render<'a> {
787 /// Creates a new builder for configuring rendering using the specified
788 /// prioritized list of sources.
789 pub fn new(sources: &'a [Source]) -> Self {
790 Self {
791 sources,
792 format: Format::Alpha,
793 offset: Point::new(0., 0.),
794 transform: None,
795 embolden: 0.,
796 foreground: [128, 128, 128, 255],
797 style: Style::default(),
798 }
799 }
800
801 /// Specifies the target format for rasterizing an outline. Default is
802 /// [`Format::Alpha`].
803 pub fn format(&mut self, format: Format) -> &mut Self {
804 self.format = format;
805 self
806 }
807
808 /// Specifies the path style to use when rasterizing an outline. Default is
809 /// [`Fill::NonZero`](zeno::Fill::NonZero).
810 pub fn style(&mut self, style: impl Into<Style<'a>>) -> &mut Self {
811 self.style = style.into();
812 self
813 }
814
815 /// Specifies an additional offset to apply when rasterizing an outline.
816 /// Default is `(0, 0)`.
817 pub fn offset(&mut self, offset: Vector) -> &mut Self {
818 self.offset = offset;
819 self
820 }
821
822 /// Specifies a transformation matrix to apply when rasterizing an
823 /// outline. Default is `None`.
824 pub fn transform(&mut self, transform: Option<Transform>) -> &mut Self {
825 self.transform = transform;
826 self
827 }
828
829 /// Specifies the strength of a faux bold transform to apply when
830 /// rasterizing an outline. Default is `0`.
831 pub fn embolden(&mut self, strength: f32) -> &mut Self {
832 self.embolden = strength;
833 self
834 }
835
836 /// Specifies an RGBA color to use when rasterizing layers of a color
837 /// outline that do not directly reference a palette color. Default is
838 /// `[128, 128, 128, 255]`.
839 pub fn default_color(&mut self, color: [u8; 4]) -> &mut Self {
840 self.foreground = color;
841 self
842 }
843
844 /// Renders the specified glyph using the current configuration into the
845 /// provided image.
846 pub fn render_into(&self, scaler: &mut Scaler, glyph_id: GlyphId, image: &mut Image) -> bool {
847 for source in self.sources {
848 match source {
849 Source::Outline => {
850 if !scaler.has_outlines() {
851 continue;
852 }
853 scaler.state.outline.clear();
854 if scaler.scale_outline_impl(glyph_id, None, None) {
855 let state = &mut scaler.state;
856 let rcx = &mut state.rcx;
857 let outline = &mut state.outline;
858 if self.embolden != 0. {
859 outline.embolden(self.embolden, self.embolden);
860 }
861 if let Some(transform) = &self.transform {
862 outline.transform(transform);
863 }
864 let placement = Mask::with_scratch(outline.path(), rcx)
865 .format(self.format)
866 .origin(Origin::BottomLeft)
867 .style(self.style)
868 .offset(self.offset)
869 .render_offset(self.offset)
870 .inspect(|fmt, w, h| {
871 image.data.resize(fmt.buffer_size(w, h), 0);
872 })
873 .render_into(&mut image.data[..], None);
874 image.placement = placement;
875 image.content = if self.format == Format::Alpha {
876 Content::Mask
877 } else {
878 Content::SubpixelMask
879 };
880 image.source = Source::Outline;
881 return true;
882 }
883 }
884 Source::ColorOutline(palette_index) => {
885 if !scaler.has_color_outlines() {
886 continue;
887 }
888 scaler.state.outline.clear();
889 if scaler.scale_color_outline_impl(glyph_id) {
890 let font = &scaler.font;
891 let proxy = &scaler.proxy;
892 let state = &mut scaler.state;
893 let scratch = &mut state.scratch0;
894 let rcx = &mut state.rcx;
895 let outline = &mut state.outline;
896 // Cool effect, but probably not generally desirable.
897 // Maybe expose a separate option?
898 // if self.embolden != 0. {
899 // outline.embolden(self.embolden, self.embolden);
900 // }
901 if let Some(transform) = &self.transform {
902 outline.transform(transform);
903 }
904 let palette = proxy.color.palette(font, *palette_index);
905
906 let total_bounds = outline.bounds();
907
908 // need to take offset into account when placing glyph
909 let base_x = (total_bounds.min.x + self.offset.x).floor() as i32;
910 let base_y = (total_bounds.min.y + self.offset.y).ceil() as i32;
911 let base_w = total_bounds.width().ceil() as u32;
912 let base_h = total_bounds.height().ceil() as u32;
913
914 image.data.resize((base_w * base_h * 4) as usize, 0);
915 image.placement.left = base_x;
916 image.placement.top = base_h as i32 + base_y;
917 image.placement.width = total_bounds.width().ceil() as u32;
918 image.placement.height = total_bounds.height().ceil() as u32;
919
920 let mut ok = true;
921 for i in 0..outline.len() {
922 let layer = match outline.get(i) {
923 Some(layer) => layer,
924 _ => {
925 ok = false;
926 break;
927 }
928 };
929
930 scratch.clear();
931 let placement = Mask::with_scratch(layer.path(), rcx)
932 .origin(Origin::BottomLeft)
933 .style(self.style)
934 .offset(self.offset)
935 .render_offset(self.offset)
936 .inspect(|fmt, w, h| {
937 scratch.resize(fmt.buffer_size(w, h), 0);
938 })
939 .render_into(&mut scratch[..], None);
940 let color = layer
941 .color_index()
942 .and_then(|i| palette.map(|p| p.get(i)))
943 .unwrap_or(self.foreground);
944 bitmap::blit(
945 &scratch[..],
946 placement.width,
947 placement.height,
948 placement.left.wrapping_sub(base_x),
949 (base_h as i32 + base_y).wrapping_sub(placement.top),
950 color,
951 &mut image.data,
952 base_w,
953 base_h,
954 );
955 }
956 if ok {
957 image.source = Source::ColorOutline(*palette_index);
958 image.content = Content::Color;
959 return true;
960 }
961 }
962 }
963 Source::Bitmap(mode) => {
964 if !scaler.has_bitmaps() {
965 continue;
966 }
967 if scaler.scale_bitmap_into(glyph_id, *mode, image) {
968 return true;
969 }
970 }
971 Source::ColorBitmap(mode) => {
972 if !scaler.has_color_bitmaps() {
973 continue;
974 }
975 if scaler.scale_color_bitmap_into(glyph_id, *mode, image) {
976 return true;
977 }
978 }
979 }
980 }
981 false
982 }
983
984 /// Renders the specified glyph using the current configuration.
985 pub fn render(&self, scaler: &mut Scaler, glyph_id: GlyphId) -> Option<Image> {
986 let mut image = Image::new();
987 if self.render_into(scaler, glyph_id, &mut image) {
988 Some(image)
989 } else {
990 None
991 }
992 }
993}