text.rs

  1use crate::{black, px};
  2
  3use super::{
  4    point, Bounds, FontId, Glyph, Hsla, Pixels, PlatformTextSystem, Point, UnderlineStyle,
  5    WindowContext,
  6};
  7use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
  8use smallvec::SmallVec;
  9use std::{
 10    borrow::Borrow,
 11    collections::HashMap,
 12    hash::{Hash, Hasher},
 13    iter,
 14    sync::Arc,
 15};
 16
 17pub struct TextLayoutCache {
 18    prev_frame: Mutex<HashMap<CacheKeyValue, Arc<LineLayout>>>,
 19    curr_frame: RwLock<HashMap<CacheKeyValue, Arc<LineLayout>>>,
 20    fonts: Arc<dyn PlatformTextSystem>,
 21}
 22
 23#[derive(Clone, Debug, PartialEq, Eq)]
 24pub struct RunStyle {
 25    pub color: Hsla,
 26    pub font_id: FontId,
 27    pub underline: Option<UnderlineStyle>,
 28}
 29
 30impl TextLayoutCache {
 31    pub fn new(fonts: Arc<dyn PlatformTextSystem>) -> Self {
 32        Self {
 33            prev_frame: Mutex::new(HashMap::new()),
 34            curr_frame: RwLock::new(HashMap::new()),
 35            fonts,
 36        }
 37    }
 38
 39    pub fn finish_frame(&self) {
 40        let mut prev_frame = self.prev_frame.lock();
 41        let mut curr_frame = self.curr_frame.write();
 42        std::mem::swap(&mut *prev_frame, &mut *curr_frame);
 43        curr_frame.clear();
 44    }
 45
 46    pub fn layout_str<'a>(
 47        &'a self,
 48        text: &'a str,
 49        font_size: Pixels,
 50        runs: &'a [(usize, RunStyle)],
 51    ) -> Line {
 52        let key = &CacheKeyRef {
 53            text,
 54            font_size,
 55            runs,
 56        } as &dyn CacheKey;
 57        let curr_frame = self.curr_frame.upgradable_read();
 58        if let Some(layout) = curr_frame.get(key) {
 59            return Line::new(layout.clone(), runs);
 60        }
 61
 62        let mut curr_frame = RwLockUpgradableReadGuard::upgrade(curr_frame);
 63        if let Some((key, layout)) = self.prev_frame.lock().remove_entry(key) {
 64            curr_frame.insert(key, layout.clone());
 65            Line::new(layout, runs)
 66        } else {
 67            let layout = Arc::new(self.fonts.layout_line(text, font_size, runs));
 68            let key = CacheKeyValue {
 69                text: text.into(),
 70                font_size,
 71                runs: SmallVec::from(runs),
 72            };
 73            curr_frame.insert(key, layout.clone());
 74            Line::new(layout, runs)
 75        }
 76    }
 77}
 78
 79trait CacheKey {
 80    fn key(&self) -> CacheKeyRef;
 81}
 82
 83impl<'a> PartialEq for (dyn CacheKey + 'a) {
 84    fn eq(&self, other: &dyn CacheKey) -> bool {
 85        self.key() == other.key()
 86    }
 87}
 88
 89impl<'a> Eq for (dyn CacheKey + 'a) {}
 90
 91impl<'a> Hash for (dyn CacheKey + 'a) {
 92    fn hash<H: Hasher>(&self, state: &mut H) {
 93        self.key().hash(state)
 94    }
 95}
 96
 97#[derive(Eq)]
 98struct CacheKeyValue {
 99    text: String,
100    font_size: Pixels,
101    runs: SmallVec<[(usize, RunStyle); 1]>,
102}
103
104impl CacheKey for CacheKeyValue {
105    fn key(&self) -> CacheKeyRef {
106        CacheKeyRef {
107            text: self.text.as_str(),
108            font_size: self.font_size,
109            runs: self.runs.as_slice(),
110        }
111    }
112}
113
114impl PartialEq for CacheKeyValue {
115    fn eq(&self, other: &Self) -> bool {
116        self.key().eq(&other.key())
117    }
118}
119
120impl Hash for CacheKeyValue {
121    fn hash<H: Hasher>(&self, state: &mut H) {
122        self.key().hash(state);
123    }
124}
125
126impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
127    fn borrow(&self) -> &(dyn CacheKey + 'a) {
128        self as &dyn CacheKey
129    }
130}
131
132#[derive(Copy, Clone)]
133struct CacheKeyRef<'a> {
134    text: &'a str,
135    font_size: Pixels,
136    runs: &'a [(usize, RunStyle)],
137}
138
139impl<'a> CacheKey for CacheKeyRef<'a> {
140    fn key(&self) -> CacheKeyRef {
141        *self
142    }
143}
144
145impl<'a> PartialEq for CacheKeyRef<'a> {
146    fn eq(&self, other: &Self) -> bool {
147        self.text == other.text
148            && self.font_size == other.font_size
149            && self.runs.len() == other.runs.len()
150            && self.runs.iter().zip(other.runs.iter()).all(
151                |((len_a, style_a), (len_b, style_b))| {
152                    len_a == len_b && style_a.font_id == style_b.font_id
153                },
154            )
155    }
156}
157
158impl<'a> Hash for CacheKeyRef<'a> {
159    fn hash<H: Hasher>(&self, state: &mut H) {
160        self.text.hash(state);
161        self.font_size.hash(state);
162        for (len, style_id) in self.runs {
163            len.hash(state);
164            style_id.font_id.hash(state);
165        }
166    }
167}
168
169#[derive(Default, Debug, Clone)]
170pub struct Line {
171    layout: Arc<LineLayout>,
172    style_runs: SmallVec<[StyleRun; 32]>,
173}
174
175#[derive(Debug, Clone)]
176struct StyleRun {
177    len: u32,
178    color: Hsla,
179    underline: UnderlineStyle,
180}
181
182#[derive(Default, Debug)]
183pub struct LineLayout {
184    pub font_size: Pixels,
185    pub width: Pixels,
186    pub ascent: Pixels,
187    pub descent: Pixels,
188    pub runs: Vec<Run>,
189    pub len: usize,
190}
191
192#[derive(Debug)]
193pub struct Run {
194    pub font_id: FontId,
195    pub glyphs: Vec<Glyph>,
196}
197
198impl Line {
199    pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
200        let mut style_runs = SmallVec::new();
201        for (len, style) in runs {
202            style_runs.push(StyleRun {
203                len: *len as u32,
204                color: style.color,
205                underline: style.underline.clone().unwrap_or_default(),
206            });
207        }
208        Self { layout, style_runs }
209    }
210
211    pub fn runs(&self) -> &[Run] {
212        &self.layout.runs
213    }
214
215    pub fn width(&self) -> Pixels {
216        self.layout.width
217    }
218
219    pub fn font_size(&self) -> Pixels {
220        self.layout.font_size
221    }
222
223    pub fn x_for_index(&self, index: usize) -> Pixels {
224        for run in &self.layout.runs {
225            for glyph in &run.glyphs {
226                if glyph.index >= index {
227                    return glyph.position.x;
228                }
229            }
230        }
231        self.layout.width
232    }
233
234    pub fn font_for_index(&self, index: usize) -> Option<FontId> {
235        for run in &self.layout.runs {
236            for glyph in &run.glyphs {
237                if glyph.index >= index {
238                    return Some(run.font_id);
239                }
240            }
241        }
242
243        None
244    }
245
246    pub fn len(&self) -> usize {
247        self.layout.len
248    }
249
250    pub fn is_empty(&self) -> bool {
251        self.layout.len == 0
252    }
253
254    pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
255        if x >= self.layout.width {
256            None
257        } else {
258            for run in self.layout.runs.iter().rev() {
259                for glyph in run.glyphs.iter().rev() {
260                    if glyph.position.x <= x {
261                        return Some(glyph.index);
262                    }
263                }
264            }
265            Some(0)
266        }
267    }
268
269    pub fn paint(
270        &self,
271        origin: Point<Pixels>,
272        visible_bounds: Bounds<Pixels>,
273        line_height: Pixels,
274        cx: &mut WindowContext,
275    ) {
276        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
277        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
278
279        let mut style_runs = self.style_runs.iter();
280        let mut run_end = 0;
281        let mut color = black();
282        let mut underline = None;
283
284        for run in &self.layout.runs {
285            let max_glyph_width = cx
286                .font_cache()
287                .bounding_box(run.font_id, self.layout.font_size)
288                .width;
289
290            for glyph in &run.glyphs {
291                let glyph_origin = origin + baseline_offset + glyph.position;
292                if glyph_origin.x > visible_bounds.upper_right().x {
293                    break;
294                }
295
296                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
297                if glyph.index >= run_end {
298                    if let Some(style_run) = style_runs.next() {
299                        if let Some((_, underline_style)) = &mut underline {
300                            if style_run.underline != *underline_style {
301                                finished_underline = underline.take();
302                            }
303                        }
304                        if style_run.underline.thickness > px(0.) {
305                            underline.get_or_insert((
306                                point(
307                                    glyph_origin.x,
308                                    origin.y + baseline_offset.y + (self.layout.descent * 0.618),
309                                ),
310                                UnderlineStyle {
311                                    color: style_run.underline.color,
312                                    thickness: style_run.underline.thickness,
313                                    squiggly: style_run.underline.squiggly,
314                                },
315                            ));
316                        }
317
318                        run_end += style_run.len as usize;
319                        color = style_run.color;
320                    } else {
321                        run_end = self.layout.len;
322                        finished_underline = underline.take();
323                    }
324                }
325
326                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
327                    continue;
328                }
329
330                if let Some((_underline_origin, _underline_style)) = finished_underline {
331                    // cx.scene().insert(Underline {
332                    //     origin: underline_origin,
333                    //     width: glyph_origin.x - underline_origin.x,
334                    //     thickness: underline_style.thickness.into(),
335                    //     color: underline_style.color.unwrap(),
336                    //     squiggly: underline_style.squiggly,
337                    // });
338                }
339
340                // todo!()
341                // if glyph.is_emoji {
342                //     cx.scene().push_image_glyph(scene::ImageGlyph {
343                //         font_id: run.font_id,
344                //         font_size: self.layout.font_size,
345                //         id: glyph.id,
346                //         origin: glyph_origin,
347                //     });
348                // } else {
349                //     cx.scene().push_glyph(scene::Glyph {
350                //         font_id: run.font_id,
351                //         font_size: self.layout.font_size,
352                //         id: glyph.id,
353                //         origin: glyph_origin,
354                //         color,
355                //     });
356                // }
357            }
358        }
359
360        if let Some((_underline_start, _underline_style)) = underline.take() {
361            let _line_end_x = origin.x + self.layout.width;
362            // cx.scene().push_underline(Underline {
363            //     origin: underline_start,
364            //     width: line_end_x - underline_start.x,
365            //     color: underline_style.color,
366            //     thickness: underline_style.thickness.into(),
367            //     squiggly: underline_style.squiggly,
368            // });
369        }
370    }
371
372    pub fn paint_wrapped(
373        &self,
374        origin: Point<Pixels>,
375        _visible_bounds: Bounds<Pixels>,
376        line_height: Pixels,
377        boundaries: &[ShapedBoundary],
378        cx: &mut WindowContext,
379    ) {
380        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
381        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
382
383        let mut boundaries = boundaries.into_iter().peekable();
384        let mut color_runs = self.style_runs.iter();
385        let mut style_run_end = 0;
386        let mut color = black();
387        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
388
389        let mut glyph_origin = origin;
390        let mut prev_position = px(0.);
391        for (run_ix, run) in self.layout.runs.iter().enumerate() {
392            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
393                glyph_origin.x += glyph.position.x - prev_position;
394
395                if boundaries
396                    .peek()
397                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
398                {
399                    boundaries.next();
400                    if let Some((_underline_origin, _underline_style)) = underline.take() {
401                        // cx.scene().push_underline(Underline {
402                        //     origin: underline_origin,
403                        //     width: glyph_origin.x - underline_origin.x,
404                        //     thickness: underline_style.thickness.into(),
405                        //     color: underline_style.color.unwrap(),
406                        //     squiggly: underline_style.squiggly,
407                        // });
408                    }
409
410                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
411                }
412                prev_position = glyph.position.x;
413
414                let mut finished_underline = None;
415                if glyph.index >= style_run_end {
416                    if let Some(style_run) = color_runs.next() {
417                        style_run_end += style_run.len as usize;
418                        color = style_run.color;
419                        if let Some((_, underline_style)) = &mut underline {
420                            if style_run.underline != *underline_style {
421                                finished_underline = underline.take();
422                            }
423                        }
424                        if style_run.underline.thickness > px(0.) {
425                            underline.get_or_insert((
426                                glyph_origin
427                                    + point(
428                                        px(0.),
429                                        baseline_offset.y + (self.layout.descent * 0.618),
430                                    ),
431                                UnderlineStyle {
432                                    color: Some(
433                                        style_run.underline.color.unwrap_or(style_run.color),
434                                    ),
435                                    thickness: style_run.underline.thickness,
436                                    squiggly: style_run.underline.squiggly,
437                                },
438                            ));
439                        }
440                    } else {
441                        style_run_end = self.layout.len;
442                        color = black();
443                        finished_underline = underline.take();
444                    }
445                }
446
447                if let Some((_underline_origin, _underline_style)) = finished_underline {
448                    // cx.scene().push_underline(Underline {
449                    //     origin: underline_origin,
450                    //     width: glyph_origin.x - underline_origin.x,
451                    //     thickness: underline_style.thickness.into(),
452                    //     color: underline_style.color.unwrap(),
453                    //     squiggly: underline_style.squiggly,
454                    // });
455                }
456
457                let _glyph_bounds = Bounds {
458                    origin: glyph_origin,
459                    size: cx
460                        .font_cache()
461                        .bounding_box(run.font_id, self.layout.font_size),
462                };
463                // todo!()
464                // if glyph_bounds.intersects(visible_bounds) {
465                //     if glyph.is_emoji {
466                //         cx.scene().push_image_glyph(scene::ImageGlyph {
467                //             font_id: run.font_id,
468                //             font_size: self.layout.font_size,
469                //             id: glyph.id,
470                //             origin: glyph_bounds.origin() + baseline_offset,
471                //         });
472                //     } else {
473                //         cx.scene().push_glyph(scene::Glyph {
474                //             font_id: run.font_id,
475                //             font_size: self.layout.font_size,
476                //             id: glyph.id,
477                //             origin: glyph_bounds.origin() + baseline_offset,
478                //             color,
479                //         });
480                //     }
481                // }
482            }
483        }
484
485        if let Some((_underline_origin, _underline_style)) = underline.take() {
486            // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
487            // cx.scene().push_underline(Underline {
488            //     origin: underline_origin,
489            //     width: line_end_x - underline_origin.x,
490            //     thickness: underline_style.thickness.into(),
491            //     color: underline_style.color,
492            //     squiggly: underline_style.squiggly,
493            // });
494        }
495    }
496}
497
498impl Run {
499    pub fn glyphs(&self) -> &[Glyph] {
500        &self.glyphs
501    }
502}
503
504#[derive(Copy, Clone, Debug, PartialEq, Eq)]
505pub struct Boundary {
506    pub ix: usize,
507    pub next_indent: u32,
508}
509
510#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
511pub struct ShapedBoundary {
512    pub run_ix: usize,
513    pub glyph_ix: usize,
514}
515
516impl Boundary {
517    fn new(ix: usize, next_indent: u32) -> Self {
518        Self { ix, next_indent }
519    }
520}
521
522pub struct LineWrapper {
523    font_system: Arc<dyn PlatformTextSystem>,
524    pub(crate) font_id: FontId,
525    pub(crate) font_size: Pixels,
526    cached_ascii_char_widths: [Option<Pixels>; 128],
527    cached_other_char_widths: HashMap<char, Pixels>,
528}
529
530impl LineWrapper {
531    pub const MAX_INDENT: u32 = 256;
532
533    pub fn new(
534        font_id: FontId,
535        font_size: Pixels,
536        font_system: Arc<dyn PlatformTextSystem>,
537    ) -> Self {
538        Self {
539            font_system,
540            font_id,
541            font_size,
542            cached_ascii_char_widths: [None; 128],
543            cached_other_char_widths: HashMap::new(),
544        }
545    }
546
547    pub fn wrap_line<'a>(
548        &'a mut self,
549        line: &'a str,
550        wrap_width: Pixels,
551    ) -> impl Iterator<Item = Boundary> + 'a {
552        let mut width = px(0.);
553        let mut first_non_whitespace_ix = None;
554        let mut indent = None;
555        let mut last_candidate_ix = 0;
556        let mut last_candidate_width = px(0.);
557        let mut last_wrap_ix = 0;
558        let mut prev_c = '\0';
559        let mut char_indices = line.char_indices();
560        iter::from_fn(move || {
561            for (ix, c) in char_indices.by_ref() {
562                if c == '\n' {
563                    continue;
564                }
565
566                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
567                    last_candidate_ix = ix;
568                    last_candidate_width = width;
569                }
570
571                if c != ' ' && first_non_whitespace_ix.is_none() {
572                    first_non_whitespace_ix = Some(ix);
573                }
574
575                let char_width = self.width_for_char(c);
576                width += char_width;
577                if width > wrap_width && ix > last_wrap_ix {
578                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
579                    {
580                        indent = Some(
581                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
582                        );
583                    }
584
585                    if last_candidate_ix > 0 {
586                        last_wrap_ix = last_candidate_ix;
587                        width -= last_candidate_width;
588                        last_candidate_ix = 0;
589                    } else {
590                        last_wrap_ix = ix;
591                        width = char_width;
592                    }
593
594                    if let Some(indent) = indent {
595                        width += self.width_for_char(' ') * indent as f32;
596                    }
597
598                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
599                }
600                prev_c = c;
601            }
602
603            None
604        })
605    }
606
607    pub fn wrap_shaped_line<'a>(
608        &'a mut self,
609        str: &'a str,
610        line: &'a Line,
611        wrap_width: Pixels,
612    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
613        let mut first_non_whitespace_ix = None;
614        let mut last_candidate_ix = None;
615        let mut last_candidate_x = px(0.);
616        let mut last_wrap_ix = ShapedBoundary {
617            run_ix: 0,
618            glyph_ix: 0,
619        };
620        let mut last_wrap_x = px(0.);
621        let mut prev_c = '\0';
622        let mut glyphs = line
623            .runs()
624            .iter()
625            .enumerate()
626            .flat_map(move |(run_ix, run)| {
627                run.glyphs()
628                    .iter()
629                    .enumerate()
630                    .map(move |(glyph_ix, glyph)| {
631                        let character = str[glyph.index..].chars().next().unwrap();
632                        (
633                            ShapedBoundary { run_ix, glyph_ix },
634                            character,
635                            glyph.position.x,
636                        )
637                    })
638            })
639            .peekable();
640
641        iter::from_fn(move || {
642            while let Some((ix, c, x)) = glyphs.next() {
643                if c == '\n' {
644                    continue;
645                }
646
647                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
648                    last_candidate_ix = Some(ix);
649                    last_candidate_x = x;
650                }
651
652                if c != ' ' && first_non_whitespace_ix.is_none() {
653                    first_non_whitespace_ix = Some(ix);
654                }
655
656                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
657                let width = next_x - last_wrap_x;
658                if width > wrap_width && ix > last_wrap_ix {
659                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
660                        last_wrap_ix = last_candidate_ix;
661                        last_wrap_x = last_candidate_x;
662                    } else {
663                        last_wrap_ix = ix;
664                        last_wrap_x = x;
665                    }
666
667                    return Some(last_wrap_ix);
668                }
669                prev_c = c;
670            }
671
672            None
673        })
674    }
675
676    fn is_boundary(&self, prev: char, next: char) -> bool {
677        (prev == ' ') && (next != ' ')
678    }
679
680    #[inline(always)]
681    fn width_for_char(&mut self, c: char) -> Pixels {
682        if (c as u32) < 128 {
683            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
684                cached_width
685            } else {
686                let width = self.compute_width_for_char(c);
687                self.cached_ascii_char_widths[c as usize] = Some(width);
688                width
689            }
690        } else {
691            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
692                *cached_width
693            } else {
694                let width = self.compute_width_for_char(c);
695                self.cached_other_char_widths.insert(c, width);
696                width
697            }
698        }
699    }
700
701    fn compute_width_for_char(&self, c: char) -> Pixels {
702        self.font_system
703            .layout_line(
704                &c.to_string(),
705                self.font_size,
706                &[(
707                    1,
708                    RunStyle {
709                        font_id: self.font_id,
710                        color: Default::default(),
711                        underline: Default::default(),
712                    },
713                )],
714            )
715            .width
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722    use crate::{AppContext, FontWeight};
723
724    #[test]
725    fn test_wrap_line() {
726        let cx = AppContext::test();
727
728        let font_cache = cx.font_cache().clone();
729        let font_system = cx.platform().font_system();
730        let family = font_cache
731            .load_family(&["Courier"], &Default::default())
732            .unwrap();
733        let font_id = font_cache
734            .select_font(family, Default::default(), Default::default())
735            .unwrap();
736
737        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
738        assert_eq!(
739            wrapper
740                .wrap_line("aa bbb cccc ddddd eeee", px(72.))
741                .collect::<Vec<_>>(),
742            &[
743                Boundary::new(7, 0),
744                Boundary::new(12, 0),
745                Boundary::new(18, 0)
746            ],
747        );
748        assert_eq!(
749            wrapper
750                .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
751                .collect::<Vec<_>>(),
752            &[
753                Boundary::new(4, 0),
754                Boundary::new(11, 0),
755                Boundary::new(18, 0)
756            ],
757        );
758        assert_eq!(
759            wrapper
760                .wrap_line("     aaaaaaa", px(72.))
761                .collect::<Vec<_>>(),
762            &[
763                Boundary::new(7, 5),
764                Boundary::new(9, 5),
765                Boundary::new(11, 5),
766            ]
767        );
768        assert_eq!(
769            wrapper
770                .wrap_line("                            ", px(72.))
771                .collect::<Vec<_>>(),
772            &[
773                Boundary::new(7, 0),
774                Boundary::new(14, 0),
775                Boundary::new(21, 0)
776            ]
777        );
778        assert_eq!(
779            wrapper
780                .wrap_line("          aaaaaaaaaaaaaa", px(72.))
781                .collect::<Vec<_>>(),
782            &[
783                Boundary::new(7, 0),
784                Boundary::new(14, 3),
785                Boundary::new(18, 3),
786                Boundary::new(22, 3),
787            ]
788        );
789    }
790
791    // todo! repeat this test
792    #[test]
793    fn test_wrap_shaped_line() {
794        let cx = AppContext::test();
795        let font_cache = cx.font_cache().clone();
796        let font_system = cx.platform().font_system();
797        let text_layout_cache = TextLayoutCache::new(font_system.clone());
798
799        let family = font_cache
800            .load_family(&["Helvetica"], &Default::default())
801            .unwrap();
802        let font_id = font_cache
803            .select_font(family, Default::default(), Default::default())
804            .unwrap();
805        let normal = RunStyle {
806            font_id,
807            color: Default::default(),
808            underline: Default::default(),
809        };
810        let bold = RunStyle {
811            font_id: font_cache
812                .select_font(family, FontWeight::BOLD, Default::default())
813                .unwrap(),
814            color: Default::default(),
815            underline: Default::default(),
816        };
817
818        let text = "aa bbb cccc ddddd eeee";
819        let line = text_layout_cache.layout_str(
820            text,
821            px(16.),
822            &[
823                (4, normal.clone()),
824                (5, bold.clone()),
825                (6, normal.clone()),
826                (1, bold),
827                (7, normal),
828            ],
829        );
830
831        let mut wrapper = LineWrapper::new(font_id, px(16.), font_system);
832        assert_eq!(
833            wrapper
834                .wrap_shaped_line(text, &line, px(72.))
835                .collect::<Vec<_>>(),
836            &[
837                ShapedBoundary {
838                    run_ix: 1,
839                    glyph_ix: 3
840                },
841                ShapedBoundary {
842                    run_ix: 2,
843                    glyph_ix: 3
844                },
845                ShapedBoundary {
846                    run_ix: 4,
847                    glyph_ix: 2
848                }
849            ],
850        );
851    }
852}