Skip to main content

text/
record.rs

1// record -- glyphs placed now and blended later from the glyph cache, by any thread (the row
2// bands of ui's render_par.rs)
3//
4//   GlyphRec::default() / rec.begin(&text)   an empty record, bound to the cache as it is now
5//   text.record_n(&mut rec, dc, sf, x, y, s, len) -> placed   draw_n's glyphs appended to rec
6//        instead of blended (dc and sf as draw_n would get them; dc's colour is recorded with
7//        every glyph): layout, shaping, fallback fonts and rasterizing the masks into the cache
8//        happen now.  false: a glyph cannot come from the cache (too large: a filled path; a
9//        colour bitmap above the cache's limit) -- that text must be drawn with draw_n
10//   text.record_icon(&mut rec, dc, sf, cp, x, y, size_fx) -> placed   the same for icon_draw
11//   rec.len()                          glyphs so far (a text's range: len before .. after)
12//   rec.valid(&text)                   the cache was not flushed since begin (the keys hold)
13//   text.glyphs() -> GlyphView         the cache read-only, Send + Sync
14//   view.draw(&rec, from, to, dc, sf)  blend glyphs from .. to in their colours inside dc's
15//                                      clip (dc.row0 honoured): the bytes draw_n gives there
16//
17// A glyph is its cache key, pen x and baseline (absolute target pixels), its box (cheap
18// culling against the clip), the colour and how it is blended (mask, colour bitmap, subpixel
19// mask with a copy of the colour's coverage adjustment).  Masks depend only on the key, so
20// blending them later, clipped, gives the pixels of drawing them now.
21
22use alloc::vec::Vec;
23
24use gfx::{Dc, Surface};
25
26use crate::glyphcache::GcEntry;
27use crate::lcd::{self, Lut};
28use crate::{bitmap_blit, mask_blit, Text};
29
30const K_MASK: u8 = 0;
31const K_BITMAP: u8 = 1;
32const K_LCD_RGB: u8 = 2;
33const K_LCD_BGR: u8 = 3;
34
35// one recorded glyph
36#[derive(Clone, Copy)]
37struct Op {
38    key: u64,
39    x: i32,
40    y: i32,
41    /// x0, y0, x1, y1 of its pixels
42    b: [i32; 4],
43    color: u32,
44    kind: u8,
45    /// subpixel masks: the coverage adjustment (GlyphRec::adj)
46    adj: u16,
47}
48
49/// glyphs placed by record_n / record_icon, blended later by GlyphView::draw
50#[derive(Default)]
51pub struct GlyphRec {
52    ops: Vec<Op>,
53    /// (rgb, coverage adjustment) of the subpixel colours
54    adj: Vec<(u32, [u8; 256])>,
55    epoch: u32,
56    ok: bool,
57}
58
59impl GlyphRec {
60    /// empty, bound to the glyph cache as it is now
61    pub fn begin(&mut self, t: &Text) {
62        self.ops.clear();
63        self.adj.clear();
64        self.epoch = t.gc.epoch;
65        self.ok = true;
66    }
67
68    /// glyphs recorded so far
69    pub fn len(&self) -> usize {
70        self.ops.len()
71    }
72
73    pub fn is_empty(&self) -> bool {
74        self.ops.is_empty()
75    }
76
77    /// the glyph cache was not flushed since begin: every recorded key is still there
78    pub fn valid(&self, t: &Text) -> bool {
79        self.epoch == t.gc.epoch
80    }
81
82    fn push(&mut self, key: u64, e: &GcEntry, x: i32, y: i32, color: u32, kind: u8, adj: u16) {
83        if e.w <= 0 || e.h <= 0 {
84            return; // nothing to blend (a space)
85        }
86        let (x0, y0) = (x.wrapping_add(e.left), y.wrapping_add(e.top));
87        let b = [x0, y0, x0.wrapping_add(e.w), y0.wrapping_add(e.h)];
88        self.ops.push(Op { key, x, y, b, color, kind, adj });
89    }
90
91    pub(crate) fn mask(&mut self, key: u64, e: &GcEntry, x: i32, y: i32, color: u32) {
92        self.push(key, e, x, y, color, K_MASK, 0);
93    }
94
95    pub(crate) fn bitmap(&mut self, key: u64, e: &GcEntry, x: i32, y: i32, color: u32) {
96        self.push(key, e, x, y, color, K_BITMAP, 0);
97    }
98
99    #[allow(clippy::too_many_arguments)]
100    pub(crate) fn lcd(&mut self, key: u64, e: &GcEntry, x: i32, y: i32, color: u32, bgr: bool, adj: &[u8; 256]) {
101        let rgb = color & 0xFF_FFFF;
102        let i = match self.adj.iter().position(|a| a.0 == rgb) {
103            Some(i) => i,
104            None => {
105                self.adj.push((rgb, *adj));
106                self.adj.len() - 1
107            }
108        };
109        self.push(key, e, x, y, color, if bgr { K_LCD_BGR } else { K_LCD_RGB }, i as u16);
110    }
111
112    /// a glyph that cannot be blended from the cache
113    pub(crate) fn fail(&mut self) {
114        self.ok = false;
115    }
116}
117
118impl Text {
119    /// draw_n's glyphs appended to rec instead of blended (record.rs); false: one of them
120    /// cannot be blended from the cache, draw that text with draw_n
121    #[allow(clippy::too_many_arguments)]
122    pub fn record_n(&mut self, rec: &mut GlyphRec, dc: &mut Dc, sf: &mut Surface, x: i32, y: i32, s: &[u8], len: usize) -> bool {
123        rec.ok = true;
124        self.rec = Some(core::mem::take(rec));
125        self.draw_n(dc, sf, x, y, s, len);
126        self.rec_end(rec)
127    }
128
129    /// icon_draw's glyph appended to rec (record_n)
130    #[allow(clippy::too_many_arguments)]
131    pub fn record_icon(&mut self, rec: &mut GlyphRec, dc: &mut Dc, sf: &mut Surface, cp: u32, x: i32, y: i32, size_fx: i32) -> bool {
132        rec.ok = true;
133        self.rec = Some(core::mem::take(rec));
134        self.icon_draw(dc, sf, cp, x, y, size_fx);
135        self.rec_end(rec)
136    }
137
138    fn rec_end(&mut self, rec: &mut GlyphRec) -> bool {
139        *rec = self.rec.take().unwrap_or_default();
140        rec.ok
141    }
142
143    /// the glyph cache read-only, for blending recorded glyphs on other threads
144    pub fn glyphs(&self) -> GlyphView<'_> {
145        GlyphView { map: &self.gc.map, lut: self.lcd_lut.as_deref() }
146    }
147}
148
149/// the glyph cache read-only (Text::glyphs): blends recorded glyphs, from any thread
150#[derive(Clone, Copy)]
151pub struct GlyphView<'a> {
152    map: &'a alloc::collections::BTreeMap<u64, GcEntry>,
153    lut: Option<&'a Lut>,
154}
155
156impl GlyphView<'_> {
157    /// blend the glyphs from .. to of rec in their colours inside dc's clip (rec.valid must
158    /// hold: a missing mask is skipped)
159    pub fn draw(&self, rec: &GlyphRec, from: usize, to: usize, dc: &mut Dc, sf: &mut Surface) {
160        let Some(ops) = rec.ops.get(from..to) else { return };
161        for op in ops {
162            let c = dc.clip;
163            if op.b[0] >= c[2] || op.b[2] <= c[0] || op.b[1] >= c[3] || op.b[3] <= c[1] {
164                continue;
165            }
166            let Some(e) = self.map.get(&op.key) else { continue };
167            if dc.color != op.color {
168                dc.set_color(op.color);
169            }
170            match op.kind {
171                K_MASK => mask_blit(dc, sf, op.x, op.y, e),
172                K_BITMAP => bitmap_blit(dc, sf, op.x, op.y, e),
173                _ => {
174                    if let (Some(lut), Some(a)) = (self.lut, rec.adj.get(op.adj as usize)) {
175                        lcd::blit(dc, sf, op.x, op.y, e, op.kind == K_LCD_BGR, lut, &a.1);
176                    }
177                }
178            }
179        }
180    }
181}