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