Skip to main content

pfx_live/flat/
cpu_text.rs

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: &paragraph.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(&paragraph.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}