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