grafix_toolbox/kit/opengl/utility/
font.rs1use crate::{lib::*, math::*};
2use GL::*;
3
4#[derive_as_obj(Default)]
5pub struct Glyph {
6 pub adv: f32,
7 pub lsb: f32,
8 pub coord: Vec4,
9 pub uv: Vec4,
10}
11
12#[derive_as_ser(Default, Debug)]
13pub struct Font {
14 glyphs: HashMap<char, Glyph>,
15 kerning: HashMap<char, HashMap<char, f32>>,
16 tex: Option<Tex2d<RED, u8>>,
17}
18impl Font {
19 pub fn tex(&self) -> &Tex2d<RED, u8> {
20 self.tex.as_ref().unwrap_or_else(|| LeakyStatic!(Tex2d<RED, u8>, { Tex2d::none() }))
21 }
22 pub fn glyph(&self, c: char) -> &Glyph {
23 self.glyphs.get(&c).unwrap_or_else(|| {
24 WARN!("No glyph {c:?} in Font");
25 self.glyphs.get(&' ').fail()
26 })
27 }
28 pub fn adv_coord(&self, prev: &Option<char>, c: char) -> (f32, Vec4) {
29 let (a, c, _) = self.adv_coord_uv(prev, c);
30 (a, c)
31 }
32 pub fn adv_coord_uv(&self, prev: &Option<char>, c: char) -> (f32, Vec4, Vec4) {
33 let g = self.glyph(c);
34 let k = prev.map_or(0., |p| self.kern(p, c));
35 let x = k;
36 (g.adv + k, g.coord.sum((x, 0, x, 0)), g.uv)
37 }
38 pub fn kern(&self, l: char, r: char) -> f32 {
39 (|| Some(*self.kerning.get(&l)?.get(&r)?))().unwrap_or(0.)
40 }
41 #[cfg(all(feature = "adv_fs", feature = "sdf"))]
42 pub fn new_cached(name: &str, alphabet: impl AsRef<str>) -> Self {
43 let alphabet = alphabet.as_ref();
44 let alph_chksum = chksum::const_fnv1(alphabet.as_bytes()).to_string();
45 let cache = format!("{name}.{alph_chksum}.font.z");
46 if let Ok(font) = FS::Load::Archive(&cache).and_then(ser::from_vec) {
47 return font;
48 }
49
50 (|| -> Res<_> {
51 format!("res/{name}.ttf")
52 .pipe(FS::Load::File)?
53 .pipe_as(|data| Self::new(data, alphabet))?
54 .tap(|font| ser::to_vec(font).map(|v| FS::Save::Archive((cache, v, 3))).warn())
55 .pipe(Ok)
56 })()
57 .explain_err(|| format!("Cannot load font {name:?}"))
58 .warn()
59 }
60 #[cfg(feature = "sdf")]
61 pub fn new(font_data: &[u8], alphabet: &str) -> Res<Self> {
62 use {super::sdf::*, crate::math::*, rusttype as ttf};
63 let (g_size, border, supersample) = (18, 2, 8);
64 let alphabet = || alphabet.chars();
65 let g_size = f32(g_size * supersample).pipe(ttf::Scale::uniform);
66 let border_sdf = border * supersample * supersample;
67 let border_keep = border * supersample;
68
69 let font = ttf::Font::try_from_bytes(font_data).res()?;
70
71 let (mut sdf, mut glyphs) = (Def::<SdfGenerator>(), vec![]);
72 let (min_y, div) = {
73 let v = font.v_metrics(g_size);
74 (v.descent, 1. / (v.ascent - v.descent).abs())
75 };
76 let geometry = alphabet()
77 .map(|c| {
78 let g = font.glyph(c).scaled(g_size);
79 let (adv, lsb) = {
80 let m = g.h_metrics();
81 (m.advance_width, m.left_side_bearing)
82 };
83 let g = g.positioned(ttf::point(0., min_y));
84 let bb = g.pixel_bounding_box().map_or(Vec4(0), |bb| {
85 let bb = (bb.min.x, -bb.max.y, bb.max.x, -bb.min.y);
86 let s = bb.zw().sub(bb.xy()).abs();
87 let s_real = {
88 ASSERT!(s.gt(0).all(), "Corrupt font data");
89 let b = border_sdf;
90 let (w, h) = ulVec2(s.sum(b * 2));
91 let data = vec![0; w * h].tap(|d| g.draw(|x, y, v| d[w * (h - usize(b + i32(y))) + usize(b + i32(x))] = u8((v * 255.).min(255.))));
92 let sdf = sdf.generate::<RED, u8, RED>(&Tex2d::new((w, h), &data[..]), b);
93 let b = b - border_keep;
94 let sdf = sdf.Cut(iVec4((0, 0, w, h)).sum((b, b, -b, -b)));
95 let uImage::<RED> { w, h, data, .. } = sdf.Cast::<RED, u8>(supersample).into();
96 glyphs.push((c, ImgBox { w, h, data }));
97 Vec2((w, h).mul(supersample))
98 };
99 let o = Vec2(s).sub(s_real).mul(0.5);
100 let ((x, y), (w, h)) = (Vec2(bb.xy()).sum(o), s_real);
101 let _half_texel @ b = 0.5 * f32(supersample);
102 (x, y, x + w, y + h).sum((b, b, -b, -b))
103 });
104 (c, bb, (adv, lsb))
105 })
106 .collect_vec();
107
108 let (mut atlas, _rejects) = GL::atlas::pack_into_atlas::<_, _, RED, _>(glyphs, GL::MAX_TEXTURE_SIZE(), GL::MAX_TEXTURE_SIZE());
109 ASSERT!(_rejects.is_empty(), "GPU cannot fit font texture");
110
111 let kerning = alphabet()
112 .filter_map(|c| {
113 let kern = alphabet()
114 .filter_map(|g| {
115 let k = font.pair_kerning(g_size, c, g) * div;
116 Some((g, k)).filter(|_| !k.eps_eq(0.))
117 })
118 .collect::<HashMap<_, _>>();
119 Some((c, kern)).filter(|(_, k)| !k.is_empty())
120 })
121 .collect();
122 DEBUG!("Font kerning: {kerning:?}");
123
124 let mut tex = None;
125 let glyphs = geometry
126 .into_iter()
127 .map(|(c, coord, adv_lsb)| {
128 let (adv, lsb) = adv_lsb.mul(div);
129 let Some(e) = atlas.remove(&c) else {
130 return (c, Glyph { adv, coord: (0., 0., adv, 0.), ..Def() });
131 };
132 let uv = e.region.sum((0.5, 0.5, -0.5, -0.5).div(e.atlas.whdl().xyxy()));
133 tex = Some(e.atlas);
134 let coord = coord.sub((-lsb, 0, -lsb, 0)).mul(div);
135 (c, Glyph { adv, lsb, coord, uv })
136 })
137 .collect();
138
139 debug_assert!({
140 #[cfg(feature = "png")]
141 {
142 let _i: uImage<RED> = (&**tex.as_valid()).into();
143 _i.save(format!("{}.png", chksum::ref_UUID(&_i)));
144 }
145 true
146 });
147
148 let tex = tex.ok_or("Font has no atlas")?.pipe(Rc::into_inner);
149
150 Self { glyphs, kerning, tex }.pipe(Ok)
151 }
152}
153unsafe impl Sync for Font {}
154
155#[cfg(feature = "sdf")]
156struct ImgBox {
157 w: u32,
158 h: u32,
159 data: Box<[u8]>,
160}
161#[cfg(feature = "sdf")]
162impl PartialEq for ImgBox {
163 fn eq(&self, r: &Self) -> bool {
164 if self.w != r.w && self.h != r.h {
165 return false;
166 }
167 let diff = self.data.iter().zip(&r.data[..]).map(|(&l, &r)| (i32(l) - i32(r)).abs()).max().unwrap_or(0);
168 diff < 5
169 }
170}
171#[cfg(feature = "sdf")]
172impl GL::atlas::Tile<u8> for ImgBox {
173 fn w(&self) -> i32 {
174 i32(self.w)
175 }
176 fn h(&self) -> i32 {
177 i32(self.h)
178 }
179 fn data(&self) -> &[u8] {
180 &self.data
181 }
182}