rafx_plugins/assets/font/
font_cooking.rs

1use fnv::FnvHashMap;
2use serde::{Deserialize, Serialize};
3use std::collections::BTreeMap;
4use std::hash::BuildHasherDefault;
5
6#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
7pub struct FontTextureCharacterMeta {
8    pub character: char,
9    pub rect: FontTextureCharacterRect,
10}
11
12#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
13pub struct FontTextureCharacterRect {
14    pub x: u16,
15    pub y: u16,
16    pub w: u16,
17    pub h: u16,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct FontTexture {
22    #[serde(with = "serde_bytes")]
23    pub image_data: Vec<u8>,
24    pub image_width: u32,
25    pub image_height: u32,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct FontTextureWithMeta {
30    pub font_texture: FontTexture,
31    pub characters: Vec<FontTextureCharacterMeta>,
32}
33
34pub struct FontTextureWithLookup {
35    pub font_texture: FontTexture,
36    pub character_lookup: FnvHashMap<char, FontTextureCharacterRect>,
37}
38
39impl FontTextureWithLookup {
40    pub fn to_font_texture_with_meta(self) -> FontTextureWithMeta {
41        let mut characters: Vec<_> = self
42            .character_lookup
43            .iter()
44            .map(|(&c, &r)| FontTextureCharacterMeta {
45                character: c,
46                rect: r,
47            })
48            .collect();
49        characters.sort_by_key(|x| x.character);
50
51        FontTextureWithMeta {
52            font_texture: self.font_texture,
53            characters,
54        }
55    }
56
57    pub fn from_font_texture_with_meta(font_texture_with_meta: FontTextureWithMeta) -> Self {
58        let mut character_lookup = FnvHashMap::with_capacity_and_hasher(
59            font_texture_with_meta.characters.len(),
60            BuildHasherDefault::default(),
61        );
62
63        for c in font_texture_with_meta.characters {
64            character_lookup.insert(c.character, c.rect);
65        }
66
67        FontTextureWithLookup {
68            character_lookup,
69            font_texture: font_texture_with_meta.font_texture,
70        }
71    }
72}
73
74pub fn create_font_texture_with_ranges(
75    font_data: &[u8],
76    character_ranges_to_include: &[(u32, u32)],
77    size: f32,
78    margin: u32,
79) -> Option<FontTextureWithMeta> {
80    // let character_ranges_to_include = vec![
81    //     (32, 128),
82    //     //(0x4e00, 0x5FCC)
83    // ];
84
85    let mut characters_to_include = vec![];
86
87    //
88    // Iterate codepoints in the font and find the characters within the given ranges
89    //
90    let face = ttf_parser::Face::from_slice(font_data, 0).unwrap();
91
92    for subtable in face.character_mapping_subtables() {
93        subtable.codepoints(|codepoint| {
94            for range in character_ranges_to_include {
95                if codepoint >= range.0 && codepoint <= range.1 {
96                    if let Some(_) = subtable.glyph_index(codepoint) {
97                        characters_to_include.push(std::char::from_u32(codepoint).unwrap());
98                    }
99                }
100            }
101        });
102    }
103
104    //
105    // Rasterize the characters to a bunch of tiny u8 bitmaps. Also create a list of regions for
106    // rectangle_pack to place
107    //
108    let settings = fontdue::FontSettings {
109        scale: size,
110        ..fontdue::FontSettings::default()
111    };
112    let font = fontdue::Font::from_bytes(font_data, settings).unwrap();
113
114    create_font_texture_with_characters(&font, characters_to_include.iter(), size, margin)
115}
116
117pub fn create_font_texture_with_characters<'a, IterT: Iterator<Item = &'a char>>(
118    font: &fontdue::Font,
119    characters: IterT,
120    size: f32,
121    margin: u32,
122) -> Option<FontTextureWithMeta> {
123    let mut rasterized_data = FnvHashMap::default();
124    let mut rects_to_place = rectangle_pack::GroupedRectsToPlace::<char, ()>::new();
125
126    for &c in characters {
127        let (metrics, data) = font.rasterize(c, size);
128        rects_to_place.push_rect(
129            c,
130            None,
131            rectangle_pack::RectToInsert::new(
132                metrics.width as u32 + (margin * 2),
133                metrics.height as u32 + (margin * 2),
134                1,
135            ),
136        );
137        rasterized_data.insert(c, (metrics, data));
138    }
139
140    //
141    // Try packing in progressively larger textures (128x128, 256x256, 512x512, ... 4096x4096)
142    //
143    let mut texture_dimensions = 128;
144    let result = loop {
145        let mut target_bins = BTreeMap::new();
146        target_bins.insert(
147            0,
148            rectangle_pack::TargetBin::new(texture_dimensions, texture_dimensions, 1),
149        );
150
151        let pack_result = rectangle_pack::pack_rects(
152            &rects_to_place,
153            target_bins,
154            &rectangle_pack::volume_heuristic,
155            &rectangle_pack::contains_smallest_box,
156        );
157
158        if let Ok(rectangle_placements) = pack_result {
159            break Some((texture_dimensions, rectangle_placements));
160        }
161
162        texture_dimensions *= 2;
163        if texture_dimensions > 4096 {
164            break None;
165        }
166    };
167
168    if result.is_none() {
169        eprintln!("Too much data, requires more than a 4k texture to store");
170        return None;
171    }
172
173    //
174    // Create the texture and copy the per-character bitmaps into it
175    //
176    let (texture_dimensions, placement) = result.unwrap();
177    let mut image_data = vec![0; texture_dimensions as usize * texture_dimensions as usize];
178    let mut character_meta = Vec::with_capacity(placement.packed_locations().len());
179
180    for (&c, (_, location)) in placement.packed_locations() {
181        let (metrics, src_data) = &rasterized_data[&c];
182        assert_eq!(metrics.width as u32, location.width() - (2 * margin));
183        assert_eq!(metrics.height as u32, location.height() - (2 * margin));
184
185        for src_x in 0..metrics.width {
186            for src_y in 0..metrics.height {
187                let src_i = metrics.width * src_y + src_x;
188
189                let dst_x = location.x() + src_x as u32 + margin;
190                let dst_y = location.y() + src_y as u32 + margin;
191                let dst_i = texture_dimensions * dst_y + dst_x;
192
193                image_data[dst_i as usize] = src_data[src_i as usize];
194            }
195        }
196
197        character_meta.push(FontTextureCharacterMeta {
198            character: c,
199            rect: FontTextureCharacterRect {
200                x: (location.x() + margin) as u16,
201                y: (location.y() + margin) as u16,
202                w: metrics.width as u16,
203                h: metrics.height as u16,
204            },
205        });
206    }
207
208    Some(FontTextureWithMeta {
209        font_texture: FontTexture {
210            image_data,
211            image_width: texture_dimensions,
212            image_height: texture_dimensions,
213        },
214        characters: character_meta,
215    })
216}