text_layout_cache.rs

  1use crate::{
  2    black, point, px, Bounds, FontId, Glyph, Hsla, LineLayout, Pixels, PlatformTextSystem, Point,
  3    Run, RunStyle, UnderlineStyle, WindowContext,
  4};
  5use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
  6use smallvec::SmallVec;
  7use std::{
  8    borrow::Borrow,
  9    collections::HashMap,
 10    hash::{Hash, Hasher},
 11    sync::Arc,
 12};
 13
 14pub(crate) struct TextLayoutCache {
 15    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
 16    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
 17    fonts: Arc<dyn PlatformTextSystem>,
 18}
 19
 20impl TextLayoutCache {
 21    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
 22        Self {
 23            prev_frame: Mutex::new(HashMap::new()),
 24            curr_frame: RwLock::new(HashMap::new()),
 25            fonts,
 26        }
 27    }
 28
 29    pub fn finish_frame(&self) {
 30        let mut prev_frame = self.prev_frame.lock();
 31        let mut curr_frame = self.curr_frame.write();
 32        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
 33        curr_frame.clear();
 34    }
 35
 36    pub fn layout_str<'a>(
 37        &'a self,
 38        text: &'a str,
 39        font_size: Pixels,
 40        runs: &'a [(usize, RunStyle)],
 41    ) -> Line {
 42        let key = &CacheKeyRef {
 43            text,
 44            font_size,
 45            runs,
 46        } as &dyn CacheKey;
 47        let curr_frame = self.curr_frame.upgradable_read();
 48        if let Some(layout) = curr_frame.get(key) {
 49            return Line::new(layout.clone(), runs);
 50        }
 51
 52        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
 53        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
 54            curr_frame.insert(key, layout.clone());
 55            Line::new(layout, runs)
 56        } else {
 57            let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
 58            let key = CacheKeyValue {
 59                text: text.into(),
 60                font_size,
 61                runs: SmallVec::from(runs),
 62            };
 63            curr_frame.insert(key, layout.clone());
 64            Line::new(layout, runs)
 65        }
 66    }
 67}
 68
 69trait CacheKey {
 70    fn key(&self) -> CacheKeyRef;
 71}
 72
 73impl<'a> PartialEq for (dyn CacheKey + 'a) {
 74    fn eq(&self, other: &dyn CacheKey) -> bool {
 75        self.key() == other.key()
 76    }
 77}
 78
 79impl<'a> Eq for (dyn CacheKey + 'a) {}
 80
 81impl<'a> Hash for (dyn CacheKey + 'a) {
 82    fn hash<H: Hasher>(&self, state: &mut H) {
 83        self.key().hash(state)
 84    }
 85}
 86
 87#[derive(Eq)]
 88struct CacheKeyValue {
 89    text: String,
 90    font_size: Pixels,
 91    runs: SmallVec<[(usize, RunStyle); 1]>,
 92}
 93
 94impl CacheKey for CacheKeyValue {
 95    fn key(&self) -> CacheKeyRef {
 96        CacheKeyRef {
 97            text: self.text.as_str(),
 98            font_size: self.font_size,
 99            runs: self.runs.as_slice(),
100        }
101    }
102}
103
104impl PartialEq for CacheKeyValue {
105    fn eq(&self, other: &Self) -> bool {
106        self.key().eq(&other.key())
107    }
108}
109
110impl Hash for CacheKeyValue {
111    fn hash<H: Hasher>(&self, state: &mut H) {
112        self.key().hash(state);
113    }
114}
115
116impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
117    fn borrow(&self) -> &(dyn CacheKey + 'a) {
118        self as &dyn CacheKey
119    }
120}
121
122#[derive(Copy, Clone)]
123struct CacheKeyRef<'a> {
124    text: &'a str,
125    font_size: Pixels,
126    runs: &'a [(usize, RunStyle)],
127}
128
129impl<'a> CacheKey for CacheKeyRef<'a> {
130    fn key(&self) -> CacheKeyRef {
131        *self
132    }
133}
134
135impl<'a> PartialEq for CacheKeyRef<'a> {
136    fn eq(&self, other: &Self) -> bool {
137        self.text == other.text
138            && self.font_size == other.font_size
139            && self.runs.len() == other.runs.len()
140            && self.runs.iter().zip(other.runs.iter()).all(
141                |((len_a, style_a), (len_b, style_b))| {
142                    len_a == len_b && style_a.font == style_b.font
143                },
144            )
145    }
146}
147
148impl<'a> Hash for CacheKeyRef<'a> {
149    fn hash<H: Hasher>(&self, state: &mut H) {
150        self.text.hash(state);
151        self.font_size.hash(state);
152        for (len, style_id) in self.runs {
153            len.hash(state);
154            style_id.font.hash(state);
155        }
156    }
157}
158
159#[derive(Default, Debug, Clone)]
160pub struct Line {
161    layout: Arc<LineLayout>,
162    style_runs: SmallVec<[StyleRun; 32]>,
163}
164
165#[derive(Debug, Clone)]
166struct StyleRun {
167    len: u32,
168    color: Hsla,
169    underline: UnderlineStyle,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
173pub struct ShapedBoundary {
174    pub run_ix: usize,
175    pub glyph_ix: usize,
176}
177
178impl Line {
179    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
180        let mut style_runs = SmallVec::new();
181        for (len, style) in runs {
182            style_runs.push(StyleRun {
183                len: *len as u32,
184                color: style.color,
185                underline: style.underline.clone().unwrap_or_default(),
186            });
187        }
188        Self { layout, style_runs }
189    }
190
191    pub fn runs(&self) -> &[Run] {
192        &self.layout.runs
193    }
194
195    pub fn width(&self) -> Pixels {
196        self.layout.width
197    }
198
199    pub fn font_size(&self) -> Pixels {
200        self.layout.font_size
201    }
202
203    pub fn x_for_index(&self, index: usize) -> Pixels {
204        for run in &self.layout.runs {
205            for glyph in &run.glyphs {
206                if glyph.index >= index {
207                    return glyph.position.x;
208                }
209            }
210        }
211        self.layout.width
212    }
213
214    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
215        for run in &self.layout.runs {
216            for glyph in &run.glyphs {
217                if glyph.index >= index {
218                    return Some(run.font_id);
219                }
220            }
221        }
222
223        None
224    }
225
226    pub fn len(&self) -> usize {
227        self.layout.len
228    }
229
230    pub fn is_empty(&self) -> bool {
231        self.layout.len == 0
232    }
233
234    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
235        if x >= self.layout.width {
236            None
237        } else {
238            for run in self.layout.runs.iter().rev() {
239                for glyph in run.glyphs.iter().rev() {
240                    if glyph.position.x <= x {
241                        return Some(glyph.index);
242                    }
243                }
244            }
245            Some(0)
246        }
247    }
248
249    pub fn paint(
250        &self,
251        origin: Point<Pixels>,
252        visible_bounds: Bounds<Pixels>,
253        line_height: Pixels,
254        cx: &mut WindowContext,
255    ) {
256        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
257        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
258
259        let mut style_runs = self.style_runs.iter();
260        let mut run_end = 0;
261        let mut color = black();
262        let mut underline = None;
263
264        for run in &self.layout.runs {
265            let max_glyph_width = cx
266                .text_system()
267                .bounding_box(run.font_id, self.layout.font_size)
268                .width;
269
270            for glyph in &run.glyphs {
271                let glyph_origin = origin + baseline_offset + glyph.position;
272                if glyph_origin.x > visible_bounds.upper_right().x {
273                    break;
274                }
275
276                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
277                if glyph.index >= run_end {
278                    if let Some(style_run) = style_runs.next() {
279                        if let Some((_, underline_style)) = &mut underline {
280                            if style_run.underline != *underline_style {
281                                finished_underline = underline.take();
282                            }
283                        }
284                        if style_run.underline.thickness > px(0.) {
285                            underline.get_or_insert((
286                                point(
287                                    glyph_origin.x,
288                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
289                                ),
290                                UnderlineStyle {
291                                    color: style_run.underline.color,
292                                    thickness: style_run.underline.thickness,
293                                    squiggly: style_run.underline.squiggly,
294                                },
295                            ));
296                        }
297
298                        run_end += style_run.len as usize;
299                        color = style_run.color;
300                    } else {
301                        run_end = self.layout.len;
302                        finished_underline = underline.take();
303                    }
304                }
305
306                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
307                    continue;
308                }
309
310                if let Some((_underline_origin, _underline_style)) = finished_underline {
311                    // cx.scene().insert(Underline {
312                    //     origin: underline_origin,
313                    //     width: glyph_origin.x - underline_origin.x,
314                    //     thickness: underline_style.thickness.into(),
315                    //     color: underline_style.color.unwrap(),
316                    //     squiggly: underline_style.squiggly,
317                    // });
318                }
319
320                // todo!()
321                // if glyph.is_emoji {
322                //     cx.scene().push_image_glyph(scene::ImageGlyph {
323                //         font_id: run.font_id,
324                //         font_size: self.layout.font_size,
325                //         id: glyph.id,
326                //         origin: glyph_origin,
327                //     });
328                // } else {
329                //     cx.scene().push_glyph(scene::Glyph {
330                //         font_id: run.font_id,
331                //         font_size: self.layout.font_size,
332                //         id: glyph.id,
333                //         origin: glyph_origin,
334                //         color,
335                //     });
336                // }
337            }
338        }
339
340        if let Some((_underline_start, _underline_style)) = underline.take() {
341            let _line_end_x = origin.x + self.layout.width;
342            // cx.scene().push_underline(Underline {
343            //     origin: underline_start,
344            //     width: line_end_x - underline_start.x,
345            //     color: underline_style.color,
346            //     thickness: underline_style.thickness.into(),
347            //     squiggly: underline_style.squiggly,
348            // });
349        }
350    }
351
352    pub fn paint_wrapped(
353        &self,
354        origin: Point<Pixels>,
355        _visible_bounds: Bounds<Pixels>,
356        line_height: Pixels,
357        boundaries: &[ShapedBoundary],
358        cx: &mut WindowContext,
359    ) {
360        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
361        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
362
363        let mut boundaries = boundaries.into_iter().peekable();
364        let mut color_runs = self.style_runs.iter();
365        let mut style_run_end = 0;
366        let mut color = black();
367        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
368
369        let mut glyph_origin = origin;
370        let mut prev_position = px(0.);
371        for (run_ix, run) in self.layout.runs.iter().enumerate() {
372            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
373                glyph_origin.x += glyph.position.x - prev_position;
374
375                if boundaries
376                    .peek()
377                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
378                {
379                    boundaries.next();
380                    if let Some((_underline_origin, _underline_style)) = underline.take() {
381                        // cx.scene().push_underline(Underline {
382                        //     origin: underline_origin,
383                        //     width: glyph_origin.x - underline_origin.x,
384                        //     thickness: underline_style.thickness.into(),
385                        //     color: underline_style.color.unwrap(),
386                        //     squiggly: underline_style.squiggly,
387                        // });
388                    }
389
390                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
391                }
392                prev_position = glyph.position.x;
393
394                let mut finished_underline = None;
395                if glyph.index >= style_run_end {
396                    if let Some(style_run) = color_runs.next() {
397                        style_run_end += style_run.len as usize;
398                        color = style_run.color;
399                        if let Some((_, underline_style)) = &mut underline {
400                            if style_run.underline != *underline_style {
401                                finished_underline = underline.take();
402                            }
403                        }
404                        if style_run.underline.thickness > px(0.) {
405                            underline.get_or_insert((
406                                glyph_origin
407                                    + point(
408                                        px(0.),
409                                        baseline_offset.y + (self.layout.descent * 0.618),
410                                    ),
411                                UnderlineStyle {
412                                    color: Some(
413                                        style_run.underline.color.unwrap_or(style_run.color),
414                                    ),
415                                    thickness: style_run.underline.thickness,
416                                    squiggly: style_run.underline.squiggly,
417                                },
418                            ));
419                        }
420                    } else {
421                        style_run_end = self.layout.len;
422                        color = black();
423                        finished_underline = underline.take();
424                    }
425                }
426
427                if let Some((_underline_origin, _underline_style)) = finished_underline {
428                    // cx.scene().push_underline(Underline {
429                    //     origin: underline_origin,
430                    //     width: glyph_origin.x - underline_origin.x,
431                    //     thickness: underline_style.thickness.into(),
432                    //     color: underline_style.color.unwrap(),
433                    //     squiggly: underline_style.squiggly,
434                    // });
435                }
436
437                let _glyph_bounds = Bounds {
438                    origin: glyph_origin,
439                    size: cx
440                        .text_system()
441                        .bounding_box(run.font_id, self.layout.font_size),
442                };
443                // todo!()
444                // if glyph_bounds.intersects(visible_bounds) {
445                //     if glyph.is_emoji {
446                //         cx.scene().push_image_glyph(scene::ImageGlyph {
447                //             font_id: run.font_id,
448                //             font_size: self.layout.font_size,
449                //             id: glyph.id,
450                //             origin: glyph_bounds.origin() + baseline_offset,
451                //         });
452                //     } else {
453                //         cx.scene().push_glyph(scene::Glyph {
454                //             font_id: run.font_id,
455                //             font_size: self.layout.font_size,
456                //             id: glyph.id,
457                //             origin: glyph_bounds.origin() + baseline_offset,
458                //             color,
459                //         });
460                //     }
461                // }
462            }
463        }
464
465        if let Some((_underline_origin, _underline_style)) = underline.take() {
466            // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
467            // cx.scene().push_underline(Underline {
468            //     origin: underline_origin,
469            //     width: line_end_x - underline_origin.x,
470            //     thickness: underline_style.thickness.into(),
471            //     color: underline_style.color,
472            //     squiggly: underline_style.squiggly,
473            // });
474        }
475    }
476}
477
478impl Run {
479    pub fn glyphs(&self) -> &[Glyph] {
480        &self.glyphs
481    }
482}