rafx_plugins/assets/font/
font_cooking.rs1use 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 mut characters_to_include = vec![];
86
87 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 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 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 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}