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