1use collections::FxHashMap;
2use gpui_engine::{
3 FontRun, LineLayout, LineLayoutIndex, PlatformTextSystem, WrapBoundary, WrappedLineLayout,
4};
5use gpui_shared_string::SharedString;
6use gpui_types::{Pixels, px};
7use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
8use smallvec::SmallVec;
9use std::{
10 borrow::Borrow,
11 hash::{Hash, Hasher},
12 ops::Range,
13 sync::{
14 Arc,
15 atomic::{AtomicUsize, Ordering},
16 },
17};
18
19use gpui_engine::LineWrapper;
20
21pub(crate) fn compute_wrap_boundaries(
22 layout: &LineLayout,
23 text: &str,
24 wrap_width: Pixels,
25 max_lines: Option<usize>,
26) -> SmallVec<[WrapBoundary; 1]> {
27 let mut boundaries = SmallVec::new();
28 let mut first_non_whitespace_ix = None;
29 let mut last_candidate_ix = None;
30 let mut last_candidate_x = px(0.);
31 let mut last_boundary = WrapBoundary {
32 run_ix: 0,
33 glyph_ix: 0,
34 };
35 let mut last_boundary_x = px(0.);
36 let mut prev_ch = '\0';
37 let mut glyphs = layout
38 .runs
39 .iter()
40 .enumerate()
41 .flat_map(move |(run_ix, run)| {
42 run.glyphs.iter().enumerate().map(move |(glyph_ix, glyph)| {
43 let character = text[glyph.index..].chars().next().unwrap();
44 (
45 WrapBoundary { run_ix, glyph_ix },
46 character,
47 glyph.position.x,
48 )
49 })
50 })
51 .peekable();
52
53 while let Some((boundary, ch, x)) = glyphs.next() {
54 if ch == '\n' {
55 continue;
56 }
57
58 if LineWrapper::is_word_char(ch) {
61 if prev_ch == ' ' && ch != ' ' && first_non_whitespace_ix.is_some() {
62 last_candidate_ix = Some(boundary);
63 last_candidate_x = x;
64 }
65 } else {
66 if ch != ' ' && first_non_whitespace_ix.is_some() {
67 last_candidate_ix = Some(boundary);
68 last_candidate_x = x;
69 }
70 }
71
72 if ch != ' ' && first_non_whitespace_ix.is_none() {
73 first_non_whitespace_ix = Some(boundary);
74 }
75
76 let next_x = glyphs.peek().map_or(layout.width, |(_, _, x)| *x);
77 let width = next_x - last_boundary_x;
78
79 if width > wrap_width && boundary > last_boundary {
80 if let Some(max_lines) = max_lines
82 && boundaries.len() >= max_lines.saturating_sub(1)
83 {
84 break;
85 }
86
87 if let Some(last_candidate_ix) = last_candidate_ix.take() {
88 last_boundary = last_candidate_ix;
89 last_boundary_x = last_candidate_x;
90 } else {
91 last_boundary = boundary;
92 last_boundary_x = x;
93 }
94 boundaries.push(last_boundary);
95 }
96 prev_ch = ch;
97 }
98
99 boundaries
100}
101
102pub struct LineLayoutCache {
105 previous_frame: Mutex<FrameCache>,
106 current_frame: RwLock<FrameCache>,
107 platform_text_system: Arc<dyn PlatformTextSystem>,
108 font_generation: Arc<AtomicUsize>,
110 cached_font_generation: AtomicUsize,
112}
113
114#[derive(Default)]
115struct FrameCache {
116 lines: FxHashMap<Arc<CacheKey>, Arc<LineLayout>>,
117 wrapped_lines: FxHashMap<Arc<CacheKey>, Arc<WrappedLineLayout>>,
118 used_lines: Vec<Arc<CacheKey>>,
119 used_wrapped_lines: Vec<Arc<CacheKey>>,
120
121 lines_by_hash: FxHashMap<Arc<HashedCacheKey>, Arc<LineLayout>>,
128 wrapped_lines_by_hash: FxHashMap<Arc<HashedCacheKey>, Arc<WrappedLineLayout>>,
129 used_lines_by_hash: Vec<Arc<HashedCacheKey>>,
130 used_wrapped_lines_by_hash: Vec<Arc<HashedCacheKey>>,
131}
132
133impl LineLayoutCache {
134 pub fn new(
136 platform_text_system: Arc<dyn PlatformTextSystem>,
137 font_generation: Arc<AtomicUsize>,
138 ) -> Self {
139 let cached_font_generation = font_generation.load(Ordering::Acquire);
140 Self {
141 previous_frame: Mutex::default(),
142 current_frame: RwLock::default(),
143 platform_text_system,
144 font_generation,
145 cached_font_generation: AtomicUsize::new(cached_font_generation),
146 }
147 }
148
149 pub fn layout_index(&self) -> LineLayoutIndex {
151 let font_generation = self.clear_if_font_generation_changed();
152 let frame = self.current_frame.read();
153 LineLayoutIndex {
154 font_generation,
155 lines_index: frame.used_lines.len(),
156 wrapped_lines_index: frame.used_wrapped_lines.len(),
157 lines_by_hash_index: frame.used_lines_by_hash.len(),
158 wrapped_lines_by_hash_index: frame.used_wrapped_lines_by_hash.len(),
159 }
160 }
161
162 pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
164 let font_generation = self.clear_if_font_generation_changed();
165 if range.start.font_generation != font_generation
166 || range.end.font_generation != font_generation
167 {
168 return;
169 }
170 let current_frame = &mut *self.current_frame.write();
171 let previous_frame = &mut *self.previous_frame.lock();
172
173 for key in &previous_frame.used_lines[range.start.lines_index..range.end.lines_index] {
174 if let Some((key, line)) = previous_frame.lines.remove_entry(key) {
175 current_frame.lines.insert(key, line);
176 }
177 current_frame.used_lines.push(key.clone());
178 }
179
180 for key in &previous_frame.used_wrapped_lines
181 [range.start.wrapped_lines_index..range.end.wrapped_lines_index]
182 {
183 if let Some((key, line)) = previous_frame.wrapped_lines.remove_entry(key) {
184 current_frame.wrapped_lines.insert(key, line);
185 }
186 current_frame.used_wrapped_lines.push(key.clone());
187 }
188
189 for key in &previous_frame.used_lines_by_hash
190 [range.start.lines_by_hash_index..range.end.lines_by_hash_index]
191 {
192 if let Some((key, line)) = previous_frame.lines_by_hash.remove_entry(key) {
193 current_frame.lines_by_hash.insert(key, line);
194 }
195 current_frame.used_lines_by_hash.push(key.clone());
196 }
197
198 for key in &previous_frame.used_wrapped_lines_by_hash
199 [range.start.wrapped_lines_by_hash_index..range.end.wrapped_lines_by_hash_index]
200 {
201 if let Some((key, line)) = previous_frame.wrapped_lines_by_hash.remove_entry(key) {
202 current_frame.wrapped_lines_by_hash.insert(key, line);
203 }
204 current_frame.used_wrapped_lines_by_hash.push(key.clone());
205 }
206 }
207
208 pub fn truncate_layouts(&self, index: LineLayoutIndex) {
210 let font_generation = self.clear_if_font_generation_changed();
211 if index.font_generation != font_generation {
212 return;
213 }
214 let current_frame = &mut *self.current_frame.write();
215 current_frame.used_lines.truncate(index.lines_index);
216 current_frame
217 .used_wrapped_lines
218 .truncate(index.wrapped_lines_index);
219 current_frame
220 .used_lines_by_hash
221 .truncate(index.lines_by_hash_index);
222 current_frame
223 .used_wrapped_lines_by_hash
224 .truncate(index.wrapped_lines_by_hash_index);
225 }
226
227 pub fn finish_frame(&self) {
229 let _font_generation = self.clear_if_font_generation_changed();
230 let mut curr_frame = self.current_frame.write();
231 let mut prev_frame = self.previous_frame.lock();
232 std::mem::swap(&mut *prev_frame, &mut *curr_frame);
233 curr_frame.lines.clear();
234 curr_frame.wrapped_lines.clear();
235 curr_frame.used_lines.clear();
236 curr_frame.used_wrapped_lines.clear();
237
238 curr_frame.lines_by_hash.clear();
239 curr_frame.wrapped_lines_by_hash.clear();
240 curr_frame.used_lines_by_hash.clear();
241 curr_frame.used_wrapped_lines_by_hash.clear();
242 }
243
244 pub fn layout_wrapped_line<Text>(
246 &self,
247 text: Text,
248 font_size: Pixels,
249 runs: &[FontRun],
250 wrap_width: Option<Pixels>,
251 max_lines: Option<usize>,
252 ) -> Arc<WrappedLineLayout>
253 where
254 Text: AsRef<str>,
255 SharedString: From<Text>,
256 {
257 let _font_generation = self.clear_if_font_generation_changed();
258 let key = &CacheKeyRef {
259 text: text.as_ref(),
260 font_size,
261 runs,
262 wrap_width,
263 force_width: None,
264 } as &dyn AsCacheKeyRef;
265
266 let current_frame = self.current_frame.upgradable_read();
267 if let Some(layout) = current_frame.wrapped_lines.get(key) {
268 return layout.clone();
269 }
270
271 let previous_frame_entry = self.previous_frame.lock().wrapped_lines.remove_entry(key);
272 if let Some((key, layout)) = previous_frame_entry {
273 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
274 current_frame
275 .wrapped_lines
276 .insert(key.clone(), layout.clone());
277 current_frame.used_wrapped_lines.push(key);
278 layout
279 } else {
280 drop(current_frame);
281 let text = SharedString::from(text);
282 let unwrapped_layout = self.layout_line::<&SharedString>(&text, font_size, runs, None);
283 let wrap_boundaries = if let Some(wrap_width) = wrap_width {
284 compute_wrap_boundaries(&unwrapped_layout, text.as_ref(), wrap_width, max_lines)
285 } else {
286 SmallVec::new()
287 };
288 let layout = Arc::new(WrappedLineLayout {
289 unwrapped_layout,
290 wrap_boundaries,
291 wrap_width,
292 });
293 let key = Arc::new(CacheKey {
294 text,
295 font_size,
296 runs: SmallVec::from(runs),
297 wrap_width,
298 force_width: None,
299 });
300
301 let mut current_frame = self.current_frame.write();
302 current_frame
303 .wrapped_lines
304 .insert(key.clone(), layout.clone());
305 current_frame.used_wrapped_lines.push(key);
306
307 layout
308 }
309 }
310
311 pub fn layout_line<Text>(
313 &self,
314 text: Text,
315 font_size: Pixels,
316 runs: &[FontRun],
317 force_width: Option<Pixels>,
318 ) -> Arc<LineLayout>
319 where
320 Text: AsRef<str>,
321 SharedString: From<Text>,
322 {
323 let _font_generation = self.clear_if_font_generation_changed();
324 let key = &CacheKeyRef {
325 text: text.as_ref(),
326 font_size,
327 runs,
328 wrap_width: None,
329 force_width,
330 } as &dyn AsCacheKeyRef;
331
332 let current_frame = self.current_frame.upgradable_read();
333 if let Some(layout) = current_frame.lines.get(key) {
334 return layout.clone();
335 }
336
337 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
338 if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
339 current_frame.lines.insert(key.clone(), layout.clone());
340 current_frame.used_lines.push(key);
341 layout
342 } else {
343 let text = SharedString::from(text);
344 let mut layout = self
345 .platform_text_system
346 .layout_line(&text, font_size, runs);
347
348 if let Some(force_width) = force_width {
349 apply_force_width_to_layout(&mut layout, force_width);
350 }
351
352 let key = Arc::new(CacheKey {
353 text,
354 font_size,
355 runs: SmallVec::from(runs),
356 wrap_width: None,
357 force_width,
358 });
359 let layout = Arc::new(layout);
360 current_frame.lines.insert(key.clone(), layout.clone());
361 current_frame.used_lines.push(key);
362 layout
363 }
364 }
365
366 pub fn try_layout_line_by_hash(
375 &self,
376 text_hash: u64,
377 text_len: usize,
378 font_size: Pixels,
379 runs: &[FontRun],
380 force_width: Option<Pixels>,
381 ) -> Option<Arc<LineLayout>> {
382 let _font_generation = self.clear_if_font_generation_changed();
383 let key_ref = HashedCacheKeyRef {
384 text_hash,
385 text_len,
386 font_size,
387 runs,
388 wrap_width: None,
389 force_width,
390 };
391
392 let current_frame = self.current_frame.read();
393 if let Some((_, layout)) = current_frame.lines_by_hash.iter().find(|(key, _)| {
394 HashedCacheKeyRef {
395 text_hash: key.text_hash,
396 text_len: key.text_len,
397 font_size: key.font_size,
398 runs: key.runs.as_slice(),
399 wrap_width: key.wrap_width,
400 force_width: key.force_width,
401 } == key_ref
402 }) {
403 return Some(layout.clone());
404 }
405
406 let previous_frame = self.previous_frame.lock();
407 if let Some((_, layout)) = previous_frame.lines_by_hash.iter().find(|(key, _)| {
408 HashedCacheKeyRef {
409 text_hash: key.text_hash,
410 text_len: key.text_len,
411 font_size: key.font_size,
412 runs: key.runs.as_slice(),
413 wrap_width: key.wrap_width,
414 force_width: key.force_width,
415 } == key_ref
416 }) {
417 return Some(layout.clone());
418 }
419
420 None
421 }
422
423 pub fn layout_line_by_hash(
432 &self,
433 text_hash: u64,
434 text_len: usize,
435 font_size: Pixels,
436 runs: &[FontRun],
437 force_width: Option<Pixels>,
438 materialize_text: impl FnOnce() -> SharedString,
439 ) -> Arc<LineLayout> {
440 let _font_generation = self.clear_if_font_generation_changed();
441 let key_ref = HashedCacheKeyRef {
442 text_hash,
443 text_len,
444 font_size,
445 runs,
446 wrap_width: None,
447 force_width,
448 };
449
450 let current_frame = self.current_frame.upgradable_read();
452 if let Some((_, layout)) = current_frame.lines_by_hash.iter().find(|(key, _)| {
453 HashedCacheKeyRef {
454 text_hash: key.text_hash,
455 text_len: key.text_len,
456 font_size: key.font_size,
457 runs: key.runs.as_slice(),
458 wrap_width: key.wrap_width,
459 force_width: key.force_width,
460 } == key_ref
461 }) {
462 return layout.clone();
463 }
464
465 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
466
467 let mut previous_frame = self.previous_frame.lock();
470 if let Some(existing_key) = previous_frame
471 .used_lines_by_hash
472 .iter()
473 .find(|key| {
474 HashedCacheKeyRef {
475 text_hash: key.text_hash,
476 text_len: key.text_len,
477 font_size: key.font_size,
478 runs: key.runs.as_slice(),
479 wrap_width: key.wrap_width,
480 force_width: key.force_width,
481 } == key_ref
482 })
483 .cloned()
484 {
485 if let Some((key, layout)) = previous_frame.lines_by_hash.remove_entry(&existing_key) {
486 current_frame
487 .lines_by_hash
488 .insert(key.clone(), layout.clone());
489 current_frame.used_lines_by_hash.push(key);
490 return layout;
491 }
492 }
493
494 let text = materialize_text();
495 let mut layout = self
496 .platform_text_system
497 .layout_line(&text, font_size, runs);
498
499 if let Some(force_width) = force_width {
500 apply_force_width_to_layout(&mut layout, force_width);
501 }
502
503 let key = Arc::new(HashedCacheKey {
504 text_hash,
505 text_len,
506 font_size,
507 runs: SmallVec::from(runs),
508 wrap_width: None,
509 force_width,
510 });
511 let layout = Arc::new(layout);
512 current_frame
513 .lines_by_hash
514 .insert(key.clone(), layout.clone());
515 current_frame.used_lines_by_hash.push(key);
516 layout
517 }
518
519 fn clear_if_font_generation_changed(&self) -> usize {
520 let font_generation = self.font_generation.load(Ordering::Acquire);
521 if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
522 return font_generation;
523 }
524
525 let mut current_frame = self.current_frame.write();
526 if self.cached_font_generation.load(Ordering::Acquire) == font_generation {
527 return font_generation;
528 }
529
530 *current_frame = FrameCache::default();
531 *self.previous_frame.lock() = FrameCache::default();
532 self.cached_font_generation
533 .store(font_generation, Ordering::Release);
534 font_generation
535 }
536}
537
538fn apply_force_width_to_layout(layout: &mut LineLayout, force_width: Pixels) {
544 let mut glyph_pos: usize = 0;
545 let mut last_base_shaped_x = px(f32::NEG_INFINITY);
547 let mut last_base_actual_x = px(0.);
548
549 for run in layout.runs.iter_mut() {
550 for glyph in run.glyphs.iter_mut() {
551 let shaped_x = glyph.position.x;
552
553 if shaped_x > last_base_shaped_x + force_width * 0.5 {
554 let forced_x = glyph_pos * force_width;
555 if (shaped_x - forced_x).abs() > px(1.) {
556 glyph.position.x = forced_x;
557 }
558 last_base_shaped_x = shaped_x;
559 last_base_actual_x = glyph.position.x;
560 glyph_pos += 1;
561 } else {
562 glyph.position.x = last_base_actual_x + (shaped_x - last_base_shaped_x);
563 }
564 }
565 }
566}
567
568trait AsCacheKeyRef {
570 fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
571}
572
573#[derive(Clone, Debug, Eq)]
574struct CacheKey {
575 text: SharedString,
576 font_size: Pixels,
577 runs: SmallVec<[FontRun; 1]>,
578 wrap_width: Option<Pixels>,
579 force_width: Option<Pixels>,
580}
581
582#[derive(Copy, Clone, PartialEq, Eq, Hash)]
583struct CacheKeyRef<'a> {
584 text: &'a str,
585 font_size: Pixels,
586 runs: &'a [FontRun],
587 wrap_width: Option<Pixels>,
588 force_width: Option<Pixels>,
589}
590
591#[derive(Clone, Debug)]
592struct HashedCacheKey {
593 text_hash: u64,
594 text_len: usize,
595 font_size: Pixels,
596 runs: SmallVec<[FontRun; 1]>,
597 wrap_width: Option<Pixels>,
598 force_width: Option<Pixels>,
599}
600
601#[derive(Copy, Clone)]
602struct HashedCacheKeyRef<'a> {
603 text_hash: u64,
604 text_len: usize,
605 font_size: Pixels,
606 runs: &'a [FontRun],
607 wrap_width: Option<Pixels>,
608 force_width: Option<Pixels>,
609}
610
611impl PartialEq for dyn AsCacheKeyRef + '_ {
612 fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
613 self.as_cache_key_ref() == other.as_cache_key_ref()
614 }
615}
616
617impl PartialEq for HashedCacheKey {
618 fn eq(&self, other: &Self) -> bool {
619 self.text_hash == other.text_hash
620 && self.text_len == other.text_len
621 && self.font_size == other.font_size
622 && self.runs.as_slice() == other.runs.as_slice()
623 && self.wrap_width == other.wrap_width
624 && self.force_width == other.force_width
625 }
626}
627
628impl Eq for HashedCacheKey {}
629
630impl Hash for HashedCacheKey {
631 fn hash<H: Hasher>(&self, state: &mut H) {
632 self.text_hash.hash(state);
633 self.text_len.hash(state);
634 self.font_size.hash(state);
635 self.runs.as_slice().hash(state);
636 self.wrap_width.hash(state);
637 self.force_width.hash(state);
638 }
639}
640
641impl PartialEq for HashedCacheKeyRef<'_> {
642 fn eq(&self, other: &Self) -> bool {
643 self.text_hash == other.text_hash
644 && self.text_len == other.text_len
645 && self.font_size == other.font_size
646 && self.runs == other.runs
647 && self.wrap_width == other.wrap_width
648 && self.force_width == other.force_width
649 }
650}
651
652impl Eq for HashedCacheKeyRef<'_> {}
653
654impl Hash for HashedCacheKeyRef<'_> {
655 fn hash<H: Hasher>(&self, state: &mut H) {
656 self.text_hash.hash(state);
657 self.text_len.hash(state);
658 self.font_size.hash(state);
659 self.runs.hash(state);
660 self.wrap_width.hash(state);
661 self.force_width.hash(state);
662 }
663}
664
665impl Eq for dyn AsCacheKeyRef + '_ {}
666
667impl Hash for dyn AsCacheKeyRef + '_ {
668 fn hash<H: Hasher>(&self, state: &mut H) {
669 self.as_cache_key_ref().hash(state)
670 }
671}
672
673impl AsCacheKeyRef for CacheKey {
674 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
675 CacheKeyRef {
676 text: &self.text,
677 font_size: self.font_size,
678 runs: self.runs.as_slice(),
679 wrap_width: self.wrap_width,
680 force_width: self.force_width,
681 }
682 }
683}
684
685impl PartialEq for CacheKey {
686 fn eq(&self, other: &Self) -> bool {
687 self.as_cache_key_ref().eq(&other.as_cache_key_ref())
688 }
689}
690
691impl Hash for CacheKey {
692 fn hash<H: Hasher>(&self, state: &mut H) {
693 self.as_cache_key_ref().hash(state);
694 }
695}
696
697impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
698 fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
699 self.as_ref() as &dyn AsCacheKeyRef
700 }
701}
702
703impl AsCacheKeyRef for CacheKeyRef<'_> {
704 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
705 *self
706 }
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712 use gpui_engine::{FontId, GlyphId, ShapedGlyph, ShapedRun};
713 use gpui_types::point;
714
715 fn glyph_at(x: f32, index: usize) -> ShapedGlyph {
716 ShapedGlyph {
717 id: GlyphId(0),
718 position: point(px(x), px(0.)),
719 index,
720 is_emoji: false,
721 }
722 }
723
724 fn make_layout(glyphs: Vec<ShapedGlyph>) -> LineLayout {
725 LineLayout {
726 font_size: px(16.),
727 width: px(100.),
728 ascent: px(12.),
729 descent: px(4.),
730 runs: vec![ShapedRun {
731 font_id: FontId(0),
732 glyphs,
733 }],
734 len: 0,
735 }
736 }
737
738 fn glyph_x_positions(layout: &LineLayout) -> Vec<f32> {
739 layout.runs[0]
740 .glyphs
741 .iter()
742 .map(|g| f32::from(g.position.x))
743 .collect()
744 }
745
746 #[test]
747 fn test_force_width_latin_unchanged() {
748 let cell_width = px(8.);
749 let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(8., 1), glyph_at(16., 2)]);
750
751 apply_force_width_to_layout(&mut layout, cell_width);
752
753 let positions = glyph_x_positions(&layout);
754 assert_eq!(positions, vec![0., 8., 16.]);
755 }
756
757 #[test]
758 fn test_force_width_combining_marks_not_advanced() {
759 let cell_width = px(8.);
760 let mut layout = make_layout(vec![
762 glyph_at(0., 0), glyph_at(0., 3), ]);
765
766 apply_force_width_to_layout(&mut layout, cell_width);
767
768 let positions = glyph_x_positions(&layout);
769 assert_eq!(positions, vec![0., 0.]);
770 }
771
772 #[test]
773 fn test_force_width_base_after_combining_mark() {
774 let cell_width = px(8.);
775 let mut layout = make_layout(vec![glyph_at(0., 0), glyph_at(0., 3), glyph_at(8., 6)]);
776
777 apply_force_width_to_layout(&mut layout, cell_width);
778
779 let positions = glyph_x_positions(&layout);
780 assert_eq!(positions, vec![0., 0., 8.]);
781 }
782
783 #[test]
784 fn test_force_width_multiple_combining_marks() {
785 let cell_width = px(8.);
786 let mut layout = make_layout(vec![
788 glyph_at(0., 0), glyph_at(0., 3), glyph_at(0., 6), glyph_at(8., 9), ]);
793
794 apply_force_width_to_layout(&mut layout, cell_width);
795
796 let positions = glyph_x_positions(&layout);
797 assert_eq!(positions, vec![0., 0., 0., 8.]);
798 }
799
800 #[test]
801 fn test_force_width_corrects_drifted_base_positions() {
802 let cell_width = px(8.);
803 let mut layout = make_layout(vec![
805 glyph_at(0.5, 0), glyph_at(10.2, 1), glyph_at(19.8, 2), ]);
809
810 apply_force_width_to_layout(&mut layout, cell_width);
811
812 let positions = glyph_x_positions(&layout);
813 assert_eq!(positions, vec![0.5, 8., 16.]);
814 }
815
816 #[test]
817 fn test_force_width_combining_mark_after_within_tolerance_base() {
818 let cell_width = px(8.);
819 let mut layout = make_layout(vec![glyph_at(0.5, 0), glyph_at(0.5, 3)]);
822
823 apply_force_width_to_layout(&mut layout, cell_width);
824
825 let positions = glyph_x_positions(&layout);
826 assert_eq!(positions, vec![0.5, 0.5]);
827 }
828}