Skip to main content

grafix_toolbox/kit/opengl/utility/
font.rs

1use 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}