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