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)
330 }
331}
332
333impl Default for ScaleContext {
334 fn default() -> Self {
335 Self::new()
336 }
337}
338
339/// Builder for configuring a scaler.
340pub struct ScalerBuilder<'a> {
341 state: &'a mut State,
342 hinting_cache: &'a mut HintingCache,
343 font: FontRef<'a>,
344 outlines: Option<OutlineGlyphCollection<'a>>,
345 proxy: &'a ScalerProxy,
346 id: [u64; 2],
347 coords: &'a mut Vec<SkrifaNormalizedCoord>,
348 size: f32,
349 hint: bool,
350}
351
352impl<'a> ScalerBuilder<'a> {
353 fn new(context: &'a mut ScaleContext, font: impl Into<FontRef<'a>>) -> Self {
354 let font = font.into();
355 let (id, proxy) = context.fonts.get(&font, None, ScalerProxy::from_font);
356 let skrifa_font = if font.offset == 0 {
357 skrifa::FontRef::new(font.data).ok()
358 } else {
359 // TODO: make this faster
360 let index = crate::FontDataRef::new(font.data)
361 .and_then(|font_data| font_data.fonts().position(|f| f.offset == font.offset));
362 index.and_then(|index| skrifa::FontRef::from_index(font.data, index as u32).ok())
363 };
364 let outlines = skrifa_font.map(|font_ref| font_ref.outline_glyphs());
365 Self {
366 state: &mut context.state,
367 hinting_cache: &mut context.hinting_cache,
368 font,
369 outlines,
370 proxy,
371 id,
372 coords: &mut context.coords,
373 size: 0.,
374 hint: false,
375 }
376 }
377
378 /// Specifies the font size in pixels per em. The default value is `0` which will produce
379 /// unscaled glyphs in original font units.
380 pub fn size(mut self, ppem: f32) -> Self {
381 self.size = ppem.max(0.);
382 self
383 }
384
385 /// Specifies whether to apply hinting to outlines. The default value is `false`.
386 pub fn hint(mut self, yes: bool) -> Self {
387 self.hint = yes;
388 self
389 }
390
391 /// Adds variation settings to the scaler.
392 pub fn variations<I>(self, settings: I) -> Self
393 where
394 I: IntoIterator,
395 I::Item: Into<Setting<f32>>,
396 {
397 if self.proxy.coord_count != 0 {
398 let vars = self.font.variations();
399 self.coords.resize(vars.len(), Default::default());
400 for setting in settings {
401 let setting = setting.into();
402 for var in vars {
403 if var.tag() == setting.tag {
404 let value = var.normalize(setting.value);
405 if let Some(c) = self.coords.get_mut(var.index()) {
406 *c = SkrifaNormalizedCoord::from_bits(value);
407 }
408 }
409 }
410 }
411 }
412 self
413 }
414
415 /// Specifies the variation settings in terms of normalized coordinates. This will replace
416 /// any previous variation settings.
417 pub fn normalized_coords<I>(self, coords: I) -> Self
418 where
419 I: IntoIterator,
420 I::Item: Borrow<NormalizedCoord>,
421 {
422 self.coords.clear();
423 self.coords.extend(
424 coords
425 .into_iter()
426 .map(|c| SkrifaNormalizedCoord::from_bits(*c.borrow())),
427 );
428 self
429 }
430
431 /// Builds a scaler for the current configuration.
432 pub fn build(self) -> Scaler<'a> {
433 let upem = self.proxy.metrics.units_per_em();
434 let skrifa_size = if self.size != 0.0 && upem != 0 {
435 SkrifaSize::new(self.size)
436 } else {
437 SkrifaSize::unscaled()
438 };
439 let hinting_instance = match (self.hint, &self.outlines) {
440 (true, Some(outlines)) => {
441 let key = hinting_cache::HintingKey {
442 id: self.id,
443 outlines,
444 size: skrifa_size,
445 coords: self.coords,
446 };
447 self.hinting_cache.get(&key)
448 }
449 _ => None,
450 };
451 Scaler {
452 state: self.state,
453 font: self.font,
454 outlines: self.outlines,
455 hinting_instance,
456 proxy: self.proxy,
457 coords: &self.coords[..],
458 size: self.size,
459 skrifa_size,
460 }
461 }
462}
463
464/// Scales outline and bitmap glyphs.
465///
466/// See the module level [documentation](index.html#outlines-and-bitmaps) for detail.
467pub struct Scaler<'a> {
468 state: &'a mut State,
469 font: FontRef<'a>,
470 outlines: Option<OutlineGlyphCollection<'a>>,
471 hinting_instance: Option<&'a skrifa::outline::HintingInstance>,
472 proxy: &'a ScalerProxy,
473 coords: &'a [SkrifaNormalizedCoord],
474 size: f32,
475 skrifa_size: SkrifaSize,
476}
477
478impl<'a> Scaler<'a> {
479 /// Returns true if scalable glyph outlines are available.
480 pub fn has_outlines(&self) -> bool {
481 self.outlines
482 .as_ref()
483 .map(|outlines| outlines.format().is_some())
484 .unwrap_or_default()
485 }
486
487 /// Scales an outline for the specified glyph into the provided outline.
488 pub fn scale_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
489 outline.clear();
490 self.scale_outline_impl(glyph_id, None, Some(outline))
491 }
492
493 /// Scales an outline for the specified glyph.
494 pub fn scale_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
495 let mut outline = Outline::new();
496 if self.scale_outline_into(glyph_id, &mut outline) {
497 Some(outline)
498 } else {
499 None
500 }
501 }
502
503 /// Returns true if scalable color glyph outlines are available.
504 pub fn has_color_outlines(&self) -> bool {
505 self.proxy.color.colr != 0 && self.proxy.color.cpal != 0
506 }
507
508 /// Scales a color outline for the specified glyph into the provided outline.
509 pub fn scale_color_outline_into(&mut self, glyph_id: GlyphId, outline: &mut Outline) -> bool {
510 outline.clear();
511 if !self.has_color_outlines() {
512 return false;
513 }
514 let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
515 Some(layers) => layers,
516 _ => return false,
517 };
518 for i in 0..layers.len() {
519 let layer = match layers.get(i) {
520 Some(layer) => layer,
521 _ => return false,
522 };
523 if !self.scale_outline_impl(layer.glyph_id, layer.color_index, Some(outline)) {
524 return false;
525 }
526 }
527 outline.set_color(true);
528 true
529 }
530
531 /// Scales a color outline for the specified glyph.
532 pub fn scale_color_outline(&mut self, glyph_id: GlyphId) -> Option<Outline> {
533 let mut outline = Outline::new();
534 if self.scale_color_outline_into(glyph_id, &mut outline) {
535 Some(outline)
536 } else {
537 None
538 }
539 }
540
541 fn scale_outline_impl(
542 &mut self,
543 glyph_id: GlyphId,
544 color_index: Option<u16>,
545 outline: Option<&mut Outline>,
546 ) -> bool {
547 let outline = match outline {
548 Some(x) => x,
549 _ => &mut self.state.outline,
550 };
551 if let Some(outlines) = &self.outlines {
552 if let Some(glyph) = outlines.get(SkrifaGlyphId::from(glyph_id)) {
553 outline.begin_layer(color_index);
554 let settings: skrifa::outline::DrawSettings =
555 if let Some(hinting_instance) = &self.hinting_instance {
556 (*hinting_instance).into()
557 } else {
558 (
559 self.skrifa_size,
560 skrifa::instance::LocationRef::new(self.coords),
561 )
562 .into()
563 };
564 if glyph.draw(settings, outline).is_ok() {
565 outline.maybe_close();
566 outline.finish();
567 return true;
568 }
569 }
570 }
571 false
572 }
573
574 // Unused when render feature is disabled.
575 #[allow(dead_code)]
576 fn scale_color_outline_impl(&mut self, glyph_id: GlyphId) -> bool {
577 if !self.has_color_outlines() {
578 return false;
579 }
580 let layers = match self.proxy.color.layers(self.font.data, glyph_id) {
581 Some(layers) => layers,
582 _ => return false,
583 };
584 self.state.outline.clear();
585 for i in 0..layers.len() {
586 let layer = match layers.get(i) {
587 Some(layer) => layer,
588 _ => return false,
589 };
590 if !self.scale_outline_impl(layer.glyph_id, layer.color_index, None) {
591 return false;
592 }
593 }
594 true
595 }
596
597 /// Returns true if alpha bitmaps are available.
598 pub fn has_bitmaps(&self) -> bool {
599 self.proxy.bitmaps.has_alpha()
600 }
601
602 /// Scales a bitmap for the specified glyph and mode into the provided image.
603 pub fn scale_bitmap_into(
604 &mut self,
605 glyph_id: u16,
606 strike: StrikeWith,
607 image: &mut Image,
608 ) -> bool {
609 self.scale_bitmap_impl(glyph_id, false, strike, image) == Some(true)
610 }
611
612 /// Scales a bitmap for the specified glyph and mode.
613 pub fn scale_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
614 let mut image = Image::new();
615 if self.scale_bitmap_into(glyph_id, strike, &mut image) {
616 Some(image)
617 } else {
618 None
619 }
620 }
621
622 /// Returns true if color bitmaps are available.
623 pub fn has_color_bitmaps(&self) -> bool {
624 self.proxy.bitmaps.has_color()
625 }
626
627 /// Scales a color bitmap for the specified glyph and mode into the provided image.
628 pub fn scale_color_bitmap_into(
629 &mut self,
630 glyph_id: u16,
631 strike: StrikeWith,
632 image: &mut Image,
633 ) -> bool {
634 self.scale_bitmap_impl(glyph_id, true, strike, image) == Some(true)
635 }
636
637 /// Scales a color bitmap for the specified glyph and mode.
638 pub fn scale_color_bitmap(&mut self, glyph_id: u16, strike: StrikeWith) -> Option<Image> {
639 let mut image = Image::new();
640 if self.scale_color_bitmap_into(glyph_id, strike, &mut image) {
641 Some(image)
642 } else {
643 None
644 }
645 }
646
647 fn scale_bitmap_impl(
648 &mut self,
649 glyph_id: GlyphId,
650 color: bool,
651 strike: StrikeWith,
652 image: &mut Image,
653 ) -> Option<bool> {
654 image.clear();
655 let size = self.size;
656 let mut strikes = if color {
657 self.proxy.bitmaps.materialize_color(&self.font)
658 } else {
659 self.proxy.bitmaps.materialize_alpha(&self.font)
660 };
661 let bitmap = match strike {
662 StrikeWith::ExactSize => {
663 if self.size == 0. {
664 None
665 } else {
666 strikes
667 .find_by_exact_ppem(size as u16, glyph_id)?
668 .get(glyph_id)
669 }
670 }
671 StrikeWith::BestFit => {
672 if self.size == 0. {
673 None
674 } else {
675 strikes
676 .find_by_nearest_ppem(size as u16, glyph_id)?
677 .get(glyph_id)
678 }
679 }
680 StrikeWith::LargestSize => strikes.find_by_largest_ppem(glyph_id)?.get(glyph_id),
681 StrikeWith::Index(i) => strikes
682 .nth(i as usize)
683 .and_then(|strike| strike.get(glyph_id)),
684 }?;
685 if bitmap.ppem == 0 {
686 return None;
687 }
688 let (_, _, bufsize) = bitmap.scaled_size(size);
689 image.data.resize(bufsize, 0);
690 self.state.scratch0.clear();
691 self.state.scratch1.clear();
692 let mut w = bitmap.width;
693 let mut h = bitmap.height;
694 let scale = size / bitmap.ppem as f32;
695 image.placement = if size != 0. && scale != 1. {
696 self.state
697 .scratch0
698 .resize(bitmap.format.buffer_size(w, h), 0);
699 w = (w as f32 * scale) as u32;
700 h = (h as f32 * scale) as u32;
701 image.data.resize(bitmap.format.buffer_size(w, h), 0);
702 if !bitmap.decode(Some(&mut self.state.scratch1), &mut self.state.scratch0) {
703 return None;
704 }
705 if !bitmap::resize(
706 &self.state.scratch0,
707 bitmap.width,
708 bitmap.height,
709 bitmap.format.channels(),
710 &mut image.data,
711 w,
712 h,
713 bitmap::Filter::Mitchell,
714 Some(&mut self.state.scratch1),
715 ) {
716 return None;
717 }
718 let left = (bitmap.left as f32 * scale) as i32;
719 let top = (bitmap.top as f32 * scale) as i32;
720 Placement {
721 left,
722 top,
723 width: w,
724 height: h,
725 }
726 } else {
727 image.data.resize(bitmap.format.buffer_size(w, h), 0);
728 if !bitmap.decode(Some(&mut self.state.scratch1), &mut image.data) {
729 return None;
730 }
731 Placement {
732 left: bitmap.left,
733 top: bitmap.top,
734 width: w,
735 height: h,
736 }
737 };
738 image.source = match color {
739 true => Source::ColorBitmap(strike),
740 false => Source::Bitmap(strike),
741 };
742 image.content = match bitmap.format.channels() {
743 1 => Content::Mask,
744 _ => Content::Color,
745 };
746 // let mut advance = bitmap.advance() as f32;
747 // if options.size != 0. && options.size as u16 != bitmap.ppem() {
748 // advance *= options.size / bitmap.ppem() as f32;
749 // }
750 Some(true)
751 }
752}
753
754/// Builder type for rendering a glyph into an image.
755///
756/// See the module level [documentation](index.html#rendering) for detail.
757#[cfg(feature = "render")]
758pub struct Render<'a> {
759 sources: &'a [Source],
760 format: Format,
761 offset: Point,
762 transform: Option<Transform>,
763 embolden: f32,
764 foreground: [u8; 4],
765 style: Style<'a>,
766}
767
768#[cfg(feature = "render")]
769impl<'a> Render<'a> {
770 /// Creates a new builder for configuring rendering using the specified
771 /// prioritized list of sources.
772 pub fn new(sources: &'a [Source]) -> Self {
773 Self {
774 sources,
775 format: Format::Alpha,
776 offset: Point::new(0., 0.),
777 transform: None,
778 embolden: 0.,
779 foreground: [128, 128, 128, 255],
780 style: Style::default(),
781 }
782 }
783
784 /// Specifies the target format for rasterizing an outline. Default is
785 /// [`Format::Alpha`].
786 pub fn format(&mut self, format: Format) -> &mut Self {
787 self.format = format;
788 self
789 }
790
791 /// Specifies the path style to use when rasterizing an outline. Default is
792 /// [`Fill::NonZero`](zeno::Fill::NonZero).
793 pub fn style(&mut self, style: impl Into<Style<'a>>) -> &mut Self {
794 self.style = style.into();
795 self
796 }
797
798 /// Specifies an additional offset to apply when rasterizing an outline.
799 /// Default is `(0, 0)`.
800 pub fn offset(&mut self, offset: Vector) -> &mut Self {
801 self.offset = offset;
802 self
803 }
804
805 /// Specifies a transformation matrix to apply when rasterizing an
806 /// outline. Default is `None`.
807 pub fn transform(&mut self, transform: Option<Transform>) -> &mut Self {
808 self.transform = transform;
809 self
810 }
811
812 /// Specifies the strength of a faux bold transform to apply when
813 /// rasterizing an outline. Default is `0`.
814 pub fn embolden(&mut self, strength: f32) -> &mut Self {
815 self.embolden = strength;
816 self
817 }
818
819 /// Specifies an RGBA color to use when rasterizing layers of a color
820 /// outline that do not directly reference a palette color. Default is
821 /// `[128, 128, 128, 255]`.
822 pub fn default_color(&mut self, color: [u8; 4]) -> &mut Self {
823 self.foreground = color;
824 self
825 }
826
827 /// Renders the specified glyph using the current configuration into the
828 /// provided image.
829 pub fn render_into(&self, scaler: &mut Scaler, glyph_id: GlyphId, image: &mut Image) -> bool {
830 for source in self.sources {
831 match source {
832 Source::Outline => {
833 if !scaler.has_outlines() {
834 continue;
835 }
836 scaler.state.outline.clear();
837 if scaler.scale_outline_impl(glyph_id, None, None) {
838 let state = &mut scaler.state;
839 let rcx = &mut state.rcx;
840 let outline = &mut state.outline;
841 if self.embolden != 0. {
842 outline.embolden(self.embolden, self.embolden);
843 }
844 if let Some(transform) = &self.transform {
845 outline.transform(transform);
846 }
847 let placement = Mask::with_scratch(outline.path(), rcx)
848 .format(self.format)
849 .origin(Origin::BottomLeft)
850 .style(self.style)
851 .render_offset(self.offset)
852 .inspect(|fmt, w, h| {
853 image.data.resize(fmt.buffer_size(w, h), 0);
854 })
855 .render_into(&mut image.data[..], None);
856 image.placement = placement;
857 image.content = if self.format == Format::Alpha {
858 Content::Mask
859 } else {
860 Content::SubpixelMask
861 };
862 image.source = Source::Outline;
863 return true;
864 }
865 }
866 Source::ColorOutline(palette_index) => {
867 if !scaler.has_color_outlines() {
868 continue;
869 }
870 scaler.state.outline.clear();
871 if scaler.scale_color_outline_impl(glyph_id) {
872 let font = &scaler.font;
873 let proxy = &scaler.proxy;
874 let state = &mut scaler.state;
875 let scratch = &mut state.scratch0;
876 let rcx = &mut state.rcx;
877 let outline = &mut state.outline;
878 // Cool effect, but probably not generally desirable.
879 // Maybe expose a separate option?
880 // if self.embolden != 0. {
881 // outline.embolden(self.embolden, self.embolden);
882 // }
883 if let Some(transform) = &self.transform {
884 outline.transform(transform);
885 }
886 let palette = proxy.color.palette(font, *palette_index);
887
888 let total_bounds = outline.bounds();
889
890 // need to take offset into account when placing glyph
891 let base_x = (total_bounds.min.x + self.offset.x).floor() as i32;
892 let base_y = (total_bounds.min.y + self.offset.y).ceil() as i32;
893 let base_w = total_bounds.width().ceil() as u32;
894 let base_h = total_bounds.height().ceil() as u32;
895
896 image.data.resize((base_w * base_h * 4) as usize, 0);
897 image.placement.left = base_x;
898 image.placement.top = base_h as i32 + base_y;
899 image.placement.width = total_bounds.width().ceil() as u32;
900 image.placement.height = total_bounds.height().ceil() as u32;
901
902 let mut ok = true;
903 for i in 0..outline.len() {
904 let layer = match outline.get(i) {
905 Some(layer) => layer,
906 _ => {
907 ok = false;
908 break;
909 }
910 };
911
912 scratch.clear();
913 let placement = Mask::with_scratch(layer.path(), rcx)
914 .origin(Origin::BottomLeft)
915 .style(self.style)
916 .render_offset(self.offset)
917 .inspect(|fmt, w, h| {
918 scratch.resize(fmt.buffer_size(w, h), 0);
919 })
920 .render_into(&mut scratch[..], None);
921 let color = layer
922 .color_index()
923 .and_then(|i| palette.map(|p| p.get(i)))
924 .unwrap_or(self.foreground);
925 bitmap::blit(
926 &scratch[..],
927 placement.width,
928 placement.height,
929 placement.left.wrapping_sub(base_x),
930 (base_h as i32 + base_y).wrapping_sub(placement.top),
931 color,
932 &mut image.data,
933 base_w,
934 base_h,
935 );
936 }
937 if ok {
938 image.source = Source::ColorOutline(*palette_index);
939 image.content = Content::Color;
940 return true;
941 }
942 }
943 }
944 Source::Bitmap(mode) => {
945 if !scaler.has_bitmaps() {
946 continue;
947 }
948 if scaler.scale_bitmap_into(glyph_id, *mode, image) {
949 return true;
950 }
951 }
952 Source::ColorBitmap(mode) => {
953 if !scaler.has_color_bitmaps() {
954 continue;
955 }
956 if scaler.scale_color_bitmap_into(glyph_id, *mode, image) {
957 return true;
958 }
959 }
960 }
961 }
962 false
963 }
964
965 /// Renders the specified glyph using the current configuration.
966 pub fn render(&self, scaler: &mut Scaler, glyph_id: GlyphId) -> Option<Image> {
967 let mut image = Image::new();
968 if self.render_into(scaler, glyph_id, &mut image) {
969 Some(image)
970 } else {
971 None
972 }
973 }
974}