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