Skip to main content

text/
glyphcache.rs

1// glyphcache -- rasterized glyph masks, cached by (font, glyph, size, x phase)
2//                                                                  [src/text/glyphcache.s]
3//
4//   gc_get(font, glyph, size_fx, phase) -> Entry / None (nothing to draw) / Direct (too
5//        large to cache: draw it with glyph_path + fill).  size_fx: pixel size (em height)
6//        in 24.8; phase: 0..3 quarter pixels.  Entry: left, top (relative to pen x /
7//        baseline y), w, h, w*h coverage bytes.  An empty glyph is an entry of 0 x 0.
8//   gc_get_bitmap(font, glyph, size_fx) -> colour bitmap entry (CBDT fonts): w*h PARGB
9//        pixels; bitmaps larger than GC_BIGBMP bytes are not cached (valid until the next one)
10//   gc_get_lcd(font, glyph, size_fx, phase, vertical) -> the subpixel mask (lcd.rs): w*h*3
11//        coverage bytes in the physical order of the subpixels, one pixel more on each side
12//        across them than gc_get's mask; Direct when too large (draw it greyscale)
13//   gc_box(font, glyph, size_fx, phase) -> {left, top, w, h} as gc_get computes it
14//   gc_flush()   gc_scale(font, size_fx) -> (sx, sy) pixels per font unit (sy snapped)
15//   gc_usage() -> (entries, entry limit, bytes, arena bytes): how full the cache is
16//
17// Hinting-free: for sizes up to 24 px the vertical scale is adjusted so the x-height lands
18// on a whole pixel; horizontally only quarter-pixel phases.  Key: 1<<63 | colour<<62 |
19// font<<40 (22 bits) | mode<<38 (0 grey, 1 subpixel columns, 2 subpixel rows) | phase<<36 |
20// size<<16 (20 bits) | glyph (16 bits).  Like the
21// assembly the cache starts over when it holds GC_SLOTS / 2 entries or its 2 MB arena is
22// full.
23
24use alloc::collections::BTreeMap;
25use alloc::vec::Vec;
26
27use gfx::{Dc, Surface};
28use rt::fx::{ceil_i, cvt, floor_i};
29
30use crate::font::{Gp, SGH_CBDT};
31use crate::registry::Face;
32use crate::Text;
33
34const GC_SLOTS: u32 = 4096;
35const GC_ARENA: usize = 2 * 1024 * 1024;
36const GC_MAXSIDE: i32 = 1024;
37const GC_MAXPIX: i32 = 256 * 1024; // larger masks are drawn directly
38const GC_SIZE_LIMIT: i32 = 1 << 20; // size_fx must fit the 20-bit key field
39const GC_FONT_MASK: u32 = 0x3F_FFFF;
40const GC_BIGBMP: usize = GC_ARENA / 4; // colour bitmaps above this are not cached
41
42/// a cached glyph: placement relative to the pen x / baseline, w x h coverage bytes (masks)
43/// or PARGB pixels (bitmaps)
44#[derive(Clone, Default)]
45pub struct GcEntry {
46    pub left: i32,
47    pub top: i32,
48    pub w: i32,
49    pub h: i32,
50    pub px: Vec<u8>,
51}
52
53#[non_exhaustive]
54pub enum GcRef<'a> {
55    None,
56    Direct,
57    Entry(&'a GcEntry),
58}
59
60#[derive(Default)]
61pub(crate) struct Gc {
62    pub(crate) map: BTreeMap<u64, GcEntry>,
63    bytes: usize,
64    big: Option<GcEntry>,
65    /// set_antialias(false): masks are black and white
66    sharp: bool,
67    /// counts the flushes (recorded glyphs name their masks by key: record.rs)
68    pub(crate) epoch: u32,
69}
70
71impl Gc {
72    // the arena block of an entry (16-byte granules)
73    fn charge(&mut self, n: usize) -> bool {
74        let n = (n + 15) & !15;
75        if self.bytes + n > GC_ARENA {
76            return false;
77        }
78        self.bytes += n;
79        true
80    }
81}
82
83// gc_key(font, glyph, size_fx, phase) (bit 63 set)
84pub(crate) fn gc_key(font: u32, glyph: u32, size: i32, phase: i32) -> u64 {
85    1u64 << 63
86        | ((font & GC_FONT_MASK) as u64) << 40
87        | ((phase & 3) as u64) << 36
88        | ((size as u32 & 0xF_FFFF) as u64) << 16
89        | (glyph & 0xFFFF) as u64
90}
91
92// the key of a subpixel mask (mode 1: columns, 2: rows)
93pub(crate) fn lcd_key(font: u32, glyph: u32, size: i32, phase: i32, vertical: bool) -> u64 {
94    gc_key(font, glyph, size, phase) | (if vertical { 2u64 } else { 1 }) << 38
95}
96
97// the key of a colour bitmap (bit 62)
98pub(crate) fn bitmap_key(font: u32, glyph: u32, size: i32) -> u64 {
99    gc_key(font, glyph, size, 0) | 1 << 62
100}
101
102impl Text {
103    /// gc_flush(): forget every cached glyph
104    pub fn gc_flush(&mut self) {
105        self.gc.map.clear();
106        self.gc.bytes = 0;
107        self.gc.big = None;
108        self.gc.epoch = self.gc.epoch.wrapping_add(1);
109    }
110
111    /// gc_usage() -> (entries, the entries at which it starts over, bytes, its arena's bytes)
112    pub fn gc_usage(&self) -> (usize, usize, usize, usize) {
113        (self.gc.map.len(), (GC_SLOTS / 2) as usize, self.gc.bytes, GC_ARENA)
114    }
115
116    /// gc_scale(font, size_fx) -> (sx, sy): pixels per font unit, sy snapped so the
117    /// x-height is whole up to 24 px
118    pub fn gc_scale(&self, font: u32, size_fx: i32) -> (f32, f32) {
119        let h = self.hdr(font);
120        let sx = (size_fx as f32 * (1.0 / 256.0)) / h.upem as f32;
121        let mut sy = sx;
122        if self.font_raster(font) == crate::FontRaster::Legacy && size_fx <= 24 * 256 && h.xh > 0 {
123            let xh = h.xh as f32;
124            let t = cvt(xh * sx);
125            if t > 0 {
126                sy = t as f32 / xh;
127            }
128        }
129        (sx, sy)
130    }
131
132    // the mask placement of gc_get {left, top, w, h, sx, sy}, or None when empty  [gc_place]
133    fn gc_place(&mut self, font: u32, g: u32, size: i32, phase: i32) -> Option<(i32, i32, i32, i32, f32, f32)> {
134        let (sx, sy) = self.gc_scale(font, size);
135        let b = self.glyph_box(font, g)?;
136        let ph = (phase & 3) as f32 * 0.25;
137        let left = floor_i(b[0] as f32 * sx + ph);
138        let w = ceil_i(b[2] as f32 * sx + ph).wrapping_sub(left);
139        let top = floor_i(b[3].wrapping_neg() as f32 * sy);
140        let h = ceil_i(b[1].wrapping_neg() as f32 * sy).wrapping_sub(top);
141        if w <= 0 || h <= 0 {
142            return None;
143        }
144        Some((left, top, w, h, sx, sy))
145    }
146
147    /// gc_box(font, glyph, size_fx, phase) -> {left, top, w, h} (not cached)
148    pub fn gc_box(&mut self, font: u32, g: u32, size: i32, phase: i32) -> Option<[i32; 4]> {
149        self.gc_place(font, g, size, phase).map(|p| [p.0, p.1, p.2, p.3])
150    }
151
152    /// smoothed glyph masks (true, the default) or black-and-white ones (the system's font
153    /// smoothing off); a change empties the cache
154    pub fn set_antialias(&mut self, on: bool) {
155        if self.gc.sharp == on {
156            self.gc.sharp = !on;
157            self.gc_flush();
158        }
159    }
160
161    /// whether glyph masks are smoothed
162    pub fn antialias(&self) -> bool {
163        !self.gc.sharp
164    }
165
166    /// gc_get(font, glyph, size_fx, phase): the cached mask, rendered on first use
167    pub fn gc_get(&mut self, font: u32, g: u32, size: i32, phase: i32) -> GcRef<'_> {
168        let phase = phase & 3;
169        if size >= GC_SIZE_LIMIT || size < 0 {
170            return GcRef::Direct;
171        }
172        let key = gc_key(font, g, size, phase);
173        if self.gc.map.contains_key(&key) {
174            return GcRef::Entry(&self.gc.map[&key]);
175        }
176        // table more than half full: start over
177        if self.gc.map.len() as u32 >= GC_SLOTS / 2 {
178            self.gc_flush();
179        }
180        // ---- metrics
181        let Some((left, top, w, h, sx, sy)) = self.gc_place(font, g, size, phase) else {
182            self.gc.map.insert(key, GcEntry::default());
183            return GcRef::Entry(&self.gc.map[&key]);
184        };
185        if w > GC_MAXSIDE || h > GC_MAXSIDE || w * h > GC_MAXPIX {
186            return GcRef::Direct;
187        }
188        // ---- allocate the entry (retry once after a flush)
189        if !self.gc.charge((w * h + 8) as usize) {
190            self.gc_flush();
191            if !self.gc.charge((w * h + 8) as usize) {
192                return GcRef::None;
193            }
194        }
195        // rasterize into a fresh A8 surface: origin at (-left + phase, -top), y up
196        let mut sf = Surface::new_a8_packed(w, h);
197        let dc = Dc::new(w, h);
198        self.gp = Gp { x: phase as f32 * 0.25 + left.wrapping_neg() as f32, y: top.wrapping_neg() as f32, sx, sy, dx: 0, dy: 0 };
199        self.path.begin(&dc);
200        self.glyph_path(font, g);
201        self.path.fill(&mut self.ras, &dc, &mut sf, 0);
202        if self.gc.sharp {
203            for c in sf.px.iter_mut() {
204                *c = if *c >= 128 { 255 } else { 0 };
205            }
206        }
207        self.gc.map.insert(key, GcEntry { left, top, w, h, px: sf.px });
208        GcRef::Entry(&self.gc.map[&key])
209    }
210
211    /// gc_get_lcd(font, glyph, size_fx, phase, vertical): the cached subpixel mask (w x h
212    /// pixels of 3 coverage bytes: left to right, or top to bottom when vertical), rendered
213    /// on first use; Direct when it is too large for the cache (draw the glyph greyscale)
214    pub fn gc_get_lcd(&mut self, font: u32, g: u32, size: i32, phase: i32, vertical: bool) -> GcRef<'_> {
215        let phase = phase & 3;
216        if size >= GC_SIZE_LIMIT || size < 0 {
217            return GcRef::Direct;
218        }
219        let key = lcd_key(font, g, size, phase, vertical);
220        if self.gc.map.contains_key(&key) {
221            return GcRef::Entry(&self.gc.map[&key]);
222        }
223        if self.gc.map.len() as u32 >= GC_SLOTS / 2 {
224            self.gc_flush();
225        }
226        let Some((left, top, w, h, sx, sy)) = self.gc_place(font, g, size, phase) else {
227            self.gc.map.insert(key, GcEntry::default());
228            return GcRef::Entry(&self.gc.map[&key]);
229        };
230        // the filter spreads coverage by two subpixels: one pixel more on each side
231        let (left, top, w, h) = if vertical { (left, top - 1, w, h + 2) } else { (left - 1, top, w + 2, h) };
232        if w > GC_MAXSIDE || h > GC_MAXSIDE || w * h * 3 > GC_MAXPIX {
233            return GcRef::Direct;
234        }
235        let n = (w * h * 3 + 8) as usize;
236        if !self.gc.charge(n) {
237            self.gc_flush();
238            if !self.gc.charge(n) {
239                return GcRef::None;
240            }
241        }
242        // rasterize at 3x across the subpixels, then filter (lcd.rs)
243        let (rw, rh) = if vertical { (w, h * 3) } else { (w * 3, h) };
244        let mut sf = Surface::new_a8_packed(rw, rh);
245        let dc = Dc::new(rw, rh);
246        let ph = phase as f32 * 0.25;
247        self.gp = if vertical {
248            Gp { x: ph - left as f32, y: (top * 3).wrapping_neg() as f32, sx, sy: sy * 3.0, dx: 0, dy: 0 }
249        } else {
250            Gp { x: (ph - left as f32) * 3.0, y: top.wrapping_neg() as f32, sx: sx * 3.0, sy, dx: 0, dy: 0 }
251        };
252        self.path.begin(&dc);
253        self.glyph_path(font, g);
254        self.path.fill(&mut self.ras, &dc, &mut sf, 0);
255        let px = crate::lcd::filter(&sf.px, w as usize, h as usize, vertical);
256        self.gc.map.insert(key, GcEntry { left, top, w, h, px });
257        GcRef::Entry(&self.gc.map[&key])
258    }
259
260    /// gc_get_bitmap(font, glyph, size_fx): the PARGB colour bitmap entry or None
261    pub fn gc_get_bitmap(&mut self, font: u32, g: u32, size: i32) -> Option<&GcEntry> {
262        if !(0..GC_SIZE_LIMIT).contains(&size) {
263            return None;
264        }
265        match self.face(font)? {
266            Face::Tt(_) | Face::Syn(_) | Face::Var(_) => {}
267            _ => return None,
268        }
269        if self.hdr(font).flags & SGH_CBDT == 0 {
270            return None;
271        }
272        let key = bitmap_key(font, g, size);
273        if !self.gc.map.contains_key(&key) {
274            if self.gc.map.len() as u32 >= GC_SLOTS / 2 {
275                self.gc_flush();
276            }
277            match self.op_bitmap(font, g, size) {
278                None => {
279                    self.gc.map.insert(key, GcEntry::default());
280                }
281                Some(b) => {
282                    let n = (b.w * b.h * 4 + 8) as usize;
283                    let e = GcEntry { left: b.left, top: b.top, w: b.w, h: b.h, px: b.px };
284                    if n > GC_BIGBMP {
285                        // too big for the arena: a separate block, not entered in the table
286                        self.gc.big = Some(e);
287                        return self.gc.big.as_ref();
288                    }
289                    if !self.gc.charge(n) {
290                        self.gc_flush();
291                        if !self.gc.charge(n) {
292                            return None;
293                        }
294                    }
295                    self.gc.map.insert(key, e);
296                }
297            }
298        }
299        let e = &self.gc.map[&key];
300        if e.w == 0 { None } else { Some(e) }
301    }
302}