1use pfx_text::{Atlas, MSDF_SPREAD, Paragraph};
2
3use super::{Draw, Fit, Glyphs, Matrix, Srgba, multiply};
4use crate::text::{PackedGlyph, pack_glyphs, pack_quads};
5
6pub const ID_CUTOUT: f32 = 0.5;
7pub const ANISOTROPY_FROM: f32 = 1.2;
8pub const ANISOTROPY_FULL: f32 = 1.5;
9pub const MOST_TAPS: u32 = 8;
10const SUBPIXEL: f32 = 256.0;
11const EDGE: f32 = 1e-4;
12const MOST_LEVEL: f32 = 3.0;
13const MOST_LEVELS: usize = 4;
14
15#[derive(Clone, Copy)]
16pub struct CpuText<'a> {
17 pub atlas: &'a Atlas,
18 pub glyphs: Glyphs<'a>,
19 pub colour: Srgba,
20}
21
22impl<'a> CpuText<'a> {
23 pub fn paragraph(paragraph: &'a Paragraph, colour: Srgba) -> Self {
24 Self {
25 atlas: ¶graph.atlas,
26 glyphs: Glyphs::Paragraph(paragraph),
27 colour,
28 }
29 }
30}
31
32type V2 = [f32; 2];
33
34fn sub(a: V2, b: V2) -> V2 {
35 [a[0] - b[0], a[1] - b[1]]
36}
37
38fn dot(a: V2, b: V2) -> f32 {
39 a[0] * b[0] + a[1] * b[1]
40}
41
42fn length(a: V2) -> f32 {
43 dot(a, a).sqrt()
44}
45
46fn smoothstep(from: f32, to: f32, x: f32) -> f32 {
47 let t = ((x - from) / (to - from)).clamp(0.0, 1.0);
48 t * t * (3.0 - 2.0 * t)
49}
50
51fn median(rgb: [f32; 3]) -> f32 {
52 rgb[0].min(rgb[1]).max(rgb[0].max(rgb[1]).min(rgb[2]))
53}
54
55struct Surface<'a> {
56 atlas: &'a Atlas,
57 msdf: bool,
58 size: V2,
59 levels: usize,
60}
61
62impl<'a> Surface<'a> {
63 fn new(atlas: &'a Atlas) -> Option<Self> {
64 let first = atlas.levels.first()?;
65 if atlas.channels != 1 && atlas.channels != 3 {
66 return None;
67 }
68 Some(Self {
69 atlas,
70 msdf: atlas.channels == 3,
71 size: [first.width as f32, first.height as f32],
72 levels: atlas.levels.len().min(MOST_LEVELS),
73 })
74 }
75
76 fn texel(&self, level: usize, x: i64, y: i64) -> [f32; 3] {
77 let mip = &self.atlas.levels[level];
78 let x = x.clamp(0, i64::from(mip.width) - 1) as usize;
79 let y = y.clamp(0, i64::from(mip.height) - 1) as usize;
80 let channels = self.atlas.channels as usize;
81 let at = (y * mip.width as usize + x) * channels;
82 let byte = |i: usize| f32::from(mip.bytes.get(at + i).copied().unwrap_or(0)) / 255.0;
83 if channels == 1 {
84 [byte(0), 0.0, 0.0]
85 } else {
86 [byte(0), byte(1), byte(2)]
87 }
88 }
89
90 fn bilinear(&self, level: usize, uv: V2) -> [f32; 3] {
91 let mip = &self.atlas.levels[level];
92 let x = uv[0] * mip.width as f32 - 0.5;
93 let y = uv[1] * mip.height as f32 - 0.5;
94 let (x0, y0) = (x.floor(), y.floor());
95 let (tx, ty) = (x - x0, y - y0);
96 let (x0, y0) = (x0 as i64, y0 as i64);
97 let a = self.texel(level, x0, y0);
98 let b = self.texel(level, x0 + 1, y0);
99 let c = self.texel(level, x0, y0 + 1);
100 let d = self.texel(level, x0 + 1, y0 + 1);
101 std::array::from_fn(|i| {
102 let top = a[i] + (b[i] - a[i]) * tx;
103 let bottom = c[i] + (d[i] - c[i]) * tx;
104 top + (bottom - top) * ty
105 })
106 }
107
108 fn sample(&self, level: f32, uv: V2) -> [f32; 3] {
109 let level = level.clamp(0.0, (self.levels - 1) as f32);
110 let low = level.floor();
111 let t = level - low;
112 let low = low as usize;
113 let first = self.bilinear(low, uv);
114 if t == 0.0 {
115 return first;
116 }
117 let second = self.bilinear((low + 1).min(self.levels - 1), uv);
118 std::array::from_fn(|i| first[i] + (second[i] - first[i]) * t)
119 }
120
121 fn distance(&self, uv: V2) -> f32 {
122 (median(self.sample(0.0, uv)) - 0.5) * MSDF_SPREAD
123 }
124}
125
126struct Quad {
127 glyph: PackedGlyph,
128 origin: V2,
129 inverse: [f32; 4],
130 point: V2,
131 along_a: V2,
132 along_b: V2,
133 pixels: [u32; 4],
134}
135
136fn rotate(angle: f32, v: V2) -> V2 {
137 let (s, c) = angle.sin_cos();
138 [c * v[0] - s * v[1], s * v[0] + c * v[1]]
139}
140
141fn place(glyph: &PackedGlyph, corner: V2) -> (V2, V2) {
142 let local = [
143 (glyph.rect[0] + corner[0] * glyph.rect[2]) * glyph.pose[3],
144 (glyph.rect[1] + corner[1] * glyph.rect[3]) * glyph.pose[3],
145 ];
146 let spun = rotate(glyph.pose[2], local);
147 let point = [glyph.pose[0] + spun[0], glyph.pose[1] + spun[1]];
148 let pivot = [glyph.turn[0], glyph.turn[1]];
149 let turned = rotate(glyph.turn[2], sub(point, pivot));
150 (point, [pivot[0] + turned[0], pivot[1] + turned[1]])
151}
152
153fn snapped(p: V2) -> V2 {
154 [
155 (p[0] * SUBPIXEL).round() / SUBPIXEL,
156 (p[1] * SUBPIXEL).round() / SUBPIXEL,
157 ]
158}
159
160fn screen(matrix: &Matrix, at: V2, size: [u32; 2]) -> V2 {
161 let x = matrix[0][0] * at[0] + matrix[1][0] * at[1] + matrix[3][0];
162 let y = matrix[0][1] * at[0] + matrix[1][1] * at[1] + matrix[3][1];
163 let w = matrix[0][3] * at[0] + matrix[1][3] * at[1] + matrix[3][3];
164 let w = if w.abs() < 1e-12 { 1.0 } else { w };
165 [
166 (x / w * 0.5 + 0.5) * size[0] as f32,
167 (0.5 - y / w * 0.5) * size[1] as f32,
168 ]
169}
170
171impl Quad {
172 fn new(glyph: PackedGlyph, matrix: &Matrix, size: [u32; 2]) -> Option<Self> {
173 let (point, turned) = place(&glyph, [0.0, 0.0]);
174 let (point_a, turned_a) = place(&glyph, [1.0, 0.0]);
175 let (point_b, turned_b) = place(&glyph, [0.0, 1.0]);
176 let (_, turned_c) = place(&glyph, [1.0, 1.0]);
177 let origin = snapped(screen(matrix, turned, size));
178 let a = sub(snapped(screen(matrix, turned_a, size)), origin);
179 let b = sub(snapped(screen(matrix, turned_b, size)), origin);
180 let corner = snapped(screen(matrix, turned_c, size));
181 let det = a[0] * b[1] - b[0] * a[1];
182 if !det.is_finite() || det.abs() < 1e-12 {
183 return None;
184 }
185 let inverse = [b[1] / det, -b[0] / det, -a[1] / det, a[0] / det];
186 let xs = [origin[0], origin[0] + a[0], origin[0] + b[0], corner[0]];
187 let ys = [origin[1], origin[1] + a[1], origin[1] + b[1], corner[1]];
188 let low = |v: [f32; 4]| v.into_iter().fold(f32::MAX, f32::min);
189 let high = |v: [f32; 4]| v.into_iter().fold(f32::MIN, f32::max);
190 let (x0, y0) = (low(xs).floor().max(0.0), low(ys).floor().max(0.0));
191 let x1 = high(xs).ceil().min(size[0] as f32);
192 let y1 = high(ys).ceil().min(size[1] as f32);
193 if !(x1 > x0 && y1 > y0) {
194 return None;
195 }
196 Some(Self {
197 glyph,
198 origin,
199 inverse,
200 point,
201 along_a: sub(point_a, point),
202 along_b: sub(point_b, point),
203 pixels: [x0 as u32, y0 as u32, x1 as u32, y1 as u32],
204 })
205 }
206
207 fn corner(&self, x: i64, y: i64) -> V2 {
208 let q = [
209 x as f32 + 0.5 - self.origin[0],
210 y as f32 + 0.5 - self.origin[1],
211 ];
212 [
213 self.inverse[0] * q[0] + self.inverse[1] * q[1],
214 self.inverse[2] * q[0] + self.inverse[3] * q[1],
215 ]
216 }
217
218 fn uv(&self, corner: V2) -> V2 {
219 let uv = self.glyph.uv_rect;
220 [
221 uv[0] + (uv[2] - uv[0]) * corner[0],
222 uv[1] + (uv[3] - uv[1]) * corner[1],
223 ]
224 }
225
226 fn coverage(&self, surface: &Surface<'_>, x: u32, y: u32) -> Option<f32> {
227 let (xi, yi) = (i64::from(x), i64::from(y));
228 let corner = self.corner(xi, yi);
229 if !(-EDGE..1.0 - EDGE).contains(&corner[0]) || !(-EDGE..1.0 - EDGE).contains(&corner[1]) {
230 return None;
231 }
232 let uv = self.uv(corner);
233 let bounds = self.glyph.uv_rect;
234 let delta = [bounds[2] - bounds[0], bounds[3] - bounds[1]];
235 let dx = [delta[0] * self.inverse[0], delta[1] * self.inverse[2]];
236 let dy = [delta[0] * self.inverse[1], delta[1] * self.inverse[3]];
237 let size = surface.size;
238 let scaled = |d: V2| [d[0] * size[0], d[1] * size[1]];
239 let (jx, jy) = (scaled(dx), scaled(dy));
240 let extent = [
241 (bounds[2] - bounds[0]) * size[0],
242 (bounds[3] - bounds[1]) * size[1],
243 ];
244 let level = if surface.msdf {
245 0.0
246 } else {
247 let footprint = length(jx).max(length(jy));
248 let safe = extent[0].min(extent[1]).max(1.0).log2().floor();
249 footprint.max(1.0).log2().clamp(0.0, safe.min(MOST_LEVEL))
250 };
251 let half = [level.exp2() * 0.5 / size[0], level.exp2() * 0.5 / size[1]];
252 let low = [
253 (bounds[0] + half[0]).min(bounds[2]),
254 (bounds[1] + half[1]).min(bounds[3]),
255 ];
256 let high = [
257 (bounds[2] - half[0]).max(low[0]),
258 (bounds[3] - half[1]).max(low[1]),
259 ];
260 let clamp = |uv: V2| [uv[0].clamp(low[0], high[0]), uv[1].clamp(low[1], high[1])];
261 let alpha = if !surface.msdf {
262 surface.sample(level, clamp(uv))[0]
263 } else {
264 let signed = |x: i64, y: i64| {
265 let uv = self.uv(self.corner(x, y));
266 median(surface.sample(0.0, clamp(uv))) - 0.5
267 };
268 let here = signed(xi, yi);
269 let (qx, qy) = (xi & !1, yi & !1);
270 let corner = signed(qx, qy);
271 let across = signed(qx + 1, qy) - corner;
272 let down = signed(qx, qy + 1) - corner;
273 let fwidth = across.abs() + down.abs();
274 let mut alpha = (0.5 + here / fwidth.max(1e-5)).clamp(0.0, 1.0);
275 let a = dot(jx, jx);
276 let b = dot(jx, jy);
277 let c = dot(jy, jy);
278 let area = (jx[0] * jy[1] - jx[1] * jy[0]).abs();
279 let major_squared = 0.5 * (a + c) + (0.25 * (a - c) * (a - c) + b * b).sqrt();
280 if major_squared > ANISOTROPY_FROM * area {
281 let major = major_squared.sqrt();
282 let minor = area / major.max(1e-6);
283 let blend = smoothstep(ANISOTROPY_FROM, ANISOTROPY_FULL, major / minor.max(1e-6));
284 let taps = (major / minor.max(1.0)).ceil().clamp(1.0, MOST_TAPS as f32) as u32;
285 let axis = major_axis(a, b, c, major_squared);
286 let filtered = anisotropic(surface, uv, [low, high], [jx, jy], axis, taps);
287 alpha += (filtered - alpha) * blend;
288 }
289 alpha
290 };
291 let point = [
292 self.point[0] + self.along_a[0] * corner[0] + self.along_b[0] * corner[1],
293 self.point[1] + self.along_a[1] * corner[0] + self.along_b[1] * corner[1],
294 ];
295 let clip = self.glyph.clip;
296 let inside = point[0] >= clip[0]
297 && point[0] <= clip[2]
298 && point[1] >= clip[1]
299 && point[1] <= clip[3];
300 Some(if inside { alpha } else { 0.0 })
301 }
302}
303
304fn major_axis(a: f32, b: f32, c: f32, major: f32) -> V2 {
305 let first = [b, major - a];
306 let second = [major - c, b];
307 let longer = if dot(first, first) >= dot(second, second) {
308 first
309 } else {
310 second
311 };
312 let squared = dot(longer, longer);
313 if squared <= 1e-12 {
314 return [1.0, 0.0];
315 }
316 let n = squared.sqrt();
317 [longer[0] / n, longer[1] / n]
318}
319
320fn anisotropic(
321 surface: &Surface<'_>,
322 uv: V2,
323 clamp: [V2; 2],
324 jacobian: [V2; 2],
325 axis: V2,
326 taps: u32,
327) -> f32 {
328 let [jx, jy] = jacobian;
329 let along = [
330 jx[0] * axis[0] + jy[0] * axis[1],
331 jx[1] * axis[0] + jy[1] * axis[1],
332 ];
333 let across = [
334 jx[0] * -axis[1] + jy[0] * axis[0],
335 jx[1] * -axis[1] + jy[1] * axis[0],
336 ];
337 let texel = [1.0 / surface.size[0], 1.0 / surface.size[1]];
338 let count = taps as f32;
339 let mut total = 0.0;
340 for tap in 0..taps {
341 let offset = (tap as f32 + 0.5) / count - 0.5;
342 let at = [
343 (uv[0] + along[0] * offset * texel[0]).clamp(clamp[0][0], clamp[1][0]),
344 (uv[1] + along[1] * offset * texel[1]).clamp(clamp[0][1], clamp[1][1]),
345 ];
346 let distance = surface.distance(at);
347 let gradient = [
348 surface.distance([at[0] + texel[0], at[1]]) - distance,
349 surface.distance([at[0], at[1] + texel[1]]) - distance,
350 ];
351 let width = dot(gradient, along).abs() / count + dot(gradient, across).abs();
352 total += (0.5 + distance / width.max(1e-4)).clamp(0.0, 1.0);
353 }
354 total / count
355}
356
357pub(crate) struct Label<'a> {
358 surface: Surface<'a>,
359 quads: Vec<Quad>,
360}
361
362impl<'a> Label<'a> {
363 pub(crate) fn new(text: &CpuText<'a>, draw: &Draw, fit: &Fit, size: [u32; 2]) -> Option<Self> {
364 let surface = Surface::new(text.atlas)?;
365 let mut glyphs = match text.glyphs {
366 Glyphs::Paragraph(paragraph) => pack_glyphs(¶graph.glyphs),
367 Glyphs::Quads(quads) => pack_quads(quads),
368 };
369 let tint = text.colour.0;
370 for glyph in &mut glyphs {
371 for (channel, value) in glyph.color.iter_mut().zip(tint).take(3) {
372 *channel *= value;
373 }
374 glyph.color[3] *= tint[3] * draw.opacity.min(1.0);
375 }
376 let matrix = multiply(fit.clip_from_layout(), draw.transform);
377 let quads = glyphs
378 .into_iter()
379 .filter_map(|glyph| Quad::new(glyph, &matrix, size))
380 .collect::<Vec<_>>();
381 Some(Self { surface, quads })
382 }
383
384 pub(crate) fn blend(&self, image: &mut [[f32; 4]], size: [u32; 2]) {
385 for quad in &self.quads {
386 let [x0, y0, x1, y1] = quad.pixels;
387 for y in y0..y1 {
388 for x in x0..x1 {
389 let Some(alpha) = quad.coverage(&self.surface, x, y) else {
390 continue;
391 };
392 let colour = quad.glyph.color;
393 let opacity = alpha * colour[3];
394 let src = [
395 colour[0] * opacity,
396 colour[1] * opacity,
397 colour[2] * opacity,
398 opacity,
399 ];
400 let dst = &mut image[(y * size[0] + x) as usize];
401 for i in 0..4 {
402 dst[i] = src[i] + dst[i] * (1.0 - src[3]);
403 }
404 }
405 }
406 }
407 }
408
409 pub(crate) fn picked(&self, pixel: [u32; 2]) -> bool {
410 self.quads.iter().any(|quad| {
411 let [x0, y0, x1, y1] = quad.pixels;
412 pixel[0] >= x0
413 && pixel[0] < x1
414 && pixel[1] >= y0
415 && pixel[1] < y1
416 && quad.glyph.color[3] > 0.0
417 && quad
418 .coverage(&self.surface, pixel[0], pixel[1])
419 .is_some_and(|alpha| alpha >= ID_CUTOUT)
420 })
421 }
422}