1#[path = "paragraph_cache.rs"]
2mod paragraph_cache;
3
4use crate::{FontId, GlyphId, Pixels, PlatformTextSystem, Point, SharedString, Size, point, px};
5use collections::{FxHashMap, FxHashSet};
6use itertools::Itertools;
7use smallvec::SmallVec;
8use std::{
9 borrow::Borrow,
10 cell::RefCell,
11 hash::{Hash, Hasher},
12 ops::Range,
13 sync::{
14 Arc,
15 atomic::{AtomicUsize, Ordering},
16 },
17};
18
19use super::LineWrapper;
20
21#[derive(Default, Debug)]
23pub struct LineLayout {
24 pub font_size: Pixels,
26 pub width: Pixels,
28 pub ascent: Pixels,
30 pub descent: Pixels,
32 pub runs: Vec<ShapedRun>,
34 pub len: usize,
36}
37
38#[derive(Debug, Clone)]
40pub struct ShapedRun {
41 pub font_id: FontId,
43 pub glyphs: Vec<ShapedGlyph>,
45}
46
47#[derive(Clone, Debug)]
49pub struct ShapedGlyph {
50 pub id: GlyphId,
52
53 pub position: Point<Pixels>,
55
56 pub index: usize,
58
59 pub is_emoji: bool,
61}
62
63impl LineLayout {
64 pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
66 if x >= self.width {
67 None
68 } else {
69 for run in self.runs.iter().rev() {
70 for glyph in run.glyphs.iter().rev() {
71 if glyph.position.x <= x {
72 return Some(glyph.index);
73 }
74 }
75 }
76 Some(0)
77 }
78 }
79
80 pub fn closest_index_for_x(&self, x: Pixels) -> usize {
83 let mut prev_index = 0;
84 let mut prev_x = px(0.);
85
86 for run in self.runs.iter() {
87 for glyph in run.glyphs.iter() {
88 if glyph.position.x >= x {
89 if glyph.position.x - x < x - prev_x {
90 return glyph.index;
91 } else {
92 return prev_index;
93 }
94 }
95 prev_index = glyph.index;
96 prev_x = glyph.position.x;
97 }
98 }
99
100 if self.len == 1 {
101 if x > self.width / 2. {
102 return 1;
103 } else {
104 return 0;
105 }
106 }
107
108 self.len
109 }
110
111 pub fn x_for_index(&self, index: usize) -> Pixels {
113 for run in &self.runs {
114 for glyph in &run.glyphs {
115 if glyph.index >= index {
116 return glyph.position.x;
117 }
118 }
119 }
120 self.width
121 }
122
123 pub fn font_id_for_index(&self, index: usize) -> Option<FontId> {
125 for run in &self.runs {
126 for glyph in &run.glyphs {
127 if glyph.index >= index {
128 return Some(run.font_id);
129 }
130 }
131 }
132 None
133 }
134
135 pub fn split_at(&self, byte_index: usize) -> (LineLayout, LineLayout) {
143 let x_offset = self.x_for_index(byte_index);
144
145 let mut left_runs = Vec::new();
147 let mut right_runs = Vec::new();
148
149 for run in &self.runs {
150 let split_pos = run.glyphs.partition_point(|g| g.index < byte_index);
151
152 if split_pos > 0 {
153 left_runs.push(ShapedRun {
154 font_id: run.font_id,
155 glyphs: run.glyphs[..split_pos].to_vec(),
156 });
157 }
158
159 if split_pos < run.glyphs.len() {
160 let right_glyphs = run.glyphs[split_pos..]
161 .iter()
162 .map(|g| ShapedGlyph {
163 id: g.id,
164 position: point(g.position.x - x_offset, g.position.y),
165 index: g.index - byte_index,
166 is_emoji: g.is_emoji,
167 })
168 .collect();
169 right_runs.push(ShapedRun {
170 font_id: run.font_id,
171 glyphs: right_glyphs,
172 });
173 }
174 }
175
176 let left = LineLayout {
177 font_size: self.font_size,
178 width: x_offset,
179 ascent: self.ascent,
180 descent: self.descent,
181 runs: left_runs,
182 len: byte_index,
183 };
184
185 let right = LineLayout {
186 font_size: self.font_size,
187 width: self.width - x_offset,
188 ascent: self.ascent,
189 descent: self.descent,
190 runs: right_runs,
191 len: self.len - byte_index,
192 };
193
194 (left, right)
195 }
196
197 fn compute_wrap_boundaries(
198 &self,
199 text: &str,
200 wrap_width: Pixels,
201 max_lines: Option<usize>,
202 scratch: &mut WrapScratch,
203 ) -> SmallVec<[WrapBoundary; 1]> {
204 let mut boundaries = SmallVec::new();
205 let mut current_segment: Option<Range<usize>> = None;
206 let mut last_candidate_ix = None;
207 let mut last_candidate_x = px(0.);
208 let mut last_boundary = WrapBoundary {
209 run_ix: 0,
210 glyph_ix: 0,
211 };
212 let mut last_boundary_x = px(0.);
213 let WrapScratch {
216 glyph_indices,
217 break_indices,
218 } = scratch;
219 glyph_indices.clear();
220 glyph_indices.extend(
221 self.runs
222 .iter()
223 .flat_map(|run| run.glyphs.iter().map(|glyph| glyph.index)),
224 );
225 break_indices.clear();
226 break_indices
227 .extend(LineWrapper::break_indices(text).filter(|index| glyph_indices.contains(index)));
228 let mut glyphs = self
229 .runs
230 .iter()
231 .enumerate()
232 .flat_map(move |(run_ix, run)| {
233 run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
234 (
235 WrapBoundary { run_ix, glyph_ix },
236 glyph.index,
237 glyph.position.x,
238 )
239 })
240 })
241 .dedup_by(|(_, previous_index, ..), (_, index, ..)| previous_index == index)
242 .peekable();
243
244 while let Some((boundary, index, x)) = glyphs.next() {
245 let byte = text.as_bytes()[index];
246 if byte == b'\n' {
247 continue;
248 }
249
250 if byte != b' '
251 && current_segment
252 .as_ref()
253 .is_none_or(|segment| !segment.contains(&index))
254 {
255 let segment = break_indices.partition_point(|break_index| *break_index <= index);
259 if current_segment.is_some() {
260 last_candidate_ix = Some(boundary);
261 last_candidate_x = x;
262 }
263 let start = segment
264 .checked_sub(1)
265 .and_then(|previous| break_indices.get(previous))
266 .copied()
267 .unwrap_or(0);
268 let end = break_indices.get(segment).copied().unwrap_or(text.len());
269 current_segment = Some(start..end);
270 }
271
272 let next_x = glyphs.peek().map_or(self.width, |(_, _, x)| *x);
273 let width = next_x - last_boundary_x;
274
275 if width > wrap_width && boundary > last_boundary {
276 if let Some(max_lines) = max_lines
278 && boundaries.len() >= max_lines.saturating_sub(1)
279 {
280 break;
281 }
282
283 if let Some(last_candidate_ix) = last_candidate_ix.take() {
284 last_boundary = last_candidate_ix;
285 last_boundary_x = last_candidate_x;
286 } else {
287 last_boundary = boundary;
288 last_boundary_x = x;
289 }
290 boundaries.push(last_boundary);
291 }
292 }
293
294 boundaries
295 }
296}
297
298#[derive(Default, Debug)]
300pub struct WrappedLineLayout {
301 pub unwrapped_layout: Arc<LineLayout>,
303
304 pub wrap_boundaries: SmallVec<[WrapBoundary; 1]>,
306
307 pub wrap_width: Option<Pixels>,
309}
310
311#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
313pub struct WrapBoundary {
314 pub run_ix: usize,
316 pub glyph_ix: usize,
318}
319
320impl WrappedLineLayout {
321 #[allow(clippy::len_without_is_empty)]
323 pub fn len(&self) -> usize {
324 self.unwrapped_layout.len
325 }
326
327 pub fn width(&self) -> Pixels {
329 self.wrap_width
330 .unwrap_or(Pixels::MAX)
331 .min(self.unwrapped_layout.width)
332 }
333
334 pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
337 Size {
338 width: self.width(),
339 height: line_height * (self.wrap_boundaries.len() + 1),
340 }
341 }
342
343 pub fn ascent(&self) -> Pixels {
345 self.unwrapped_layout.ascent
346 }
347
348 pub fn descent(&self) -> Pixels {
350 self.unwrapped_layout.descent
351 }
352
353 pub fn wrap_boundaries(&self) -> &[WrapBoundary] {
355 &self.wrap_boundaries
356 }
357
358 pub fn font_size(&self) -> Pixels {
360 self.unwrapped_layout.font_size
361 }
362
363 pub fn runs(&self) -> &[ShapedRun] {
365 &self.unwrapped_layout.runs
366 }
367
368 pub fn index_for_position(
372 &self,
373 position: Point<Pixels>,
374 line_height: Pixels,
375 ) -> Result<usize, usize> {
376 self._index_for_position(position, line_height, false)
377 }
378
379 pub fn closest_index_for_position(
385 &self,
386 position: Point<Pixels>,
387 line_height: Pixels,
388 ) -> Result<usize, usize> {
389 self._index_for_position(position, line_height, true)
390 }
391
392 fn _index_for_position(
393 &self,
394 position: Point<Pixels>,
395 line_height: Pixels,
396 closest: bool,
397 ) -> Result<usize, usize> {
398 let wrapped_line_ix = (position.y / line_height) as usize;
399
400 let wrapped_line_start_index;
401 let wrapped_line_start_x;
402 if wrapped_line_ix > 0 {
403 let Some(line_start_boundary) = self.wrap_boundaries.get(wrapped_line_ix - 1) else {
404 return Err(0);
405 };
406 let run = &self.unwrapped_layout.runs[line_start_boundary.run_ix];
407 let glyph = &run.glyphs[line_start_boundary.glyph_ix];
408 wrapped_line_start_index = glyph.index;
409 wrapped_line_start_x = glyph.position.x;
410 } else {
411 wrapped_line_start_index = 0;
412 wrapped_line_start_x = Pixels::ZERO;
413 };
414
415 let wrapped_line_end_index;
416 let wrapped_line_end_x;
417 if wrapped_line_ix < self.wrap_boundaries.len() {
418 let next_wrap_boundary_ix = wrapped_line_ix;
419 let next_wrap_boundary = self.wrap_boundaries[next_wrap_boundary_ix];
420 let run = &self.unwrapped_layout.runs[next_wrap_boundary.run_ix];
421 let glyph = &run.glyphs[next_wrap_boundary.glyph_ix];
422 wrapped_line_end_index = glyph.index;
423 wrapped_line_end_x = glyph.position.x;
424 } else {
425 wrapped_line_end_index = self.unwrapped_layout.len;
426 wrapped_line_end_x = self.unwrapped_layout.width;
427 };
428
429 let mut position_in_unwrapped_line = position;
430 position_in_unwrapped_line.x += wrapped_line_start_x;
431 if position_in_unwrapped_line.x < wrapped_line_start_x {
432 Err(wrapped_line_start_index)
433 } else if position_in_unwrapped_line.x >= wrapped_line_end_x {
434 Err(wrapped_line_end_index)
435 } else {
436 if closest {
437 Ok(self
438 .unwrapped_layout
439 .closest_index_for_x(position_in_unwrapped_line.x))
440 } else {
441 self.unwrapped_layout
444 .index_for_x(position_in_unwrapped_line.x)
445 .ok_or(wrapped_line_end_index)
446 }
447 }
448 }
449
450 pub fn position_for_index(&self, index: usize, line_height: Pixels) -> Option<Point<Pixels>> {
452 let mut line_start_ix = 0;
453 let line_end_indices = self
454 .wrap_boundaries
455 .iter()
456 .map(|wrap_boundary| {
457 let run = &self.unwrapped_layout.runs[wrap_boundary.run_ix];
458 let glyph = &run.glyphs[wrap_boundary.glyph_ix];
459 glyph.index
460 })
461 .chain([self.len()])
462 .enumerate();
463 for (ix, line_end_ix) in line_end_indices {
464 let line_y = ix as f32 * line_height;
465 if index < line_start_ix {
466 break;
467 } else if index > line_end_ix {
468 line_start_ix = line_end_ix;
469 continue;
470 } else {
471 let line_start_x = self.unwrapped_layout.x_for_index(line_start_ix);
472 let x = self.unwrapped_layout.x_for_index(index) - line_start_x;
473 return Some(point(x, line_y));
474 }
475 }
476
477 None
478 }
479}
480
481pub(crate) struct LineLayoutCache {
482 state: RefCell<LineLayoutCacheState>,
483 wrap_scratch: RefCell<WrapScratch>,
484 platform_text_system: Arc<dyn PlatformTextSystem>,
485 font_generation: Arc<AtomicUsize>,
487 cached_font_generation: AtomicUsize,
489}
490
491#[derive(Default)]
492struct WrapScratch {
493 glyph_indices: FxHashSet<usize>,
494 break_indices: Vec<usize>,
495}
496
497#[derive(Default)]
498struct LineLayoutCacheState {
499 previous_frame: FrameCache,
500 current_frame: FrameCache,
501}
502
503#[derive(Default)]
504struct FrameCache {
505 paragraphs: paragraph_cache::ParagraphFrameCache,
506 lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
507 wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
508 used_lines: Vec<Arc<CacheKey>>,
509 used_wrapped_lines: Vec<Arc<CacheKey>>,
510}
511
512#[derive(Clone, Default)]
513pub(crate) struct LineLayoutIndex {
514 font_generation: usize,
515 paragraphs: paragraph_cache::ParagraphIndex,
516 lines_index: usize,
517 wrapped_lines_index: usize,
518}
519
520impl LineLayoutCache {
521 pub fn new(
522 platform_text_system: Arc<dyn PlatformTextSystem>,
523 font_generation: Arc<AtomicUsize>,
524 ) -> Self {
525 let cached_font_generation = font_generation.load(Ordering::Acquire);
526 Self {
527 state: RefCell::default(),
528 wrap_scratch: RefCell::default(),
529 platform_text_system,
530 font_generation,
531 cached_font_generation: AtomicUsize::new(cached_font_generation),
532 }
533 }
534
535 pub fn layout_index(&self) -> LineLayoutIndex {
536 let font_generation = self.clear_if_font_generation_changed();
537 let state = self.state.borrow();
538 let frame = &state.current_frame;
539 LineLayoutIndex {
540 font_generation,
541 paragraphs: frame.paragraphs.index(),
542 lines_index: frame.used_lines.len(),
543 wrapped_lines_index: frame.used_wrapped_lines.len(),
544 }
545 }
546
547 pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
548 let font_generation = self.clear_if_font_generation_changed();
549 if range.start.font_generation != font_generation
550 || range.end.font_generation != font_generation
551 {
552 return;
553 }
554 let mut state = self.state.borrow_mut();
555 let LineLayoutCacheState {
556 previous_frame,
557 current_frame,
558 } = &mut *state;
559
560 current_frame.paragraphs.reuse_from(
561 &mut previous_frame.paragraphs,
562 range.start.paragraphs..range.end.paragraphs,
563 );
564
565 for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
566 if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
567 current_frame.lines.insert(key, line);
568 }
569 current_frame.used_lines.push(key.clone());
570 }
571
572 for key in &previous_frame.used_wrapped_lines
573 [range.start.wrapped_lines_index..range.end.wrapped_lines_index]
574 {
575 if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
576 current_frame.wrapped_lines.insert(key, line);
577 }
578 current_frame.used_wrapped_lines.push(key.clone());
579 }
580 }
581
582 pub fn truncate_layouts(&self, index: LineLayoutIndex) {
583 let font_generation = self.clear_if_font_generation_changed();
584 if index.font_generation != font_generation {
585 return;
586 }
587 let mut state = self.state.borrow_mut();
588 let current_frame = &mut state.current_frame;
589 current_frame.paragraphs.truncate(index.paragraphs);
590 current_frame.used_lines.truncate(index.lines_index);
591 current_frame
592 .used_wrapped_lines
593 .truncate(index.wrapped_lines_index);
594 }
595
596 pub fn finish_frame(&self) {
597 let _font_generation = self.clear_if_font_generation_changed();
598 let LineLayoutCacheState {
599 previous_frame,
600 current_frame,
601 } = &mut *self.state.borrow_mut();
602 std::mem::swap(previous_frame, current_frame);
603 current_frame.lines.clear();
604 current_frame.wrapped_lines.clear();
605 current_frame.used_lines.clear();
606 current_frame.used_wrapped_lines.clear();
607 current_frame.paragraphs.clear();
608 }
609
610 pub fn layout_wrapped_line<Text>(
611 &self,
612 text: Text,
613 font_size: Pixels,
614 runs: &[FontRun],
615 wrap_width: Option<Pixels>,
616 max_lines: Option<usize>,
617 ) -> Arc<WrappedLineLayout>
618 where
619 Text: AsRef<str>,
620 SharedString: From<Text>,
621 {
622 let _font_generation = self.clear_if_font_generation_changed();
623 let key = &CacheKeyRef {
624 text: text.as_ref(),
625 font_size,
626 runs,
627 wrap_width,
628 force_width: None,
629 } as &dyn AsCacheKeyRef;
630
631 let state = self.state.borrow();
632 if let Some(layout) = state.current_frame.wrapped_lines.get(key) {
633 return layout.clone();
634 }
635 drop(state);
636
637 let mut state = self.state.borrow_mut();
638 let previous_frame_entry = state.previous_frame.wrapped_lines.remove_entry(key);
639 if let Some((key, layout)) = previous_frame_entry {
640 state
641 .current_frame
642 .wrapped_lines
643 .insert(key.clone(), layout.clone());
644 state.current_frame.used_wrapped_lines.push(key);
645 layout
646 } else {
647 drop(state);
648 let text = SharedString::from(text);
649 let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs, None);
650 let wrap_boundaries = if let Some(wrap_width) = wrap_width {
651 unwrapped_layout.compute_wrap_boundaries(
652 text.as_ref(),
653 wrap_width,
654 max_lines,
655 &mut self.wrap_scratch.borrow_mut(),
656 )
657 } else {
658 SmallVec::new()
659 };
660 let layout = Arc::new(WrappedLineLayout {
661 unwrapped_layout,
662 wrap_boundaries,
663 wrap_width,
664 });
665 let key = Arc::new(CacheKey {
666 text,
667 font_size,
668 runs: SmallVec::from(runs),
669 wrap_width,
670 force_width: None,
671 });
672
673 let mut state = self.state.borrow_mut();
674 state
675 .current_frame
676 .wrapped_lines
677 .insert(key.clone(), layout.clone());
678 state.current_frame.used_wrapped_lines.push(key);
679
680 layout
681 }
682 }
683
684 pub fn layout_line<Text>(
685 &self,
686 text: Text,
687 font_size: Pixels,
688 runs: &[FontRun],
689 force_width: Option<Pixels>,
690 ) -> Arc<LineLayout>
691 where
692 Text: AsRef<str>,
693 SharedString: From<Text>,
694 {
695 let _font_generation = self.clear_if_font_generation_changed();
696 let key = &CacheKeyRef {
697 text: text.as_ref(),
698 font_size,
699 runs,
700 wrap_width: None,
701 force_width,
702 } as &dyn AsCacheKeyRef;
703
704 let state = self.state.borrow();
705 if let Some(layout) = state.current_frame.lines.get(key) {
706 return layout.clone();
707 }
708 drop(state);
709
710 let mut state = self.state.borrow_mut();
711 let LineLayoutCacheState {
712 previous_frame,
713 current_frame,
714 } = &mut *state;
715 if let Some((key, layout)) = previous_frame.lines.remove_entry(key) {
716 current_frame.lines.insert(key.clone(), layout.clone());
717 current_frame.used_lines.push(key);
718 layout
719 } else {
720 let text = SharedString::from(text);
721 let mut layout = self
722 .platform_text_system
723 .layout_line(&text, font_size, runs);
724
725 if let Some(force_width) = force_width {
726 apply_force_width_to_layout(&mut layout, force_width);
727 }
728
729 let key = Arc::new(CacheKey {
730 text,
731 font_size,
732 runs: SmallVec::from(runs),
733 wrap_width: None,
734 force_width,
735 });
736 let layout = Arc::new(layout);
737 current_frame.lines.insert(key.clone(), layout.clone());
738 current_frame.used_lines.push(key);
739 layout
740 }
741 }
742
743 fn clear_if_font_generation_changed(&self) -> usize {
744 let font_generation = self.font_generation.load(Ordering::Acquire);
745 if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
746 return font_generation;
747 }
748
749 let mut state = self.state.borrow_mut();
750 if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
751 return font_generation;
752 }
753
754 *state = LineLayoutCacheState::default();
755 self.cached_font_generation
756 .store(font_generation, Ordering::Release);
757 font_generation
758 }
759}
760
761fn apply_force_width_to_layout(layout: &mut LineLayout, force_width: Pixels) {
767 let mut glyph_pos: usize = 0;
768 let mut last_base_shaped_x = px(f32::NEG_INFINITY);
770 let mut last_base_actual_x = px(0.);
771
772 for run in layout.runs.iter_mut() {
773 for glyph in run.glyphs.iter_mut() {
774 let shaped_x = glyph.position.x;
775
776 if shaped_x > last_base_shaped_x + force_width * 0.5 {
777 let forced_x = glyph_pos * force_width;
778 if (shaped_x - forced_x).abs() > px(1.) {
779 glyph.position.x = forced_x;
780 }
781 last_base_shaped_x = shaped_x;
782 last_base_actual_x = glyph.position.x;
783 glyph_pos += 1;
784 } else {
785 glyph.position.x = last_base_actual_x + (shaped_x - last_base_shaped_x);
786 }
787 }
788 }
789}
790
791#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
793#[expect(missing_docs)]
794pub struct FontRun {
795 pub len: usize,
796 pub font_id: FontId,
797}
798
799trait AsCacheKeyRef {
800 fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
801}
802
803#[derive(Clone, Debug, Eq)]
804struct CacheKey {
805 text: SharedString,
806 font_size: Pixels,
807 runs: SmallVec<[FontRun; 1]>,
808 wrap_width: Option<Pixels>,
809 force_width: Option<Pixels>,
810}
811
812#[derive(Copy, Clone, PartialEq, Eq, Hash)]
813struct CacheKeyRef<'a> {
814 text: &'a str,
815 font_size: Pixels,
816 runs: &'a [FontRun],
817 wrap_width: Option<Pixels>,
818 force_width: Option<Pixels>,
819}
820
821impl PartialEq for dyn AsCacheKeyRef + '_ {
822 fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
823 self.as_cache_key_ref() == other.as_cache_key_ref()
824 }
825}
826
827impl Eq for dyn AsCacheKeyRef + '_ {}
828
829impl Hash for dyn AsCacheKeyRef + '_ {
830 fn hash<H: Hasher>(&self, state: &mut H) {
831 self.as_cache_key_ref().hash(state)
832 }
833}
834
835impl AsCacheKeyRef for CacheKey {
836 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
837 CacheKeyRef {
838 text: &self.text,
839 font_size: self.font_size,
840 runs: self.runs.as_slice(),
841 wrap_width: self.wrap_width,
842 force_width: self.force_width,
843 }
844 }
845}
846
847impl PartialEq for CacheKey {
848 fn eq(&self, other: &Self) -> bool {
849 self.as_cache_key_ref().eq(&other.as_cache_key_ref())
850 }
851}
852
853impl Hash for CacheKey {
854 fn hash<H: Hasher>(&self, state: &mut H) {
855 self.as_cache_key_ref().hash(state);
856 }
857}
858
859impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
860 fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
861 self.as_ref() as &dyn AsCacheKeyRef
862 }
863}
864
865impl AsCacheKeyRef for CacheKeyRef<'_> {
866 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
867 *self
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874 use crate::GlyphId;
875
876 fn glyph_at(x: f32, index: usize) -> ShapedGlyph {
877 ShapedGlyph {
878 id: GlyphId(0),
879 position: point(px(x), px(0.)),
880 index,
881 is_emoji: false,
882 }
883 }
884
885 fn make_layout(glyphs: Vec<ShapedGlyph>) -> LineLayout {
886 LineLayout {
887 font_size: px(16.),
888 width: px(100.),
889 ascent: px(12.),
890 descent: px(4.),
891 runs: vec![ShapedRun {
892 font_id: FontId(0),
893 glyphs,
894 }],
895 len: 0,
896 }
897 }
898
899 fn glyph_x_positions(layout: &LineLayout) -> Vec<f32> {
900 layout.runs[0]
901 .glyphs
902 .iter()
903 .map(|g| f32::from(g.position.x))
904 .collect()
905 }
906
907 #[test]
908 fn test_wrap_preserves_shaped_clusters() {
909 let mut layout = make_layout(vec![
910 glyph_at(0., 0),
911 glyph_at(5., 0),
912 glyph_at(10., 3),
913 glyph_at(15., 3),
914 ]);
915 layout.width = px(20.);
916 layout.len = "你好".len();
917 assert_eq!(
918 layout
919 .compute_wrap_boundaries("你好", px(15.), None, &mut WrapScratch::default())
920 .as_slice(),
921 &[WrapBoundary {
922 run_ix: 0,
923 glyph_ix: 2
924 }],
925 );
926
927 let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(10., 0)]);
928 layout.width = px(20.);
929 layout.len = 1;
930 assert!(
931 layout
932 .compute_wrap_boundaries("a", px(10.), None, &mut WrapScratch::default())
933 .is_empty()
934 );
935 }
936
937 #[test]
938 fn test_wrap_right_to_left_text() {
939 for (text, indices, expected_boundaries) in [
940 ("אבג דהו", vec![11, 9, 7, 6, 4, 2, 0], vec![4]),
941 ("a אבג b", vec![0, 1, 6, 4, 2, 8, 9], vec![2, 6]),
942 ] {
943 let mut layout = make_layout(
944 indices
945 .iter()
946 .enumerate()
947 .map(|(position, index)| glyph_at(position as f32 * 10., *index))
948 .collect(),
949 );
950 layout.width = px(indices.len() as f32 * 10.);
951 layout.len = text.len();
952 assert_eq!(
953 layout.compute_wrap_boundaries(text, px(40.), None, &mut WrapScratch::default()),
954 expected_boundaries
955 .into_iter()
956 .map(|glyph_ix| WrapBoundary {
957 run_ix: 0,
958 glyph_ix
959 })
960 .collect::<SmallVec<[WrapBoundary; 1]>>(),
961 "{text:?}",
962 );
963 }
964 }
965
966 #[test]
967 fn test_wrap_ignores_breaks_inside_shaped_clusters() {
968 let text = "x a-bcde";
969 let mut layout = make_layout(
970 [0, 1, 2, 5, 6, 7]
971 .into_iter()
972 .map(|index| glyph_at(index as f32 * 10., index))
973 .collect(),
974 );
975 layout.width = px(80.);
976 layout.len = text.len();
977 assert_eq!(
978 layout
979 .compute_wrap_boundaries(text, px(60.), None, &mut WrapScratch::default())
980 .as_slice(),
981 &[WrapBoundary {
982 run_ix: 0,
983 glyph_ix: 2
984 }],
985 );
986 }
987
988 #[test]
989 fn test_force_width_latin_unchanged() {
990 let cell_width = px(8.);
991 let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(8., 1), glyph_at(16., 2)]);
992
993 apply_force_width_to_layout(&mut layout, cell_width);
994
995 let positions = glyph_x_positions(&layout);
996 assert_eq!(positions, vec![0., 8., 16.]);
997 }
998
999 #[test]
1000 fn test_force_width_combining_marks_not_advanced() {
1001 let cell_width = px(8.);
1002 let mut layout = make_layout(vec![
1004 glyph_at(0., 0), glyph_at(0., 3), ]);
1007
1008 apply_force_width_to_layout(&mut layout, cell_width);
1009
1010 let positions = glyph_x_positions(&layout);
1011 assert_eq!(positions, vec![0., 0.]);
1012 }
1013
1014 #[test]
1015 fn test_force_width_base_after_combining_mark() {
1016 let cell_width = px(8.);
1017 let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3), glyph_at(8., 6)]);
1018
1019 apply_force_width_to_layout(&mut layout, cell_width);
1020
1021 let positions = glyph_x_positions(&layout);
1022 assert_eq!(positions, vec![0., 0., 8.]);
1023 }
1024
1025 #[test]
1026 fn test_force_width_multiple_combining_marks() {
1027 let cell_width = px(8.);
1028 let mut layout = make_layout(vec![
1030 glyph_at(0., 0), glyph_at(0., 3), glyph_at(0., 6), glyph_at(8., 9), ]);
1035
1036 apply_force_width_to_layout(&mut layout, cell_width);
1037
1038 let positions = glyph_x_positions(&layout);
1039 assert_eq!(positions, vec![0., 0., 0., 8.]);
1040 }
1041
1042 #[test]
1043 fn test_force_width_corrects_drifted_base_positions() {
1044 let cell_width = px(8.);
1045 let mut layout = make_layout(vec![
1047 glyph_at(0.5, 0), glyph_at(10.2, 1), glyph_at(19.8, 2), ]);
1051
1052 apply_force_width_to_layout(&mut layout, cell_width);
1053
1054 let positions = glyph_x_positions(&layout);
1055 assert_eq!(positions, vec![0.5, 8., 16.]);
1056 }
1057
1058 #[test]
1059 fn test_force_width_combining_mark_after_within_tolerance_base() {
1060 let cell_width = px(8.);
1061 let mut layout = make_layout(vec![glyph_at(0.5, 0), glyph_at(0.5, 3)]);
1064
1065 apply_force_width_to_layout(&mut layout, cell_width);
1066
1067 let positions = glyph_x_positions(&layout);
1068 assert_eq!(positions, vec![0.5, 0.5]);
1069 }
1070}