1use crate::{
2 Bounds, FontId, GlyphId, InlineBidiScope, InlineBoxRequest, InlineLayoutRequest,
3 InlineTextMetrics, InlineTextStyle, InlineVisualLine, Pixels, PlatformTextSystem, Point,
4 SharedString, Size, StrikethroughStyle, TextLayoutOptions, TextLayoutRequest, TextRun,
5 UnderlineStyle, VerticalAlign,
6};
7use collections::FxHashMap;
8use palette::Hsla;
9use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
10use smallvec::SmallVec;
11use std::{
12 borrow::Borrow,
13 hash::{Hash, Hasher},
14 ops::Range,
15 sync::{
16 Arc,
17 atomic::{AtomicU64, Ordering},
18 },
19};
20
21#[derive(Clone, Debug)]
29pub struct LineLayout {
30 pub font_size: Pixels,
32 pub width: Pixels,
34 pub ascent: Pixels,
36 pub descent: Pixels,
38 pub visual_lines: SmallVec<[VisualLine; 1]>,
40 pub paint_fragments: Vec<PaintFragment>,
42 pub len: usize,
44 pub platform_layout: Arc<dyn PlatformTextLayout>,
46}
47
48#[derive(Clone, Copy, Debug, Default, PartialEq)]
50pub struct PositionedInlineBox {
51 pub id: u64,
53 pub line_index: usize,
55 pub bounds: Bounds<Pixels>,
57}
58
59#[derive(Clone, Copy, Debug, Default, PartialEq)]
61pub struct InlineRangeGeometry {
62 pub bounds: Bounds<Pixels>,
64 pub visual_line_index: usize,
66}
67
68#[derive(Clone, Debug)]
76pub struct InlineLayout {
77 pub layout: Arc<LineLayout>,
79 pub lines: Vec<InlineVisualLine>,
81 pub boxes: Vec<PositionedInlineBox>,
83 pub alignment_offset: Pixels,
85 pub size: Size<Pixels>,
87}
88
89fn base_inline_line_bounds(metrics: InlineTextMetrics, line_height: Pixels) -> (Pixels, Pixels) {
90 let half_leading = ((line_height - metrics.ascent - metrics.descent) / 2.).max(Pixels::ZERO);
91
92 (
93 -metrics.ascent - half_leading,
94 metrics.descent + half_leading,
95 )
96}
97
98fn expand_inline_line_for_box(
99 top: &mut Pixels,
100 bottom: &mut Pixels,
101 top_box_height: &mut Pixels,
102 bottom_box_height: &mut Pixels,
103 height: Pixels,
104 metrics: InlineTextMetrics,
105 align: VerticalAlign,
106) {
107 match align {
108 VerticalAlign::Baseline => *top = (*top).min(-height),
109 VerticalAlign::Middle => {
110 let middle = -metrics.x_height / 2.;
111 *top = (*top).min(middle - height / 2.);
112 *bottom = (*bottom).max(middle + height / 2.);
113 }
114 VerticalAlign::Top => *top_box_height = (*top_box_height).max(height),
115 VerticalAlign::Bottom => *bottom_box_height = (*bottom_box_height).max(height),
116 }
117}
118
119fn aligned_inline_box_y(
120 line: InlineVisualLine,
121 metrics: InlineTextMetrics,
122 height: Pixels,
123 align: VerticalAlign,
124) -> Pixels {
125 match align {
126 VerticalAlign::Baseline => line.baseline - height,
127 VerticalAlign::Middle => line.baseline - metrics.x_height / 2. - height / 2.,
128 VerticalAlign::Top => Pixels::ZERO,
129 VerticalAlign::Bottom => line.size.height - height,
130 }
131}
132
133pub fn align_inline_boxes(
135 lines: &mut [InlineVisualLine],
136 boxes: &mut [PositionedInlineBox],
137 size: &mut Size<Pixels>,
138 requests: &[InlineBoxRequest],
139 line_metrics: &[InlineTextMetrics],
140 text_bounds: &[(Pixels, Pixels)],
141 fallback_metrics: InlineTextMetrics,
142 line_height: Pixels,
143) {
144 let request_alignments = requests
145 .iter()
146 .map(|request| (request.id, request.vertical_align))
147 .collect::<FxHashMap<_, _>>();
148 let mut boxes_by_line: FxHashMap<_, Vec<_>> = FxHashMap::default();
149
150 for inline_box in boxes
151 .iter_mut()
152 .filter(|inline_box| inline_box.line_index < lines.len())
153 {
154 let align = request_alignments
155 .get(&inline_box.id)
156 .copied()
157 .unwrap_or(VerticalAlign::Baseline);
158
159 boxes_by_line
160 .entry(inline_box.line_index)
161 .or_default()
162 .push((inline_box, align));
163 }
164
165 let mut line_y = Pixels::ZERO;
166
167 for (line_index, line) in lines.iter_mut().enumerate() {
168 let line_boxes = boxes_by_line.remove(&line_index).unwrap_or_default();
169 let metrics = line_metrics
170 .get(line_index)
171 .copied()
172 .unwrap_or(fallback_metrics);
173 let (mut top, mut bottom) = text_bounds
174 .get(line_index)
175 .copied()
176 .unwrap_or_else(|| base_inline_line_bounds(metrics, line_height));
177 let mut top_box_height = Pixels::ZERO;
178 let mut bottom_box_height = Pixels::ZERO;
179
180 for (inline_box, align) in &line_boxes {
181 expand_inline_line_for_box(
182 &mut top,
183 &mut bottom,
184 &mut top_box_height,
185 &mut bottom_box_height,
186 inline_box.bounds.size.height,
187 metrics,
188 *align,
189 );
190 }
191
192 bottom = bottom.max(top + top_box_height);
193 top = top.min(bottom - bottom_box_height);
194 line.origin.y = line_y;
195 line.size.height = bottom - top;
196 line.baseline = -top;
197
198 for (inline_box, align) in line_boxes {
199 inline_box.bounds.origin.y =
200 line_y + aligned_inline_box_y(*line, metrics, inline_box.bounds.size.height, align);
201 }
202
203 line_y += line.size.height;
204 }
205
206 size.height = line_y;
207}
208
209pub trait PlatformTextLayout: Send + Sync + std::fmt::Debug {
224 fn len(&self) -> usize;
226
227 fn line_count(&self) -> usize;
229
230 fn size(&self) -> Size<Pixels>;
232
233 fn byte_index_from_pixel_point(
238 &self,
239 pixel_point: Point<Pixels>,
240 line_height: Pixels,
241 ) -> Result<usize, usize>;
242
243 fn caret_from_pixel_point(
246 &self,
247 pixel_point: Point<Pixels>,
248 line_height: Pixels,
249 ) -> Result<CaretPosition, CaretPosition>;
250
251 fn caret_bounds(&self, caret: CaretPosition, line_height: Pixels) -> Option<Bounds<Pixels>>;
253
254 fn normalized_caret(&self, caret: CaretPosition) -> CaretPosition;
256
257 fn adjacent_visual_caret(
259 &self,
260 caret: CaretPosition,
261 direction: VisualDirection,
262 ) -> Option<CaretPosition>;
263
264 fn selection_bounds(
266 &self,
267 byte_range: Range<usize>,
268 line_height: Pixels,
269 ) -> Vec<Bounds<Pixels>>;
270
271 fn inline_geometry(&self, range: Range<usize>) -> Option<Vec<InlineRangeGeometry>> {
277 if range.is_empty() {
278 return None;
279 }
280
281 let line_height = self.size().height / self.line_count().max(1) as f32;
282
283 Some(
284 self.selection_bounds(range, line_height)
285 .into_iter()
286 .map(|bounds| InlineRangeGeometry {
287 visual_line_index: (bounds.origin.y / line_height) as usize,
288 bounds,
289 })
290 .collect(),
291 )
292 }
293
294 fn inline_geometry_for_ranges(&self, ranges: &[Range<usize>]) -> Vec<Vec<InlineRangeGeometry>> {
296 ranges
297 .iter()
298 .map(|range| self.inline_geometry(range.clone()).unwrap_or_default())
299 .collect()
300 }
301
302 fn logical_cluster_before(&self, caret: CaretPosition) -> Option<Range<usize>>;
304
305 fn logical_cluster_after(&self, caret: CaretPosition) -> Option<Range<usize>>;
307
308 fn caret_movement(
310 &self,
311 caret: CaretPosition,
312 movement: TextMovement,
313 vertical_navigation_x: Option<Pixels>,
314 ) -> CaretMovement;
315
316 fn selection_from_pixel_point(
318 &self,
319 pixel_point: Point<Pixels>,
320 line_height: Pixels,
321 kind: TextSelectionKind,
322 ) -> Range<usize>;
323}
324
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
327pub enum VisualDirection {
328 Left,
330 Right,
332}
333
334#[derive(Clone, Copy, Debug, Eq, PartialEq)]
336pub enum TextDirection {
337 Left,
339 Right,
341 Up,
343 Down,
345 Start,
347 End,
349}
350
351#[derive(Clone, Copy, Debug, Eq, PartialEq)]
353pub enum TextBoundary {
354 Cluster,
362 Word,
364 VisualLine,
366 HardLine,
368 Document,
370}
371
372#[derive(Clone, Copy, Debug, Eq, PartialEq)]
378pub struct TextMovement {
379 pub direction: TextDirection,
381 pub boundary: TextBoundary,
383}
384
385impl TextDirection {
386 pub const fn with_boundary(self, boundary: TextBoundary) -> TextMovement {
388 TextMovement {
389 direction: self,
390 boundary,
391 }
392 }
393}
394
395#[derive(Clone, Copy, Debug, Eq, PartialEq)]
397pub enum TextSelectionKind {
398 Word,
400 VisualLine,
402 HardLine,
404}
405
406#[derive(Clone, Debug, Default, PartialEq)]
411pub struct VisualLine {
412 pub text_range: Range<usize>,
414 pub paint_fragment_range: Range<usize>,
417 pub advance_width: Pixels,
421 pub offset: Pixels,
423 pub direction: crate::ResolvedDirection,
425}
426
427#[derive(Clone, Debug, Default, PartialEq)]
432pub struct PaintStyle {
433 pub color: Hsla,
435 pub background_color: Option<Hsla>,
437 pub underline: Option<UnderlineStyle>,
439 pub strikethrough: Option<StrikethroughStyle>,
441}
442
443impl From<&TextRun> for PaintStyle {
444 fn from(run: &TextRun) -> Self {
445 Self {
446 color: run.color,
447 background_color: run.background_color,
448 underline: run.underline,
449 strikethrough: run.strikethrough,
450 }
451 }
452}
453
454#[derive(Clone, Debug, PartialEq)]
456pub struct PaintFragment {
457 pub source_run: usize,
459 pub font_id: FontId,
461 pub font_size: Pixels,
463 pub glyphs: Arc<[ShapedGlyph]>,
465 pub x_range: Range<Pixels>,
467 pub style: PaintStyle,
469 pub underline_offset: Option<Pixels>,
471 pub strikethrough_offset: Option<Pixels>,
473}
474
475#[derive(Clone, Debug, PartialEq)]
477pub struct ShapedGlyph {
478 pub id: GlyphId,
480
481 pub position: Point<Pixels>,
483
484 pub is_emoji: bool,
486}
487
488#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
494pub enum CaretAffinity {
495 #[default]
497 Downstream,
498 Upstream,
500}
501
502static HARD_LINE_BREAK_CHARACTERS: phf::Set<char> = phf::phf_set! {
504 '\n',
505 '\r',
506 '\u{001c}',
507 '\u{001d}',
508 '\u{001e}',
509 '\u{0085}',
510 '\u{2028}',
511 '\u{2029}',
512};
513
514pub fn is_paragraph_separator(character: char) -> bool {
519 character != '\u{2028}' && HARD_LINE_BREAK_CHARACTERS.contains(&character)
520}
521
522impl CaretAffinity {
523 pub fn for_inserted_text(text: &str) -> Self {
528 Self::from(text)
529 }
530}
531
532impl From<&str> for CaretAffinity {
533 fn from(text: &str) -> Self {
534 let Some(last_character) = text.chars().next_back() else {
535 return Self::Downstream;
536 };
537
538 if HARD_LINE_BREAK_CHARACTERS.contains(&last_character) {
539 return Self::Downstream;
540 }
541
542 Self::Upstream
543 }
544}
545
546#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
552pub struct CaretPosition {
553 pub index: usize,
555 pub affinity: CaretAffinity,
557}
558
559impl From<(usize, CaretAffinity)> for CaretPosition {
560 fn from((index, affinity): (usize, CaretAffinity)) -> Self {
561 Self { index, affinity }
562 }
563}
564
565impl CaretPosition {
566 pub const fn attached_to_next_cluster(index: usize) -> Self {
568 Self {
569 index,
570 affinity: CaretAffinity::Downstream,
571 }
572 }
573
574 pub const fn attached_to_previous_cluster(index: usize) -> Self {
576 Self {
577 index,
578 affinity: CaretAffinity::Upstream,
579 }
580 }
581}
582
583#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
588pub struct CaretSelection {
589 pub anchor: CaretPosition,
591 pub caret: CaretPosition,
593}
594
595impl From<usize> for CaretSelection {
596 fn from(index: usize) -> Self {
597 CaretPosition::attached_to_next_cluster(index).into()
598 }
599}
600
601impl From<CaretPosition> for CaretSelection {
602 fn from(caret: CaretPosition) -> Self {
603 Self {
604 anchor: caret,
605 caret,
606 }
607 }
608}
609
610impl From<Range<usize>> for CaretSelection {
611 fn from(range: Range<usize>) -> Self {
613 Self {
614 anchor: CaretPosition::attached_to_next_cluster(range.end),
615 caret: CaretPosition::attached_to_next_cluster(range.start),
616 }
617 }
618}
619
620impl CaretSelection {
621 pub fn is_empty(&self) -> bool {
625 self.anchor.index == self.caret.index
626 }
627
628 pub fn endpoint_ordering(self) -> std::cmp::Ordering {
630 self.caret.index.cmp(&self.anchor.index)
631 }
632
633 pub fn byte_range(self) -> Range<usize> {
635 self.anchor.index.min(self.caret.index)..self.anchor.index.max(self.caret.index)
636 }
637
638 pub fn min(mut self, max_idx: usize) -> Self {
640 self.caret.index = self.caret.index.min(max_idx);
641 self.anchor.index = self.anchor.index.min(max_idx);
642
643 self
644 }
645
646 pub fn with_caret(self, caret: CaretPosition) -> Self {
648 Self {
649 anchor: self.anchor,
650 caret,
651 }
652 }
653}
654
655#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
657pub struct CaretMovement<T = CaretPosition> {
658 pub result: T,
660 pub vertical_navigation_x: Option<Pixels>,
663}
664
665pub type CaretSelectionMovement = CaretMovement<CaretSelection>;
667
668impl CaretMovement<CaretSelection> {
669 pub fn move_or_select_to(self, caret: CaretPosition, extend: bool) -> Self {
673 let result = if extend {
674 self.result.with_caret(caret)
675 } else {
676 caret.into()
677 };
678
679 Self {
680 result,
681 vertical_navigation_x: None,
682 }
683 }
684}
685
686#[derive(Debug)]
694pub struct ShapedTextLayout {
695 pub layout: Arc<LineLayout>,
697
698 pub wrap_width: Option<Pixels>,
700}
701
702impl ShapedTextLayout {
703 #[allow(clippy::len_without_is_empty)]
705 pub fn len(&self) -> usize {
706 self.layout.platform_layout.len()
707 }
708
709 pub fn width(&self) -> Pixels {
711 self.wrap_width
712 .unwrap_or(Pixels::MAX)
713 .min(self.layout.width)
714 }
715
716 pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
718 Size {
719 width: self.width(),
720 height: line_height * self.line_count(),
721 }
722 }
723
724 pub fn line_count(&self) -> usize {
726 self.layout.platform_layout.line_count()
727 }
728
729 pub fn ascent(&self) -> Pixels {
731 self.layout.ascent
732 }
733
734 pub fn descent(&self) -> Pixels {
736 self.layout.descent
737 }
738
739 pub fn visual_lines(&self) -> &[VisualLine] {
741 &self.layout.visual_lines
742 }
743
744 pub fn fragments_for_line(&self, line: &VisualLine) -> &[PaintFragment] {
746 &self.layout.paint_fragments[line.paint_fragment_range.clone()]
747 }
748
749 pub fn font_size(&self) -> Pixels {
751 self.layout.font_size
752 }
753
754 pub fn byte_index_for_pixel_point(
762 &self,
763 pixel_point: Point<Pixels>,
764 line_height: Pixels,
765 ) -> Result<usize, usize> {
766 self.layout
767 .platform_layout
768 .byte_index_from_pixel_point(pixel_point, line_height)
769 }
770
771 pub fn closest_byte_index_for_pixel_point(
784 &self,
785 pixel_point: Point<Pixels>,
786 line_height: Pixels,
787 ) -> Result<usize, usize> {
788 self.closest_caret_for_pixel_point(pixel_point, line_height)
789 .map(|caret| caret.index)
790 .map_err(|caret| caret.index)
791 }
792
793 pub fn closest_caret_for_pixel_point(
798 &self,
799 pixel_point: Point<Pixels>,
800 line_height: Pixels,
801 ) -> Result<CaretPosition, CaretPosition> {
802 self.layout
803 .platform_layout
804 .caret_from_pixel_point(pixel_point, line_height)
805 }
806
807 pub fn visual_position_for_byte_index(
813 &self,
814 byte_index: usize,
815 line_height: Pixels,
816 ) -> Option<Point<Pixels>> {
817 if byte_index > self.len() {
818 return None;
819 }
820
821 self.visual_position_for_caret(
822 CaretPosition::attached_to_next_cluster(byte_index),
823 line_height,
824 )
825 }
826
827 pub fn visual_position_for_caret(
829 &self,
830 caret: CaretPosition,
831 line_height: Pixels,
832 ) -> Option<Point<Pixels>> {
833 self.layout
834 .platform_layout
835 .caret_bounds(caret, line_height)
836 .map(|bounds| bounds.origin)
837 }
838
839 pub fn normalized_caret(&self, caret: CaretPosition) -> CaretPosition {
841 self.layout.platform_layout.normalized_caret(caret)
842 }
843
844 pub fn adjacent_visual_caret(
848 &self,
849 caret: CaretPosition,
850 direction: VisualDirection,
851 ) -> Option<CaretPosition> {
852 self.layout
853 .platform_layout
854 .adjacent_visual_caret(caret, direction)
855 }
856
857 pub fn logical_cluster_before(&self, caret: CaretPosition) -> Option<Range<usize>> {
859 self.layout.platform_layout.logical_cluster_before(caret)
860 }
861
862 pub fn logical_cluster_after(&self, caret: CaretPosition) -> Option<Range<usize>> {
864 self.layout.platform_layout.logical_cluster_after(caret)
865 }
866
867 pub fn caret_movement(
872 &self,
873 caret: CaretPosition,
874 movement: TextMovement,
875 vertical_navigation_x: Option<Pixels>,
876 ) -> CaretMovement {
877 self.layout
878 .platform_layout
879 .caret_movement(caret, movement, vertical_navigation_x)
880 }
881
882 pub fn selection_movement(
894 &self,
895 selection: CaretSelection,
896 movement: TextMovement,
897 extend: bool,
898 vertical_navigation_x: Option<Pixels>,
899 line_height: Pixels,
900 ) -> CaretSelectionMovement {
901 let forward = movement.direction == TextDirection::Right;
902 let horizontal = matches!(
903 movement.direction,
904 TextDirection::Left | TextDirection::Right
905 ) && matches!(
906 movement.boundary,
907 TextBoundary::Cluster | TextBoundary::Word
908 );
909
910 if !extend && !selection.is_empty() && horizontal {
911 let caret_visual_position =
912 self.visual_position_for_caret(selection.caret, line_height);
913 let anchor_visual_position =
914 self.visual_position_for_caret(selection.anchor, line_height);
915 let (visual_start, visual_end) = caret_visual_position
916 .zip(anchor_visual_position)
917 .map(|(caret_visual_position, anchor_visual_position)| {
918 if (caret_visual_position.y, caret_visual_position.x)
919 <= (anchor_visual_position.y, anchor_visual_position.x)
920 {
921 (selection.caret, selection.anchor)
922 } else {
923 (selection.anchor, selection.caret)
924 }
925 })
926 .unwrap_or_else(|| {
927 if selection.caret.index <= selection.anchor.index {
928 (selection.caret, selection.anchor)
929 } else {
930 (selection.anchor, selection.caret)
931 }
932 });
933
934 let caret = if forward { visual_end } else { visual_start };
935
936 return CaretSelectionMovement {
937 result: caret.into(),
938 vertical_navigation_x: None,
939 };
940 }
941
942 let CaretMovement {
943 result: caret,
944 vertical_navigation_x,
945 } = self.caret_movement(selection.caret, movement, vertical_navigation_x);
946
947 CaretSelectionMovement {
948 result: if extend {
949 selection.with_caret(caret)
950 } else {
951 caret.into()
952 },
953 vertical_navigation_x,
954 }
955 }
956
957 pub fn selection_from_pixel_point(
959 &self,
960 pixel_point: Point<Pixels>,
961 line_height: Pixels,
962 kind: TextSelectionKind,
963 ) -> Range<usize> {
964 self.layout
965 .platform_layout
966 .selection_from_pixel_point(pixel_point, line_height, kind)
967 }
968
969 pub fn selection_bounds(
971 &self,
972 byte_range: Range<usize>,
973 line_height: Pixels,
974 ) -> SmallVec<[Bounds<Pixels>; 4]> {
975 let mut result = SmallVec::new();
976
977 if byte_range.is_empty() {
978 return result;
979 }
980
981 result.extend(
982 self.layout
983 .platform_layout
984 .selection_bounds(byte_range, line_height),
985 );
986 result
987 }
988}
989
990impl std::ops::Deref for ShapedTextLayout {
991 type Target = LineLayout;
992
993 fn deref(&self) -> &Self::Target {
994 &self.layout
995 }
996}
997
998pub(crate) struct LineLayoutCache {
999 previous_frame: Mutex<FrameCache>,
1000 current_frame: RwLock<FrameCache>,
1001 platform_text_system: Arc<dyn PlatformTextSystem>,
1002 font_generation: AtomicU64,
1003}
1004
1005#[derive(Default)]
1006struct FrameCache {
1007 lines: FrameLayouts<CacheKey, LineLayout>,
1008 shaped_texts: FrameLayouts<CacheKey, ShapedTextLayout>,
1009 inline_layouts: FrameLayouts<InlineCacheKey, InlineLayout>,
1010}
1011
1012struct FrameLayouts<Key, Value> {
1013 entries: FxHashMap<Arc<Key>, Arc<Value>>,
1014 used: Vec<Arc<Key>>,
1015}
1016
1017impl<Key, Value> Default for FrameLayouts<Key, Value> {
1018 fn default() -> Self {
1019 Self {
1020 entries: FxHashMap::default(),
1021 used: Vec::new(),
1022 }
1023 }
1024}
1025
1026impl<Key, Value> FrameLayouts<Key, Value>
1027where
1028 Key: Eq + Hash,
1029{
1030 fn get<Query>(&self, key: &Query) -> Option<&Arc<Value>>
1031 where
1032 Arc<Key>: Borrow<Query>,
1033 Query: Eq + Hash + ?Sized,
1034 {
1035 self.entries.get(key)
1036 }
1037
1038 fn remove_entry<Query>(&mut self, key: &Query) -> Option<(Arc<Key>, Arc<Value>)>
1039 where
1040 Arc<Key>: Borrow<Query>,
1041 Query: Eq + Hash + ?Sized,
1042 {
1043 self.entries.remove_entry(key)
1044 }
1045
1046 fn insert(&mut self, key: Arc<Key>, value: Arc<Value>) {
1047 self.entries.insert(key.clone(), value);
1048 self.used.push(key);
1049 }
1050
1051 fn reuse(&mut self, previous: &mut Self, range: Range<usize>) {
1052 let Some(keys) = previous.used.get(range) else {
1054 return;
1055 };
1056 for key in keys {
1057 if let Some(layout) = previous.entries.remove(key) {
1058 self.entries.insert(key.clone(), layout);
1059 }
1060
1061 self.used.push(key.clone());
1062 }
1063 }
1064
1065 fn clear(&mut self) {
1066 self.entries.clear();
1067 self.used.clear();
1068 }
1069}
1070
1071fn repaint_line_layout(layout: Arc<LineLayout>, runs: &[TextRun]) -> Arc<LineLayout> {
1072 let paint_matches = layout.paint_fragments.iter().all(|fragment| {
1073 runs.get(fragment.source_run)
1074 .is_some_and(|run| fragment.style == PaintStyle::from(run))
1075 });
1076
1077 if paint_matches {
1078 return layout;
1079 }
1080
1081 let mut repainted = (*layout).clone();
1082
1083 for fragment in &mut repainted.paint_fragments {
1084 if let Some(run) = runs.get(fragment.source_run) {
1085 fragment.style = PaintStyle::from(run);
1086 }
1087 }
1088
1089 Arc::new(repainted)
1090}
1091
1092fn repaint_shaped_text_layout(
1093 layout: Arc<ShapedTextLayout>,
1094 runs: &[TextRun],
1095) -> Arc<ShapedTextLayout> {
1096 let repainted = repaint_line_layout(layout.layout.clone(), runs);
1097
1098 if Arc::ptr_eq(&repainted, &layout.layout) {
1099 return layout;
1100 }
1101
1102 Arc::new(ShapedTextLayout {
1103 layout: repainted,
1104 wrap_width: layout.wrap_width,
1105 })
1106}
1107
1108#[derive(Clone, Default)]
1109pub(crate) struct LineLayoutIndex {
1110 lines_index: usize,
1111 shaped_texts_index: usize,
1112 inline_layouts_index: usize,
1113}
1114
1115impl LineLayoutIndex {
1116 pub(crate) fn rebased(&self, from: &Self, to: &Self) -> Self {
1120 let shift = |index: usize, from: usize, to: usize| index.saturating_sub(from) + to;
1121 Self {
1122 lines_index: shift(self.lines_index, from.lines_index, to.lines_index),
1123 shaped_texts_index: shift(
1124 self.shaped_texts_index,
1125 from.shaped_texts_index,
1126 to.shaped_texts_index,
1127 ),
1128 inline_layouts_index: shift(
1129 self.inline_layouts_index,
1130 from.inline_layouts_index,
1131 to.inline_layouts_index,
1132 ),
1133 }
1134 }
1135}
1136
1137impl LineLayoutCache {
1138 pub fn new(platform_text_system: Arc<dyn PlatformTextSystem>) -> Self {
1139 let font_generation = platform_text_system.font_generation();
1140 Self {
1141 previous_frame: Mutex::default(),
1142 current_frame: RwLock::default(),
1143 platform_text_system,
1144 font_generation: AtomicU64::new(font_generation),
1145 }
1146 }
1147
1148 pub fn layout_index(&self) -> LineLayoutIndex {
1149 let frame = self.current_frame.read();
1150 LineLayoutIndex {
1151 lines_index: frame.lines.used.len(),
1152 shaped_texts_index: frame.shaped_texts.used.len(),
1153 inline_layouts_index: frame.inline_layouts.used.len(),
1154 }
1155 }
1156
1157 pub fn clear(&self) {
1159 *self.previous_frame.lock() = FrameCache::default();
1160 *self.current_frame.write() = FrameCache::default();
1161 }
1162
1163 fn sync_font_generation(&self) {
1164 let generation = self.platform_text_system.font_generation();
1165 if self.font_generation.load(Ordering::Acquire) != generation {
1166 self.clear();
1167 self.font_generation.store(generation, Ordering::Release);
1168 }
1169 }
1170
1171 pub fn reuse_layouts(&self, range: Range<LineLayoutIndex>) {
1172 let mut previous_frame = &mut *self.previous_frame.lock();
1173 let mut current_frame = &mut *self.current_frame.write();
1174
1175 current_frame.lines.reuse(
1176 &mut previous_frame.lines,
1177 range.start.lines_index..range.end.lines_index,
1178 );
1179 current_frame.shaped_texts.reuse(
1180 &mut previous_frame.shaped_texts,
1181 range.start.shaped_texts_index..range.end.shaped_texts_index,
1182 );
1183 current_frame.inline_layouts.reuse(
1184 &mut previous_frame.inline_layouts,
1185 range.start.inline_layouts_index..range.end.inline_layouts_index,
1186 );
1187 }
1188
1189 pub fn truncate_layouts(&self, index: LineLayoutIndex) {
1190 let mut current_frame = &mut *self.current_frame.write();
1191 current_frame.lines.used.truncate(index.lines_index);
1192 current_frame
1193 .shaped_texts
1194 .used
1195 .truncate(index.shaped_texts_index);
1196 current_frame
1197 .inline_layouts
1198 .used
1199 .truncate(index.inline_layouts_index);
1200 }
1201
1202 pub fn finish_frame(&self) {
1203 let mut prev_frame = self.previous_frame.lock();
1204 let mut curr_frame = self.current_frame.write();
1205 std::mem::swap(&mut *prev_frame, &mut *curr_frame);
1206 curr_frame.lines.clear();
1207 curr_frame.shaped_texts.clear();
1208 curr_frame.inline_layouts.clear();
1209 }
1210
1211 pub fn layout_text_with_options<Text>(
1212 &self,
1213 text: Text,
1214 font_size: Pixels,
1215 runs: &[TextRun],
1216 options: TextLayoutOptions,
1217 ) -> Arc<ShapedTextLayout>
1218 where
1219 Text: AsRef<str>,
1220 SharedString: From<Text>,
1221 {
1222 self.sync_font_generation();
1223 let wrap_width = options.wrap_width;
1224 let shaping_runs = runs.iter().map(ShapingRun::from).collect::<SmallVec<_>>();
1225 let key = &CacheKeyRef {
1226 text: text.as_ref(),
1227 font_size,
1228 runs: &shaping_runs,
1229 options,
1230 } as &dyn AsCacheKeyRef;
1231
1232 let current_frame = self.current_frame.upgradable_read();
1233 if let Some(layout) = current_frame.shaped_texts.get(key) {
1234 return repaint_shaped_text_layout(layout.clone(), runs);
1235 }
1236
1237 let previous_frame_entry = self.previous_frame.lock().shaped_texts.remove_entry(key);
1238 if let Some((key, layout)) = previous_frame_entry {
1239 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1240 current_frame.shaped_texts.insert(key, layout.clone());
1241 repaint_shaped_text_layout(layout, runs)
1242 } else {
1243 drop(current_frame);
1244 let text = SharedString::from(text);
1245 let document_layout = if options != TextLayoutOptions::default() || text.contains('\n')
1246 {
1247 Arc::new(self.platform_text_system.layout_text(TextLayoutRequest {
1248 text: &text,
1249 font_size,
1250 runs,
1251 options,
1252 }))
1253 } else {
1254 self.layout_line::<&SharedString>(&text, font_size, runs)
1255 };
1256
1257 let layout = Arc::new(ShapedTextLayout {
1258 layout: document_layout,
1259 wrap_width,
1260 });
1261 let key = Arc::new(CacheKey {
1262 text,
1263 font_size,
1264 runs: shaping_runs,
1265 options,
1266 });
1267
1268 let mut current_frame = self.current_frame.write();
1269 current_frame.shaped_texts.insert(key, layout.clone());
1270
1271 layout
1272 }
1273 }
1274
1275 pub fn layout_line<Text>(
1276 &self,
1277 text: Text,
1278 font_size: Pixels,
1279 runs: &[TextRun],
1280 ) -> Arc<LineLayout>
1281 where
1282 Text: AsRef<str>,
1283 SharedString: From<Text>,
1284 {
1285 self.sync_font_generation();
1286 let shaping_runs = runs.iter().map(ShapingRun::from).collect::<SmallVec<_>>();
1287 let key = &CacheKeyRef {
1288 text: text.as_ref(),
1289 font_size,
1290 runs: &shaping_runs,
1291 options: TextLayoutOptions::default(),
1292 } as &dyn AsCacheKeyRef;
1293
1294 let current_frame = self.current_frame.upgradable_read();
1295 if let Some(layout) = current_frame.lines.get(key) {
1296 return repaint_line_layout(layout.clone(), runs);
1297 }
1298
1299 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1300 if let Some((key, layout)) = self.previous_frame.lock().lines.remove_entry(key) {
1301 current_frame.lines.insert(key, layout.clone());
1302 repaint_line_layout(layout, runs)
1303 } else {
1304 let text = SharedString::from(text);
1305 let layout = self.platform_text_system.layout_text(TextLayoutRequest {
1306 text: &text,
1307 font_size,
1308 runs,
1309 options: TextLayoutOptions::default(),
1310 });
1311 let key = Arc::new(CacheKey {
1312 text,
1313 font_size,
1314 runs: shaping_runs,
1315 options: TextLayoutOptions::default(),
1316 });
1317 let layout = Arc::new(layout);
1318 current_frame.lines.insert(key, layout.clone());
1319 layout
1320 }
1321 }
1322
1323 pub fn layout_inline(&self, request: InlineLayoutRequest<'_>) -> Arc<InlineLayout> {
1324 self.sync_font_generation();
1325 let key = &request as &dyn AsInlineCacheKeyRef;
1326 let current_frame = self.current_frame.upgradable_read();
1327
1328 if let Some(layout) = current_frame.inline_layouts.get(key) {
1329 return layout.clone();
1330 }
1331
1332 let previous_frame_entry = self.previous_frame.lock().inline_layouts.remove_entry(key);
1333
1334 if let Some((key, layout)) = previous_frame_entry {
1335 let mut current_frame = RwLockUpgradableReadGuard::upgrade(current_frame);
1336 current_frame.inline_layouts.insert(key, layout.clone());
1337
1338 return layout;
1339 }
1340
1341 drop(current_frame);
1342 let layout = Arc::new(self.platform_text_system.layout_inline(request));
1343 let key = Arc::new(InlineCacheKey::from(request));
1344
1345 let mut current_frame = self.current_frame.write();
1346 current_frame.inline_layouts.insert(key, layout.clone());
1347
1348 layout
1349 }
1350}
1351
1352trait AsInlineCacheKeyRef {
1353 fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_>;
1354}
1355
1356#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1357struct InlineCacheKey {
1358 text: SharedString,
1359 runs: SmallVec<[TextRun; 1]>,
1360 text_styles: Vec<InlineTextStyle>,
1361 boxes: Vec<InlineBoxRequest>,
1362 font_size: Pixels,
1363 line_height: Pixels,
1364 text_metrics: InlineTextMetrics,
1365 options: TextLayoutOptions,
1366 bidi_scopes: Vec<InlineBidiScope>,
1367}
1368
1369impl From<InlineLayoutRequest<'_>> for InlineCacheKey {
1370 fn from(request: InlineLayoutRequest<'_>) -> Self {
1371 Self {
1372 text: SharedString::new(request.text),
1373 runs: SmallVec::from(request.runs),
1374 text_styles: request.text_styles.to_vec(),
1375 boxes: request.boxes.to_vec(),
1376 font_size: request.font_size,
1377 line_height: request.line_height,
1378 text_metrics: request.text_metrics,
1379 options: request.options,
1380 bidi_scopes: request.bidi_scopes.to_vec(),
1381 }
1382 }
1383}
1384
1385impl AsInlineCacheKeyRef for InlineCacheKey {
1386 fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_> {
1387 InlineLayoutRequest {
1388 text: &self.text,
1389 runs: &self.runs,
1390 text_styles: &self.text_styles,
1391 boxes: &self.boxes,
1392 font_size: self.font_size,
1393 line_height: self.line_height,
1394 text_metrics: self.text_metrics,
1395 options: self.options,
1396 bidi_scopes: &self.bidi_scopes,
1397 }
1398 }
1399}
1400
1401impl AsInlineCacheKeyRef for InlineLayoutRequest<'_> {
1402 fn as_inline_cache_key_ref(&self) -> InlineLayoutRequest<'_> {
1403 *self
1404 }
1405}
1406
1407impl PartialEq for dyn AsInlineCacheKeyRef + '_ {
1408 fn eq(&self, other: &dyn AsInlineCacheKeyRef) -> bool {
1409 self.as_inline_cache_key_ref() == other.as_inline_cache_key_ref()
1410 }
1411}
1412
1413impl Eq for dyn AsInlineCacheKeyRef + '_ {}
1414
1415impl Hash for dyn AsInlineCacheKeyRef + '_ {
1416 fn hash<H: Hasher>(&self, state: &mut H) {
1417 self.as_inline_cache_key_ref().hash(state);
1418 }
1419}
1420
1421impl<'a> Borrow<dyn AsInlineCacheKeyRef + 'a> for Arc<InlineCacheKey> {
1422 fn borrow(&self) -> &(dyn AsInlineCacheKeyRef + 'a) {
1423 self.as_ref() as &dyn AsInlineCacheKeyRef
1424 }
1425}
1426
1427trait AsCacheKeyRef {
1428 fn as_cache_key_ref(&self) -> CacheKeyRef<'_>;
1429}
1430
1431#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1432struct CacheKey {
1433 text: SharedString,
1434 font_size: Pixels,
1435 runs: SmallVec<[ShapingRun; 1]>,
1436 options: TextLayoutOptions,
1437}
1438
1439#[derive(Copy, Clone, PartialEq, Eq, Hash)]
1440struct CacheKeyRef<'a> {
1441 text: &'a str,
1442 font_size: Pixels,
1443 runs: &'a [ShapingRun],
1444 options: TextLayoutOptions,
1445}
1446
1447#[derive(Clone, Debug, Eq, Hash, PartialEq)]
1448struct ShapingRun {
1449 len: usize,
1450 font: crate::Font,
1451 letter_spacing: Option<Pixels>,
1452}
1453
1454impl From<&TextRun> for ShapingRun {
1455 fn from(run: &TextRun) -> Self {
1456 Self {
1457 len: run.len,
1458 font: run.font.clone(),
1459 letter_spacing: run.letter_spacing,
1460 }
1461 }
1462}
1463
1464impl PartialEq for dyn AsCacheKeyRef + '_ {
1465 fn eq(&self, other: &dyn AsCacheKeyRef) -> bool {
1466 self.as_cache_key_ref() == other.as_cache_key_ref()
1467 }
1468}
1469
1470impl Eq for dyn AsCacheKeyRef + '_ {}
1471
1472impl Hash for dyn AsCacheKeyRef + '_ {
1473 fn hash<H: Hasher>(&self, state: &mut H) {
1474 self.as_cache_key_ref().hash(state)
1475 }
1476}
1477
1478impl AsCacheKeyRef for CacheKey {
1479 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1480 CacheKeyRef {
1481 text: &self.text,
1482 font_size: self.font_size,
1483 runs: self.runs.as_slice(),
1484 options: self.options,
1485 }
1486 }
1487}
1488
1489impl<'a> Borrow<dyn AsCacheKeyRef + 'a> for Arc<CacheKey> {
1490 fn borrow(&self) -> &(dyn AsCacheKeyRef + 'a) {
1491 self.as_ref() as &dyn AsCacheKeyRef
1492 }
1493}
1494
1495impl AsCacheKeyRef for CacheKeyRef<'_> {
1496 fn as_cache_key_ref(&self) -> CacheKeyRef<'_> {
1497 *self
1498 }
1499}
1500
1501#[cfg(test)]
1502mod tests {
1503 use super::*;
1504 use crate::{point, px, size};
1505
1506 #[test]
1507 fn caret_selection_endpoint_ordering_uses_byte_indices() {
1508 for (caret, anchor, expected) in [
1509 (
1510 CaretPosition::attached_to_next_cluster(2),
1511 CaretPosition::attached_to_next_cluster(5),
1512 std::cmp::Ordering::Less,
1513 ),
1514 (
1515 CaretPosition::attached_to_next_cluster(5),
1516 CaretPosition::attached_to_previous_cluster(5),
1517 std::cmp::Ordering::Equal,
1518 ),
1519 (
1520 CaretPosition::attached_to_previous_cluster(8),
1521 CaretPosition::attached_to_next_cluster(5),
1522 std::cmp::Ordering::Greater,
1523 ),
1524 ] {
1525 let selection = CaretSelection { anchor, caret };
1526 assert_eq!(selection.endpoint_ordering(), expected);
1527 }
1528 }
1529
1530 #[test]
1531 fn paragraph_separators_match_unicode_bidi_class_b() {
1532 for character in [
1533 '\n', '\r', '\u{001c}', '\u{001d}', '\u{001e}', '\u{0085}', '\u{2029}',
1534 ] {
1535 assert!(is_paragraph_separator(character));
1536 }
1537
1538 for character in [' ', '\u{2028}'] {
1539 assert!(!is_paragraph_separator(character));
1540 }
1541 }
1542
1543 #[test]
1544 fn inserted_text_affinity_tracks_trailing_paragraph_separators() {
1545 for (text, expected) in [
1546 ("", CaretAffinity::Downstream),
1547 ("ordinary text", CaretAffinity::Upstream),
1548 ("\n", CaretAffinity::Downstream),
1549 ("\r", CaretAffinity::Downstream),
1550 ("\u{001c}", CaretAffinity::Downstream),
1551 ("\u{001d}", CaretAffinity::Downstream),
1552 ("\u{001e}", CaretAffinity::Downstream),
1553 ("\u{0085}", CaretAffinity::Downstream),
1554 ("\u{2028}", CaretAffinity::Downstream),
1555 ("\u{2029}", CaretAffinity::Downstream),
1556 ("\nordinary text", CaretAffinity::Upstream),
1557 ] {
1558 assert_eq!(CaretAffinity::from(text), expected, "From({text:?})");
1559 assert_eq!(
1560 CaretAffinity::for_inserted_text(text),
1561 expected,
1562 "for_inserted_text({text:?})"
1563 );
1564 }
1565 }
1566
1567 #[test]
1568 fn inline_box_alignment_preserves_unsorted_boxes_and_empty_lines() {
1569 let mut lines = [InlineVisualLine::default(); 3];
1570 let mut boxes =
1571 [(20, 2, 14.), (10, 0, 18.), (90, 3, 100.)].map(|(id, line_index, height)| {
1572 PositionedInlineBox {
1573 id,
1574 line_index,
1575 bounds: Bounds::new(point(px(7.), px(99.)), size(px(5.), px(height))),
1576 }
1577 });
1578 let mut layout_size = size(px(100.), px(999.));
1579 let original_lines = lines;
1580 let original_boxes = boxes;
1581
1582 let requests = [InlineBoxRequest {
1583 id: 20,
1584 index: 0,
1585 size: boxes[0].bounds.size,
1586 vertical_align: VerticalAlign::Top,
1587 }];
1588 let metrics = InlineTextMetrics {
1589 ascent: px(8.),
1590 descent: px(2.),
1591 x_height: px(4.),
1592 };
1593
1594 align_inline_boxes(
1595 &mut lines,
1596 &mut boxes,
1597 &mut layout_size,
1598 &requests,
1599 &[metrics],
1600 &[(px(-7.), px(3.)), (px(-11.), px(4.))],
1601 metrics,
1602 px(12.),
1603 );
1604
1605 for ((line, mut expected_line), (origin_y, height, baseline)) in lines
1606 .iter()
1607 .zip(original_lines)
1608 .zip([(0., 21., 18.), (21., 15., 11.), (36., 14., 9.)])
1609 {
1610 expected_line.origin.y = px(origin_y);
1611 expected_line.size.height = px(height);
1612 expected_line.baseline = px(baseline);
1613 assert_eq!(*line, expected_line);
1614 }
1615
1616 for ((inline_box, mut expected_box), origin_y) in
1617 boxes.iter().zip(original_boxes).zip([36., 0., 99.])
1618 {
1619 expected_box.bounds.origin.y = px(origin_y);
1620 assert_eq!(*inline_box, expected_box);
1621 }
1622
1623 assert_eq!(layout_size, size(px(100.), px(50.)));
1624 }
1625}