1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// SPDX-License-Identifier: MIT OR Apache-2.0

use std::collections::HashMap;
use swash::scale::{ScaleContext, image::Content};
use swash::scale::{Render, Source, StrikeWith};
use swash::zeno::{Format, Vector};

use crate::{CacheKey, FontMatches};

pub use swash::scale::image::Image as SwashImage;

fn swash_image(context: &mut ScaleContext, matches: &FontMatches, cache_key: CacheKey) -> Option<SwashImage> {
    let font = match matches.get_font(&cache_key.font_id) {
        Some(some) => some,
        None => {
            log::warn!("did not find font {:?}", cache_key.font_id);
            return None;
        },
    };

    // Build the scaler
    let mut scaler = context
        .builder(font.as_swash())
        .size(cache_key.font_size as f32)
        .hint(true)
        .build();

    // Compute the fractional offset-- you'll likely want to quantize this
    // in a real renderer
    let offset =
        Vector::new(cache_key.x_bin.as_float(), cache_key.y_bin.as_float());

    // Select our source order
    Render::new(&[
        // Color outline with the first palette
        Source::ColorOutline(0),
        // Color bitmap with best fit selection mode
        Source::ColorBitmap(StrikeWith::BestFit),
        // Standard scalable outline
        Source::Outline,
    ])
    // Select a subpixel format
    .format(Format::Alpha)
    // Apply the fractional offset
    .offset(offset)
    // Render the image
    .render(&mut scaler, cache_key.glyph_id)
}

pub struct SwashCache {
    context: ScaleContext,
    pub image_cache: HashMap<CacheKey, Option<SwashImage>>,
}

impl SwashCache {
    /// Create a new swash cache
    pub fn new() -> Self {
        Self {
            context: ScaleContext::new(),
            image_cache: HashMap::new()
        }
    }

    /// Create a swash Image from a cache key, without caching results
    pub fn get_image_uncached(&mut self, matches: &FontMatches, cache_key: CacheKey) -> Option<SwashImage> {
        swash_image(&mut self.context, matches, cache_key)
    }

    /// Create a swash Image from a cache key, caching results
    pub fn get_image(&mut self, matches: &FontMatches, cache_key: CacheKey) -> &Option<SwashImage> {
        self.image_cache.entry(cache_key).or_insert_with(|| {
            swash_image(&mut self.context, matches, cache_key)
        })
    }

    /// Enumerate pixels in an Image, use `with_image` for better performance
    pub fn with_pixels<F: FnMut(i32, i32, u32)>(
        &mut self,
        matches: &FontMatches<'_>,
        cache_key: CacheKey,
        base: u32,
        mut f: F
    ) {
        if let Some(image) = self.get_image(matches, cache_key) {
            let x = image.placement.left;
            let y = -image.placement.top;

            match image.content {
                Content::Mask => {
                    let mut i = 0;
                    for off_y in 0..image.placement.height as i32 {
                        for off_x in 0..image.placement.width as i32 {
                            //TODO: blend base alpha?
                            let color = (image.data[i] as u32) << 24 | base & 0xFFFFFF;
                            f(x + off_x, y + off_y, color);
                            i += 1;
                        }
                    }
                }
                Content::Color => {
                    let mut i = 0;
                    for off_y in 0..image.placement.height as i32 {
                        for off_x in 0..image.placement.width as i32 {
                            //TODO: blend base alpha?
                            let color = (image.data[i + 3] as u32) << 24
                                | (image.data[i] as u32) << 16
                                | (image.data[i + 1] as u32) << 8
                                | (image.data[i + 2] as u32);
                            f(x + off_x, y + off_y, color);
                            i += 4;
                        }
                    }
                }
                Content::SubpixelMask => {
                    log::warn!("TODO: SubpixelMask");
                }
            }
        }
    }
}