1use core::{ops::Range, str::FromStr as _};
2use std::sync::Arc;
3
4use super::{
5 cursor::{CCursor, LayoutCursor},
6 font::UvRect,
7 index::{ByteIndex, ByteRange, ByteRangeExt as _, CharIndex},
8};
9use crate::{Color32, FontId, Mesh, Stroke, text::FontsView};
10use emath::{Align, GuiRounding as _, NumExt as _, OrderedFloat, Pos2, Rect, Vec2, pos2, vec2};
11pub use font_types::Tag;
12use smallvec::SmallVec;
13
14#[derive(Clone, Debug, PartialEq)]
48#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
49pub struct LayoutJob {
50 pub text: String,
52
53 pub sections: Vec<LayoutSection>,
61
62 pub wrap: TextWrapping,
64
65 pub first_row_min_height: f32,
71
72 pub break_on_newline: bool,
80
81 pub halign: Align,
83
84 pub justify: bool,
86
87 pub round_output_to_gui: bool,
89
90 pub keep_trailing_whitespace: bool,
97}
98
99impl Default for LayoutJob {
100 #[inline]
101 fn default() -> Self {
102 Self {
103 text: Default::default(),
104 sections: Default::default(),
105 wrap: Default::default(),
106 first_row_min_height: 0.0,
107 break_on_newline: true,
108 halign: Align::LEFT,
109 justify: false,
110 round_output_to_gui: true,
111 keep_trailing_whitespace: false,
112 }
113 }
114}
115
116impl LayoutJob {
117 #[inline]
119 pub fn clear(&mut self) {
120 self.text.clear();
121 self.sections.clear();
122 }
123
124 #[inline]
126 pub fn simple(text: String, font_id: FontId, color: Color32, wrap_width: f32) -> Self {
127 Self {
128 sections: vec![LayoutSection {
129 leading_space: 0.0,
130 byte_range: ByteRange::full(&text),
131 format: TextFormat::simple(font_id, color),
132 }],
133 text,
134 wrap: TextWrapping {
135 max_width: wrap_width,
136 ..Default::default()
137 },
138 break_on_newline: true,
139 ..Default::default()
140 }
141 }
142
143 #[inline]
145 pub fn simple_format(text: String, format: TextFormat) -> Self {
146 Self {
147 sections: vec![LayoutSection {
148 leading_space: 0.0,
149 byte_range: ByteRange::full(&text),
150 format,
151 }],
152 text,
153 break_on_newline: true,
154 ..Default::default()
155 }
156 }
157
158 #[inline]
160 pub fn simple_singleline(text: String, font_id: FontId, color: Color32) -> Self {
161 Self {
162 sections: vec![LayoutSection {
163 leading_space: 0.0,
164 byte_range: ByteRange::full(&text),
165 format: TextFormat::simple(font_id, color),
166 }],
167 text,
168 wrap: Default::default(),
169 break_on_newline: false,
170 ..Default::default()
171 }
172 }
173
174 #[inline]
175 pub fn single_section(text: String, format: TextFormat) -> Self {
176 Self {
177 sections: vec![LayoutSection {
178 leading_space: 0.0,
179 byte_range: ByteRange::full(&text),
180 format,
181 }],
182 text,
183 wrap: Default::default(),
184 break_on_newline: true,
185 ..Default::default()
186 }
187 }
188
189 #[inline]
190 pub fn is_empty(&self) -> bool {
191 self.sections.is_empty()
192 }
193
194 pub fn append(&mut self, text: &str, leading_space: f32, format: TextFormat) {
201 let start = self.text.len();
202 self.text += text;
203 let byte_range = ByteIndex(start)..ByteIndex(self.text.len());
204
205 if leading_space == 0.0
208 && let Some(last) = self.sections.last_mut()
209 && last.format == format
210 {
211 last.byte_range.end = byte_range.end;
212 return;
213 }
214
215 self.sections.push(LayoutSection {
216 leading_space,
217 byte_range,
218 format,
219 });
220 }
221
222 pub fn format_at_byte(&self, byte_idx: ByteIndex) -> &TextFormat {
229 self.debug_sanity_check();
230 let last = self.sections.last().expect("LayoutJob has no sections");
231 let idx = self
232 .sections
233 .partition_point(|section| section.byte_range.end <= byte_idx);
234 let section = self.sections.get(idx).unwrap_or(last);
235 §ion.format
236 }
237
238 #[cfg_attr(not(debug_assertions), expect(clippy::unused_self))]
243 pub fn debug_sanity_check(&self) {
244 #[cfg(debug_assertions)]
245 {
246 if self.sections.is_empty() {
247 assert!(
248 self.text.is_empty(),
249 "LayoutJob has text but no sections: {:?}",
250 self.text
251 );
252 return;
253 }
254
255 assert_eq!(
256 self.sections
257 .first()
258 .expect("checked above")
259 .byte_range
260 .start,
261 ByteIndex::ZERO,
262 "First LayoutSection must start at byte 0"
263 );
264 assert_eq!(
265 self.sections.last().expect("checked above").byte_range.end,
266 ByteIndex(self.text.len()),
267 "Last LayoutSection must end at the end of the text"
268 );
269
270 for section in &self.sections {
271 let Range { start, end } = section.byte_range;
272 assert!(start <= end, "LayoutSection has a reversed byte_range");
273 }
274 for (prev, next) in core::iter::zip(&self.sections, self.sections.iter().skip(1)) {
275 assert_eq!(
276 prev.byte_range.end, next.byte_range.start,
277 "LayoutSections must be ordered with no gaps and no overlaps"
278 );
279 }
280 }
281 }
282
283 pub fn font_height(&self, fonts: &mut FontsView<'_>) -> f32 {
287 let mut max_height = 0.0_f32;
288 for section in &self.sections {
289 max_height = max_height.max(fonts.row_height(§ion.format.font_id));
290 }
291 max_height
292 }
293
294 pub fn effective_wrap_width(&self) -> f32 {
296 if self.round_output_to_gui {
297 self.wrap.max_width + 0.5
301 } else {
302 self.wrap.max_width
303 }
304 }
305}
306
307impl core::hash::Hash for LayoutJob {
308 #[inline]
309 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
310 let Self {
311 text,
312 sections,
313 wrap,
314 first_row_min_height,
315 break_on_newline,
316 halign,
317 justify,
318 round_output_to_gui,
319 keep_trailing_whitespace,
320 } = self;
321
322 text.hash(state);
323 sections.hash(state);
324 wrap.hash(state);
325 emath::OrderedFloat(*first_row_min_height).hash(state);
326 break_on_newline.hash(state);
327 halign.hash(state);
328 justify.hash(state);
329 round_output_to_gui.hash(state);
330 keep_trailing_whitespace.hash(state);
331 }
332}
333
334#[derive(Clone, Debug, PartialEq)]
346#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
347pub struct LayoutSection {
348 pub leading_space: f32,
350
351 pub byte_range: ByteRange,
353
354 pub format: TextFormat,
356}
357
358impl core::hash::Hash for LayoutSection {
359 #[inline]
360 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
361 let Self {
362 leading_space,
363 byte_range,
364 format,
365 } = self;
366 OrderedFloat(*leading_space).hash(state);
367 byte_range.hash(state);
368 format.hash(state);
369 }
370}
371
372pub trait IntoTag {
376 fn into_tag(self) -> font_types::Tag;
377}
378
379impl IntoTag for font_types::Tag {
380 #[inline(always)]
381 fn into_tag(self) -> font_types::Tag {
382 self
383 }
384}
385
386impl IntoTag for u32 {
387 #[inline(always)]
388 fn into_tag(self) -> font_types::Tag {
389 font_types::Tag::from_u32(self)
390 }
391}
392
393impl IntoTag for [u8; 4] {
394 #[inline(always)]
395 fn into_tag(self) -> font_types::Tag {
396 font_types::Tag::new_checked(&self).expect("Invalid variation axis tag")
397 }
398}
399
400impl IntoTag for &[u8; 4] {
401 #[inline(always)]
402 fn into_tag(self) -> font_types::Tag {
403 font_types::Tag::new_checked(self).expect("Invalid variation axis tag")
404 }
405}
406
407impl IntoTag for &str {
408 #[inline(always)]
409 fn into_tag(self) -> font_types::Tag {
410 font_types::Tag::from_str(self).expect("Invalid variation axis tag")
411 }
412}
413
414#[derive(Clone, Debug, PartialEq, Default)]
417#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
418pub struct VariationCoords(SmallVec<[(font_types::Tag, f32); 2]>);
419
420impl VariationCoords {
421 pub fn new<T: IntoTag>(values: impl IntoIterator<Item = (T, f32)>) -> Self {
433 Self(values.into_iter().map(|(t, c)| (t.into_tag(), c)).collect())
434 }
435
436 #[inline(always)]
438 pub fn push(&mut self, tag: impl IntoTag, coord: f32) {
439 self.0.push((tag.into_tag(), coord));
440 }
441
442 pub fn remove(&mut self, index: usize) {
444 self.0.remove(index);
445 }
446
447 pub fn clear(&mut self) {
448 self.0.clear();
449 }
450}
451
452impl AsRef<[(font_types::Tag, f32)]> for VariationCoords {
453 #[inline(always)]
454 fn as_ref(&self) -> &[(font_types::Tag, f32)] {
455 &self.0
456 }
457}
458
459impl AsMut<[(font_types::Tag, f32)]> for VariationCoords {
460 fn as_mut(&mut self) -> &mut [(font_types::Tag, f32)] {
461 &mut self.0
462 }
463}
464
465impl core::hash::Hash for VariationCoords {
466 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
467 self.0.len().hash(state);
468 for (tag, coord) in &self.0 {
469 tag.hash(state);
470 OrderedFloat(*coord).hash(state);
471 }
472 }
473}
474
475#[derive(Clone, Debug, PartialEq)]
477#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
478pub struct TextFormat {
479 pub font_id: FontId,
480
481 pub extra_letter_spacing: f32,
485
486 pub line_height: Option<f32>,
494
495 pub color: Color32,
497
498 pub background: Color32,
499
500 pub expand_bg: f32,
504
505 pub coords: VariationCoords,
506
507 pub italics: bool,
508
509 pub underline: Stroke,
510
511 pub strikethrough: Stroke,
512
513 pub valign: Align,
523}
524
525impl Default for TextFormat {
526 #[inline]
527 fn default() -> Self {
528 Self {
529 font_id: FontId::default(),
530 extra_letter_spacing: 0.0,
531 line_height: None,
532 color: Color32::GRAY,
533 background: Color32::TRANSPARENT,
534 expand_bg: 1.0,
535 coords: VariationCoords::default(),
536 italics: false,
537 underline: Stroke::NONE,
538 strikethrough: Stroke::NONE,
539 valign: Align::BOTTOM,
540 }
541 }
542}
543
544impl core::hash::Hash for TextFormat {
545 #[inline]
546 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
547 let Self {
548 font_id,
549 extra_letter_spacing,
550 line_height,
551 color,
552 background,
553 expand_bg,
554 coords,
555 italics,
556 underline,
557 strikethrough,
558 valign,
559 } = self;
560 font_id.hash(state);
561 emath::OrderedFloat(*extra_letter_spacing).hash(state);
562 if let Some(line_height) = *line_height {
563 emath::OrderedFloat(line_height).hash(state);
564 }
565 color.hash(state);
566 background.hash(state);
567 emath::OrderedFloat(*expand_bg).hash(state);
568 coords.hash(state);
569 italics.hash(state);
570 underline.hash(state);
571 strikethrough.hash(state);
572 valign.hash(state);
573 }
574}
575
576impl TextFormat {
577 #[inline]
578 pub fn simple(font_id: FontId, color: Color32) -> Self {
579 Self {
580 font_id,
581 color,
582 ..Default::default()
583 }
584 }
585}
586
587#[derive(Clone, Copy, Debug, PartialEq, Eq)]
593#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
594pub enum TextWrapMode {
595 Extend,
597
598 Wrap,
600
601 Truncate,
605}
606
607#[derive(Clone, Debug, PartialEq)]
609#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
610pub struct TextWrapping {
611 pub max_width: f32,
620
621 pub max_rows: usize,
635
636 pub break_anywhere: bool,
645
646 pub overflow_character: Option<char>,
652}
653
654impl core::hash::Hash for TextWrapping {
655 #[inline]
656 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
657 let Self {
658 max_width,
659 max_rows,
660 break_anywhere,
661 overflow_character,
662 } = self;
663 emath::OrderedFloat(*max_width).hash(state);
664 max_rows.hash(state);
665 break_anywhere.hash(state);
666 overflow_character.hash(state);
667 }
668}
669
670impl Default for TextWrapping {
671 fn default() -> Self {
672 Self {
673 max_width: f32::INFINITY,
674 max_rows: usize::MAX,
675 break_anywhere: false,
676 overflow_character: Some('…'),
677 }
678 }
679}
680
681impl TextWrapping {
682 pub fn from_wrap_mode_and_width(mode: TextWrapMode, max_width: f32) -> Self {
684 match mode {
685 TextWrapMode::Extend => Self::no_max_width(),
686 TextWrapMode::Wrap => Self::wrap_at_width(max_width),
687 TextWrapMode::Truncate => Self::truncate_at_width(max_width),
688 }
689 }
690
691 pub fn no_max_width() -> Self {
693 Self {
694 max_width: f32::INFINITY,
695 ..Default::default()
696 }
697 }
698
699 pub fn wrap_at_width(max_width: f32) -> Self {
701 Self {
702 max_width,
703 ..Default::default()
704 }
705 }
706
707 pub fn truncate_at_width(max_width: f32) -> Self {
709 Self {
710 max_width,
711 max_rows: 1,
712 break_anywhere: true,
713 ..Default::default()
714 }
715 }
716}
717
718#[derive(Clone, Debug, PartialEq)]
735#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
736pub struct Galley {
737 pub job: Arc<LayoutJob>,
740
741 pub rows: Vec<PlacedRow>,
749
750 pub elided: bool,
752
753 pub rect: Rect,
762
763 pub mesh_bounds: Rect,
766
767 pub num_vertices: usize,
769
770 pub num_indices: usize,
772
773 pub pixels_per_point: f32,
778
779 pub(crate) intrinsic_size: Vec2,
780}
781
782#[derive(Clone, Debug, PartialEq)]
783#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
784pub struct PlacedRow {
785 pub pos: Pos2,
789
790 pub row: Arc<Row>,
792
793 pub ends_with_newline: bool,
799}
800
801impl PlacedRow {
802 pub fn rect(&self) -> Rect {
806 Rect::from_min_size(self.pos, self.row.size)
807 }
808
809 pub fn rect_without_leading_space(&self) -> Rect {
811 let x = self.pos.x + self.glyphs.first().map_or(0.0, |g| g.pos.x);
812 let right = self.pos.x + self.size.x;
813 Rect::from_min_max(
814 Pos2::new(x, self.pos.y),
815 Pos2::new(right, self.pos.y + self.size.y),
816 )
817 }
818}
819
820impl core::ops::Deref for PlacedRow {
821 type Target = Row;
822
823 fn deref(&self) -> &Self::Target {
824 &self.row
825 }
826}
827
828#[derive(Clone, Debug, PartialEq)]
829#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
830pub struct Row {
831 pub(crate) section_index_at_start: u32,
837
838 pub glyphs: Vec<Glyph>,
840
841 pub size: Vec2,
844
845 pub visuals: RowVisuals,
847}
848
849#[derive(Clone, Debug, PartialEq, Eq)]
851#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
852pub struct RowVisuals {
853 pub mesh: Mesh,
856
857 pub mesh_bounds: Rect,
860
861 pub glyph_index_start: usize,
866
867 pub glyph_vertex_range: Range<usize>,
871}
872
873impl Default for RowVisuals {
874 fn default() -> Self {
875 Self {
876 mesh: Default::default(),
877 mesh_bounds: Rect::NOTHING,
878 glyph_index_start: 0,
879 glyph_vertex_range: 0..0,
880 }
881 }
882}
883
884#[derive(Copy, Clone, Debug, PartialEq)]
885#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
886pub struct Glyph {
887 pub chr: char,
889
890 pub pos: Pos2,
893
894 pub advance_width: f32,
896
897 pub line_height: f32,
902
903 pub font_ascent: f32,
905
906 pub font_height: f32,
908
909 pub font_face_ascent: f32,
911
912 pub font_face_height: f32,
914
915 pub uv_rect: UvRect,
917
918 pub(crate) section_index: u32,
924
925 pub first_vertex: u32,
927}
928
929impl Glyph {
930 #[inline]
931 pub fn size(&self) -> Vec2 {
932 Vec2::new(self.advance_width, self.line_height)
933 }
934
935 #[inline]
936 pub fn max_x(&self) -> f32 {
937 self.pos.x + self.advance_width
938 }
939
940 #[inline]
942 pub fn logical_rect(&self) -> Rect {
943 Rect::from_min_size(self.pos - vec2(0.0, self.font_ascent), self.size())
944 }
945}
946
947impl Row {
950 pub fn text(&self) -> String {
952 self.glyphs.iter().map(|g| g.chr).collect()
953 }
954
955 #[inline]
957 pub fn char_count_excluding_newline(&self) -> CharIndex {
958 CharIndex(self.glyphs.len())
959 }
960
961 pub fn char_at(&self, desired_x: f32) -> CharIndex {
964 for (i, glyph) in self.glyphs.iter().enumerate() {
965 if desired_x < glyph.logical_rect().center().x {
966 return CharIndex(i);
967 }
968 }
969 self.char_count_excluding_newline()
970 }
971
972 pub fn x_offset(&self, column: CharIndex) -> f32 {
973 if let Some(glyph) = self.glyphs.get(column.0) {
974 glyph.pos.x
975 } else {
976 self.size.x
977 }
978 }
979
980 #[inline]
981 pub fn height(&self) -> f32 {
982 self.size.y
983 }
984}
985
986impl PlacedRow {
987 #[inline]
988 pub fn min_y(&self) -> f32 {
989 self.rect().top()
990 }
991
992 #[inline]
993 pub fn max_y(&self) -> f32 {
994 self.rect().bottom()
995 }
996
997 #[inline]
999 pub fn char_count_including_newline(&self) -> CharIndex {
1000 CharIndex(self.row.glyphs.len() + (self.ends_with_newline as usize))
1001 }
1002}
1003
1004impl Galley {
1005 #[inline]
1006 pub fn is_empty(&self) -> bool {
1007 self.job.is_empty()
1008 }
1009
1010 #[inline]
1012 pub fn text(&self) -> &str {
1013 &self.job.text
1014 }
1015
1016 #[inline]
1017 pub fn size(&self) -> Vec2 {
1018 self.rect.size()
1019 }
1020
1021 #[inline]
1026 pub fn intrinsic_size(&self) -> Vec2 {
1027 if self.job.round_output_to_gui {
1030 self.intrinsic_size.round_ui()
1031 } else {
1032 self.intrinsic_size
1033 }
1034 }
1035
1036 pub(crate) fn round_output_to_gui(&mut self) {
1037 for placed_row in &mut self.rows {
1038 let rounded_size = placed_row.row.size.round_ui();
1040 if placed_row.row.size != rounded_size {
1041 Arc::make_mut(&mut placed_row.row).size = rounded_size;
1042 }
1043 }
1044
1045 let rect = &mut self.rect;
1046
1047 let did_exceed_wrap_width_by_a_lot = rect.width() > self.job.wrap.max_width + 1.0;
1048
1049 *rect = rect.round_ui();
1050
1051 if did_exceed_wrap_width_by_a_lot {
1052 } else {
1055 rect.max.x = rect
1057 .max
1058 .x
1059 .at_most(rect.min.x + self.job.wrap.max_width)
1060 .floor_ui();
1061 }
1062 }
1063
1064 pub fn concat(job: Arc<LayoutJob>, galleys: &[Arc<Self>], pixels_per_point: f32) -> Self {
1066 profiling::function_scope!();
1067
1068 let mut merged_galley = Self {
1069 job,
1070 rows: Vec::new(),
1071 elided: false,
1072 rect: Rect::ZERO,
1073 mesh_bounds: Rect::NOTHING,
1074 num_vertices: 0,
1075 num_indices: 0,
1076 pixels_per_point,
1077 intrinsic_size: Vec2::ZERO,
1078 };
1079
1080 for (i, galley) in galleys.iter().enumerate() {
1081 let current_y_offset = merged_galley.rect.height();
1082 let is_last_galley = i + 1 == galleys.len();
1083
1084 merged_galley
1085 .rows
1086 .extend(galley.rows.iter().enumerate().map(|(row_idx, placed_row)| {
1087 let new_pos = placed_row.pos + current_y_offset * Vec2::Y;
1088 let new_pos = new_pos.round_to_pixels(pixels_per_point);
1089 merged_galley.mesh_bounds |=
1090 placed_row.visuals.mesh_bounds.translate(new_pos.to_vec2());
1091 merged_galley.rect |= Rect::from_min_size(new_pos, placed_row.size);
1092
1093 let mut ends_with_newline = placed_row.ends_with_newline;
1094 let is_last_row_in_galley = row_idx + 1 == galley.rows.len();
1095 ends_with_newline |= !is_last_galley && is_last_row_in_galley;
1097 super::PlacedRow {
1098 pos: new_pos,
1099 row: Arc::clone(&placed_row.row),
1100 ends_with_newline,
1101 }
1102 }));
1103
1104 merged_galley.num_vertices += galley.num_vertices;
1105 merged_galley.num_indices += galley.num_indices;
1106 merged_galley.elided |= galley.elided;
1109 merged_galley.intrinsic_size.x =
1110 f32::max(merged_galley.intrinsic_size.x, galley.intrinsic_size.x);
1111 merged_galley.intrinsic_size.y += galley.intrinsic_size.y;
1112 }
1113
1114 if merged_galley.job.round_output_to_gui {
1115 merged_galley.round_output_to_gui();
1116 }
1117
1118 merged_galley
1119 }
1120}
1121
1122impl AsRef<str> for Galley {
1123 #[inline]
1124 fn as_ref(&self) -> &str {
1125 self.text()
1126 }
1127}
1128
1129impl core::borrow::Borrow<str> for Galley {
1130 #[inline]
1131 fn borrow(&self) -> &str {
1132 self.text()
1133 }
1134}
1135
1136impl core::ops::Deref for Galley {
1137 type Target = str;
1138 #[inline]
1139 fn deref(&self) -> &str {
1140 self.text()
1141 }
1142}
1143
1144impl Galley {
1148 fn end_pos(&self) -> Rect {
1150 if let Some(row) = self.rows.last() {
1151 let x = row.rect().right();
1152 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1153 } else {
1154 Rect::from_min_max(pos2(0.0, 0.0), pos2(0.0, 0.0))
1156 }
1157 }
1158
1159 pub fn pos_from_layout_cursor(&self, layout_cursor: &LayoutCursor) -> Rect {
1161 let Some(row) = self.rows.get(layout_cursor.row) else {
1162 return self.end_pos();
1163 };
1164
1165 let x = row.x_offset(layout_cursor.column) + row.pos.x;
1166 Rect::from_min_max(pos2(x, row.min_y()), pos2(x, row.max_y()))
1167 }
1168
1169 pub fn pos_from_cursor(&self, cursor: CCursor) -> Rect {
1171 self.pos_from_layout_cursor(&self.layout_from_cursor(cursor))
1172 }
1173
1174 pub fn cursor_from_pos(&self, pos: Vec2) -> CCursor {
1182 const VMARGIN: f32 = 5.0;
1184
1185 if let Some(first_row) = self.rows.first()
1186 && pos.y < first_row.min_y() - VMARGIN
1187 {
1188 return self.begin();
1189 }
1190 if let Some(last_row) = self.rows.last()
1191 && last_row.max_y() + VMARGIN < pos.y
1192 {
1193 return self.end();
1194 }
1195
1196 let mut best_y_dist = f32::INFINITY;
1197 let mut cursor = CCursor::default();
1198
1199 let mut ccursor_index = CharIndex::ZERO;
1200
1201 for row in &self.rows {
1202 let min_y = row.min_y();
1203 let max_y = row.max_y();
1204
1205 let is_pos_within_row = min_y <= pos.y && pos.y <= max_y;
1206 let y_dist = (min_y - pos.y).abs().min((max_y - pos.y).abs());
1207 if is_pos_within_row || y_dist < best_y_dist {
1208 best_y_dist = y_dist;
1209 let column = row.char_at(pos.x - row.pos.x);
1211 let prefer_next_row = column < row.char_count_excluding_newline();
1212 cursor = CCursor {
1213 index: ccursor_index + column,
1214 prefer_next_row,
1215 };
1216
1217 if is_pos_within_row {
1218 return cursor;
1219 }
1220 }
1221 ccursor_index += row.char_count_including_newline();
1222 }
1223
1224 cursor
1225 }
1226}
1227
1228impl Galley {
1230 #[inline]
1234 #[expect(clippy::unused_self)]
1235 pub fn begin(&self) -> CCursor {
1236 CCursor::default()
1237 }
1238
1239 pub fn end(&self) -> CCursor {
1241 if self.rows.is_empty() {
1242 return Default::default();
1243 }
1244 let mut ccursor = CCursor {
1245 index: CharIndex::ZERO,
1246 prefer_next_row: true,
1247 };
1248 for row in &self.rows {
1249 let row_char_count = row.char_count_including_newline();
1250 ccursor.index += row_char_count;
1251 }
1252 ccursor
1253 }
1254}
1255
1256impl Galley {
1258 pub fn layout_from_cursor(&self, cursor: CCursor) -> LayoutCursor {
1260 let prefer_next_row = cursor.prefer_next_row;
1261 let mut ccursor_it = CCursor {
1262 index: CharIndex::ZERO,
1263 prefer_next_row,
1264 };
1265
1266 for (row_nr, row) in self.rows.iter().enumerate() {
1267 let row_char_count = row.char_count_excluding_newline();
1268
1269 if ccursor_it.index <= cursor.index && cursor.index <= ccursor_it.index + row_char_count
1270 {
1271 let column = cursor.index - ccursor_it.index;
1272
1273 let select_next_row_instead = prefer_next_row
1274 && !row.ends_with_newline
1275 && column >= row.char_count_excluding_newline();
1276 if !select_next_row_instead {
1277 return LayoutCursor {
1278 row: row_nr,
1279 column,
1280 };
1281 }
1282 }
1283 ccursor_it.index += row.char_count_including_newline();
1284 }
1285 debug_assert!(ccursor_it == self.end(), "Cursor out of bounds");
1286
1287 if let Some(last_row) = self.rows.last() {
1288 LayoutCursor {
1289 row: self.rows.len() - 1,
1290 column: last_row.char_count_including_newline(),
1291 }
1292 } else {
1293 Default::default()
1294 }
1295 }
1296
1297 fn cursor_from_layout(&self, layout_cursor: LayoutCursor) -> CCursor {
1298 if layout_cursor.row >= self.rows.len() {
1299 return self.end();
1300 }
1301
1302 let prefer_next_row =
1303 layout_cursor.column < self.rows[layout_cursor.row].char_count_excluding_newline();
1304 let mut cursor_it = CCursor {
1305 index: CharIndex::ZERO,
1306 prefer_next_row,
1307 };
1308
1309 for (row_nr, row) in self.rows.iter().enumerate() {
1310 if row_nr == layout_cursor.row {
1311 cursor_it.index += layout_cursor.column.min(row.char_count_excluding_newline());
1312
1313 return cursor_it;
1314 }
1315 cursor_it.index += row.char_count_including_newline();
1316 }
1317 cursor_it
1318 }
1319}
1320
1321impl Galley {
1323 #[expect(clippy::unused_self)]
1324 pub fn cursor_left_one_character(&self, cursor: &CCursor) -> CCursor {
1325 if cursor.index == CharIndex::ZERO {
1326 Default::default()
1327 } else {
1328 CCursor {
1329 index: cursor.index - 1,
1330 prefer_next_row: true, }
1332 }
1333 }
1334
1335 pub fn cursor_right_one_character(&self, cursor: &CCursor) -> CCursor {
1336 CCursor {
1337 index: (cursor.index + 1).min(self.end().index),
1338 prefer_next_row: true, }
1340 }
1341
1342 pub fn clamp_cursor(&self, cursor: &CCursor) -> CCursor {
1343 self.cursor_from_layout(self.layout_from_cursor(*cursor))
1344 }
1345
1346 pub fn cursor_up_one_row(
1347 &self,
1348 cursor: &CCursor,
1349 h_pos: Option<f32>,
1350 ) -> (CCursor, Option<f32>) {
1351 let layout_cursor = self.layout_from_cursor(*cursor);
1352 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1353 if layout_cursor.row == 0 {
1354 (CCursor::default(), None)
1355 } else {
1356 let new_row = layout_cursor.row - 1;
1357
1358 let new_layout_cursor = {
1359 let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x);
1362 LayoutCursor {
1363 row: new_row,
1364 column,
1365 }
1366 };
1367 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1368 }
1369 }
1370
1371 pub fn cursor_down_one_row(
1372 &self,
1373 cursor: &CCursor,
1374 h_pos: Option<f32>,
1375 ) -> (CCursor, Option<f32>) {
1376 let layout_cursor = self.layout_from_cursor(*cursor);
1377 let h_pos = h_pos.unwrap_or_else(|| self.pos_from_layout_cursor(&layout_cursor).center().x);
1378 if layout_cursor.row + 1 < self.rows.len() {
1379 let new_row = layout_cursor.row + 1;
1380
1381 let new_layout_cursor = {
1382 let column = self.rows[new_row].char_at(h_pos - self.rows[new_row].pos.x);
1385 LayoutCursor {
1386 row: new_row,
1387 column,
1388 }
1389 };
1390
1391 (self.cursor_from_layout(new_layout_cursor), Some(h_pos))
1392 } else {
1393 (self.end(), None)
1394 }
1395 }
1396
1397 pub fn cursor_begin_of_row(&self, cursor: &CCursor) -> CCursor {
1398 let layout_cursor = self.layout_from_cursor(*cursor);
1399 self.cursor_from_layout(LayoutCursor {
1400 row: layout_cursor.row,
1401 column: CharIndex::ZERO,
1402 })
1403 }
1404
1405 pub fn cursor_end_of_row(&self, cursor: &CCursor) -> CCursor {
1406 let layout_cursor = self.layout_from_cursor(*cursor);
1407 self.cursor_from_layout(LayoutCursor {
1408 row: layout_cursor.row,
1409 column: self.rows[layout_cursor.row].char_count_excluding_newline(),
1410 })
1411 }
1412
1413 pub fn cursor_begin_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1414 let mut layout_cursor = self.layout_from_cursor(*cursor);
1415 layout_cursor.column = CharIndex::ZERO;
1416
1417 loop {
1418 let prev_row = layout_cursor
1419 .row
1420 .checked_sub(1)
1421 .and_then(|row| self.rows.get(row));
1422
1423 let Some(prev_row) = prev_row else {
1424 break;
1426 };
1427
1428 if prev_row.ends_with_newline {
1429 break;
1430 }
1431
1432 layout_cursor.row -= 1;
1433 }
1434
1435 self.cursor_from_layout(layout_cursor)
1436 }
1437
1438 pub fn cursor_end_of_paragraph(&self, cursor: &CCursor) -> CCursor {
1439 let mut layout_cursor = self.layout_from_cursor(*cursor);
1440 loop {
1441 let row = &self.rows[layout_cursor.row];
1442 if row.ends_with_newline || layout_cursor.row == self.rows.len() - 1 {
1443 layout_cursor.column = row.char_count_excluding_newline();
1444 break;
1445 }
1446
1447 layout_cursor.row += 1;
1448 }
1449
1450 self.cursor_from_layout(layout_cursor)
1451 }
1452}