1use crate::{
2 App, Bounds, DevicePixels, Half, Hsla, LineLayout, Pixels, Point, RenderGlyphParams, Result,
3 SharedString, StrikethroughStyle, TextAlign, UnderlineStyle, Window, WrapBoundary,
4 WrappedLineLayout, black, fill, point, px, size, underline_y_offset,
5};
6use derive_more::{Deref, DerefMut};
7use smallvec::SmallVec;
8use std::{ops::Range, sync::Arc};
9
10#[derive(Clone, Debug)]
15pub struct GlyphRasterData {
16 pub bounds: Vec<Bounds<DevicePixels>>,
18 pub params: Vec<RenderGlyphParams>,
20}
21
22#[derive(Debug, Clone)]
24pub struct DecorationRun {
25 pub len: u32,
27
28 pub color: Hsla,
30
31 pub background_color: Option<Hsla>,
33
34 pub underline: Option<UnderlineStyle>,
36
37 pub strikethrough: Option<StrikethroughStyle>,
39}
40
41#[derive(Clone, Default, Debug, Deref, DerefMut)]
43pub struct ShapedLine {
44 #[deref]
45 #[deref_mut]
46 pub(crate) layout: Arc<LineLayout>,
47 pub text: SharedString,
49 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
50}
51
52impl ShapedLine {
53 pub fn cursor(&self) -> ShapedLineCursor<'_> {
55 assert_eq!(
56 self.len(),
57 self.text.len(),
58 "cannot split a shaped line with an adjusted length"
59 );
60 let byte_ordered = self
61 .layout
62 .runs
63 .iter()
64 .flat_map(|run| run.glyphs.iter().map(|glyph| glyph.index))
65 .is_sorted();
66 ShapedLineCursor {
67 line: self,
68 unordered_remainder: (!byte_ordered).then(|| self.clone()),
69 byte_index: 0,
70 run_index: 0,
71 glyph_index: 0,
72 decoration_index: 0,
73 decoration_offset: 0,
74 x_offset: px(0.),
75 }
76 }
77
78 #[allow(clippy::len_without_is_empty)]
80 pub fn len(&self) -> usize {
81 self.layout.len
82 }
83
84 pub fn width(&self) -> Pixels {
89 self.layout.width
90 }
91
92 pub fn with_len(mut self, len: usize) -> Self {
95 let layout = self.layout.as_ref();
96 self.layout = Arc::new(LineLayout {
97 font_size: layout.font_size,
98 width: layout.width,
99 ascent: layout.ascent,
100 descent: layout.descent,
101 runs: layout.runs.clone(),
102 len,
103 });
104 self
105 }
106
107 pub fn paint(
109 &self,
110 origin: Point<Pixels>,
111 line_height: Pixels,
112 align: TextAlign,
113 align_width: Option<Pixels>,
114 window: &mut Window,
115 cx: &mut App,
116 ) -> Result<()> {
117 self.paint_with_underline_handler(
118 origin,
119 line_height,
120 align,
121 align_width,
122 window,
123 cx,
124 |_, origin, width, style, window| window.paint_underline(origin, width, style),
125 )
126 }
127
128 pub fn paint_with_underline_handler(
130 &self,
131 origin: Point<Pixels>,
132 line_height: Pixels,
133 align: TextAlign,
134 align_width: Option<Pixels>,
135 window: &mut Window,
136 cx: &mut App,
137 mut paint_underline: impl FnMut(
138 Range<usize>,
139 Point<Pixels>,
140 Pixels,
141 &UnderlineStyle,
142 &mut Window,
143 ),
144 ) -> Result<()> {
145 paint_line(
146 origin,
147 &self.layout,
148 line_height,
149 align,
150 align_width,
151 &self.decoration_runs,
152 &[],
153 window,
154 cx,
155 &mut paint_underline,
156 )
157 }
158
159 pub fn paint_background(
161 &self,
162 origin: Point<Pixels>,
163 line_height: Pixels,
164 align: TextAlign,
165 align_width: Option<Pixels>,
166 window: &mut Window,
167 cx: &mut App,
168 ) -> Result<()> {
169 paint_line_background(
170 origin,
171 &self.layout,
172 line_height,
173 align,
174 align_width,
175 &self.decoration_runs,
176 &[],
177 window,
178 cx,
179 )?;
180
181 Ok(())
182 }
183
184 pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) {
194 let (left_layout, right_layout) = self.layout.split_at(byte_index);
195
196 let mut left_decorations = SmallVec::new();
198 let mut right_decorations = SmallVec::new();
199 let mut decoration_offset = 0u32;
200 let split_point = byte_index as u32;
201
202 for decoration in &self.decoration_runs {
203 let run_end = decoration_offset + decoration.len;
204
205 if run_end <= split_point {
206 left_decorations.push(decoration.clone());
207 } else if decoration_offset >= split_point {
208 right_decorations.push(decoration.clone());
209 } else {
210 let left_len = split_point - decoration_offset;
211 let right_len = run_end - split_point;
212 left_decorations.push(DecorationRun {
213 len: left_len,
214 color: decoration.color,
215 background_color: decoration.background_color,
216 underline: decoration.underline,
217 strikethrough: decoration.strikethrough,
218 });
219 right_decorations.push(DecorationRun {
220 len: right_len,
221 color: decoration.color,
222 background_color: decoration.background_color,
223 underline: decoration.underline,
224 strikethrough: decoration.strikethrough,
225 });
226 }
227
228 decoration_offset = run_end;
229 }
230
231 let left_text = if byte_index == self.text.len() {
233 self.text.clone()
234 } else {
235 SharedString::new(&self.text[..byte_index])
236 };
237 let right_text = if byte_index == 0 {
238 self.text.clone()
239 } else {
240 SharedString::new(&self.text[byte_index..])
241 };
242
243 let left = ShapedLine {
244 layout: Arc::new(left_layout),
245 text: left_text,
246 decoration_runs: left_decorations,
247 };
248
249 let right = ShapedLine {
250 layout: Arc::new(right_layout),
251 text: right_text,
252 decoration_runs: right_decorations,
253 };
254
255 (left, right)
256 }
257}
258
259pub struct ShapedLineCursor<'a> {
266 line: &'a ShapedLine,
267 unordered_remainder: Option<ShapedLine>,
269 byte_index: usize,
270 run_index: usize,
271 glyph_index: usize,
272 decoration_index: usize,
273 decoration_offset: u32,
274 x_offset: Pixels,
275}
276
277impl<'a> ShapedLineCursor<'a> {
278 pub fn take_until(&mut self, byte_index: usize) -> ShapedLine {
283 assert!(
284 byte_index >= self.byte_index,
285 "split boundary moved backwards"
286 );
287 assert!(
288 byte_index <= self.line.len(),
289 "split boundary exceeds line length"
290 );
291 assert!(
292 self.line.text.is_char_boundary(byte_index),
293 "split boundary is not a UTF-8 character boundary"
294 );
295 let previous_index = self.byte_index;
296 let previous_x = self.x_offset;
297 if let Some(remainder) = &mut self.unordered_remainder {
298 let (piece, rest) = remainder.split_at(byte_index - previous_index);
299 *remainder = rest;
300 self.byte_index = byte_index;
301 self.x_offset = self.line.layout.x_for_index(byte_index);
302 return piece;
303 }
304 let mut runs = Vec::new();
305 let mut next_x = self.line.layout.width;
306 while let Some(run) = self.line.layout.runs.get(self.run_index) {
307 let start = self.glyph_index;
308 while let Some(glyph) = run.glyphs.get(self.glyph_index) {
309 if glyph.index >= byte_index {
310 break;
311 }
312 self.glyph_index += 1;
313 }
314 let end = self.glyph_index;
315 if start < end {
316 runs.push(crate::ShapedRun {
317 font_id: run.font_id,
318 glyphs: run.glyphs[start..end]
319 .iter()
320 .map(|glyph| crate::ShapedGlyph {
321 id: glyph.id,
322 position: point(glyph.position.x - previous_x, glyph.position.y),
323 index: glyph.index - previous_index,
324 is_emoji: glyph.is_emoji,
325 })
326 .collect(),
327 });
328 }
329 if let Some(glyph) = run.glyphs.get(self.glyph_index) {
330 next_x = glyph.position.x;
331 break;
332 }
333 self.run_index += 1;
334 self.glyph_index = 0;
335 }
336 let mut decorations = SmallVec::new();
337 while let Some(decoration) = self.line.decoration_runs.get(self.decoration_index)
338 && (self.decoration_offset < byte_index as u32
339 || (decoration.len == 0 && self.decoration_offset == byte_index as u32))
340 {
341 let end = self.decoration_offset + decoration.len;
342 let start = self.decoration_offset.max(previous_index as u32);
343 let len = end.min(byte_index as u32) - start;
344 if len > 0 || decoration.len == 0 {
345 decorations.push(DecorationRun {
346 len,
347 color: decoration.color,
348 background_color: decoration.background_color,
349 underline: decoration.underline,
350 strikethrough: decoration.strikethrough,
351 });
352 }
353 if end <= byte_index as u32 {
354 self.decoration_index += 1;
355 self.decoration_offset = end;
356 } else {
357 break;
358 }
359 }
360 self.byte_index = byte_index;
361 self.x_offset = next_x;
362 ShapedLine {
363 layout: Arc::new(LineLayout {
364 font_size: self.line.layout.font_size,
365 width: next_x - previous_x,
366 ascent: self.line.layout.ascent,
367 descent: self.line.layout.descent,
368 runs,
369 len: byte_index - previous_index,
370 }),
371 text: SharedString::new(&self.line.text[previous_index..byte_index]),
372 decoration_runs: decorations,
373 }
374 }
375
376 pub fn x_offset(&self) -> Pixels {
378 self.x_offset
379 }
380}
381
382impl LineLayout {
383 pub fn paint(
389 &self,
390 origin: Point<Pixels>,
391 line_height: Pixels,
392 align: TextAlign,
393 align_width: Option<Pixels>,
394 decoration_runs: &[DecorationRun],
395 window: &mut Window,
396 cx: &mut App,
397 ) -> Result<()> {
398 paint_line(
399 origin,
400 self,
401 line_height,
402 align,
403 align_width,
404 decoration_runs,
405 &[],
406 window,
407 cx,
408 &mut |_, origin, width, style, window| window.paint_underline(origin, width, style),
409 )
410 }
411
412 pub fn paint_background(
418 &self,
419 origin: Point<Pixels>,
420 line_height: Pixels,
421 align: TextAlign,
422 align_width: Option<Pixels>,
423 decoration_runs: &[DecorationRun],
424 window: &mut Window,
425 cx: &mut App,
426 ) -> Result<()> {
427 paint_line_background(
428 origin,
429 self,
430 line_height,
431 align,
432 align_width,
433 decoration_runs,
434 &[],
435 window,
436 cx,
437 )
438 }
439}
440
441#[derive(Default, Debug, Deref, DerefMut)]
443pub struct WrappedLine {
444 #[deref]
445 #[deref_mut]
446 pub(crate) layout: Arc<WrappedLineLayout>,
447 pub text: SharedString,
449 pub(crate) decoration_runs: Vec<DecorationRun>,
450}
451
452impl WrappedLine {
453 #[allow(clippy::len_without_is_empty)]
455 pub fn len(&self) -> usize {
456 self.layout.len()
457 }
458
459 pub fn paint(
461 &self,
462 origin: Point<Pixels>,
463 line_height: Pixels,
464 align: TextAlign,
465 bounds: Option<Bounds<Pixels>>,
466 window: &mut Window,
467 cx: &mut App,
468 ) -> Result<()> {
469 let align_width = match bounds {
470 Some(bounds) => Some(bounds.size.width),
471 None => self.layout.wrap_width,
472 };
473
474 paint_line(
475 origin,
476 &self.layout.unwrapped_layout,
477 line_height,
478 align,
479 align_width,
480 &self.decoration_runs,
481 &self.wrap_boundaries,
482 window,
483 cx,
484 &mut |_, origin, width, style, window| window.paint_underline(origin, width, style),
485 )?;
486
487 Ok(())
488 }
489
490 pub fn paint_background(
492 &self,
493 origin: Point<Pixels>,
494 line_height: Pixels,
495 align: TextAlign,
496 bounds: Option<Bounds<Pixels>>,
497 window: &mut Window,
498 cx: &mut App,
499 ) -> Result<()> {
500 let align_width = match bounds {
501 Some(bounds) => Some(bounds.size.width),
502 None => self.layout.wrap_width,
503 };
504
505 paint_line_background(
506 origin,
507 &self.layout.unwrapped_layout,
508 line_height,
509 align,
510 align_width,
511 &self.decoration_runs,
512 &self.wrap_boundaries,
513 window,
514 cx,
515 )?;
516
517 Ok(())
518 }
519}
520
521fn paint_line(
522 origin: Point<Pixels>,
523 layout: &LineLayout,
524 line_height: Pixels,
525 align: TextAlign,
526 align_width: Option<Pixels>,
527 decoration_runs: &[DecorationRun],
528 wrap_boundaries: &[WrapBoundary],
529 window: &mut Window,
530 cx: &mut App,
531 paint_underline: &mut dyn FnMut(
532 Range<usize>,
533 Point<Pixels>,
534 Pixels,
535 &UnderlineStyle,
536 &mut Window,
537 ),
538) -> Result<()> {
539 let line_bounds = Bounds::new(
540 origin,
541 size(
542 layout.width,
543 line_height * (wrap_boundaries.len() as f32 + 1.),
544 ),
545 );
546 window.paint_layer(line_bounds, |window| {
547 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
548 let baseline_offset = point(px(0.), padding_top + layout.ascent);
549 let underline_y_offset = underline_y_offset(line_height, layout.ascent, layout.descent);
550 let mut decoration_runs = decoration_runs.iter();
551 let mut wraps = wrap_boundaries.iter().peekable();
552 let mut run_end = 0;
553 let mut color = black();
554 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle, Range<usize>)> = None;
555 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
556 let text_system = cx.text_system().clone();
557 let mut glyph_origin = point(
558 aligned_origin_x(
559 origin,
560 align_width.unwrap_or(layout.width),
561 px(0.0),
562 &align,
563 layout,
564 wraps.peek(),
565 ),
566 origin.y,
567 );
568 let mut prev_glyph_position = Point::default();
569 let mut max_glyph_size = size(px(0.), px(0.));
570 let mut first_glyph_x = origin.x;
571 for (run_ix, run) in layout.runs.iter().enumerate() {
572 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
573
574 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
575 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
576 if glyph_ix == 0 && run_ix == 0 {
577 first_glyph_x = glyph_origin.x;
578 }
579
580 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
581 wraps.next();
582 if let Some((underline_origin, underline_style, underline_range)) =
583 current_underline.as_mut()
584 {
585 if glyph_origin.x == underline_origin.x {
586 underline_origin.x -= max_glyph_size.width.half();
587 };
588 paint_underline(
589 underline_range.clone(),
590 *underline_origin,
591 glyph_origin.x - underline_origin.x,
592 underline_style,
593 window,
594 );
595 if glyph.index < run_end {
596 underline_origin.x = origin.x;
597 underline_origin.y += line_height;
598 } else {
599 current_underline = None;
600 }
601 }
602 if let Some((strikethrough_origin, strikethrough_style)) =
603 current_strikethrough.as_mut()
604 {
605 if glyph_origin.x == strikethrough_origin.x {
606 strikethrough_origin.x -= max_glyph_size.width.half();
607 };
608 window.paint_strikethrough(
609 *strikethrough_origin,
610 glyph_origin.x - strikethrough_origin.x,
611 strikethrough_style,
612 );
613 if glyph.index < run_end {
614 strikethrough_origin.x = origin.x;
615 strikethrough_origin.y += line_height;
616 } else {
617 current_strikethrough = None;
618 }
619 }
620
621 glyph_origin.x = aligned_origin_x(
622 origin,
623 align_width.unwrap_or(layout.width),
624 glyph.position.x,
625 &align,
626 layout,
627 wraps.peek(),
628 );
629 glyph_origin.y += line_height;
630 }
631 prev_glyph_position = glyph.position;
632
633 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle, Range<usize>)> =
634 None;
635 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
636 if glyph.index >= run_end {
637 let mut style_run = decoration_runs.next();
638
639 while let Some(run) = style_run {
641 if glyph.index < run_end + (run.len as usize) {
642 break;
643 }
644 run_end += run.len as usize;
645 style_run = decoration_runs.next();
646 }
647
648 if let Some(style_run) = style_run {
649 let style_run_start = run_end;
650 if let Some((_, underline_style, underline_range)) = &mut current_underline
651 {
652 if style_run.underline.as_ref() != Some(underline_style) {
653 finished_underline = current_underline.take();
654 } else {
655 underline_range.end = style_run_start + style_run.len as usize;
656 }
657 }
658 if let Some(run_underline) = style_run.underline.as_ref() {
659 current_underline.get_or_insert((
660 point(glyph_origin.x, glyph_origin.y + underline_y_offset),
661 UnderlineStyle {
662 color: Some(run_underline.color.unwrap_or(style_run.color)),
663 thickness: run_underline.thickness,
664 wavy: run_underline.wavy,
665 },
666 style_run_start..style_run_start + style_run.len as usize,
667 ));
668 }
669 if let Some((_, strikethrough_style)) = &mut current_strikethrough
670 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
671 {
672 finished_strikethrough = current_strikethrough.take();
673 }
674 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
675 current_strikethrough.get_or_insert((
676 point(
677 glyph_origin.x,
678 glyph_origin.y
679 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
680 ),
681 StrikethroughStyle {
682 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
683 thickness: run_strikethrough.thickness,
684 },
685 ));
686 }
687
688 run_end += style_run.len as usize;
689 color = style_run.color;
690 } else {
691 run_end = layout.len;
692 finished_underline = current_underline.take();
693 finished_strikethrough = current_strikethrough.take();
694 }
695 }
696
697 if let Some((mut underline_origin, underline_style, underline_range)) =
698 finished_underline
699 {
700 if underline_origin.x == glyph_origin.x {
701 underline_origin.x -= max_glyph_size.width.half();
702 };
703 paint_underline(
704 underline_range,
705 underline_origin,
706 glyph_origin.x - underline_origin.x,
707 &underline_style,
708 window,
709 );
710 }
711
712 if let Some((mut strikethrough_origin, strikethrough_style)) =
713 finished_strikethrough
714 {
715 if strikethrough_origin.x == glyph_origin.x {
716 strikethrough_origin.x -= max_glyph_size.width.half();
717 };
718 window.paint_strikethrough(
719 strikethrough_origin,
720 glyph_origin.x - strikethrough_origin.x,
721 &strikethrough_style,
722 );
723 }
724
725 let max_glyph_bounds = Bounds {
726 origin: glyph_origin,
727 size: max_glyph_size,
728 };
729
730 let content_mask = window.content_mask();
731 if max_glyph_bounds.intersects(&content_mask.bounds) {
732 let vertical_offset = point(px(0.0), glyph.position.y);
733 if glyph.is_emoji {
734 window.paint_emoji(
735 glyph_origin + baseline_offset + vertical_offset,
736 run.font_id,
737 glyph.id,
738 layout.font_size,
739 )?;
740 } else {
741 window.paint_glyph(
742 glyph_origin + baseline_offset + vertical_offset,
743 run.font_id,
744 glyph.id,
745 layout.font_size,
746 color,
747 )?;
748 }
749 }
750 }
751 }
752
753 let mut last_line_end_x = first_glyph_x + layout.width;
754 if let Some(boundary) = wrap_boundaries.last() {
755 let run = &layout.runs[boundary.run_ix];
756 let glyph = &run.glyphs[boundary.glyph_ix];
757 last_line_end_x -= glyph.position.x;
758 }
759
760 if let Some((mut underline_start, underline_style, underline_range)) =
761 current_underline.take()
762 {
763 if last_line_end_x == underline_start.x {
764 underline_start.x -= max_glyph_size.width.half()
765 };
766 paint_underline(
767 underline_range,
768 underline_start,
769 last_line_end_x - underline_start.x,
770 &underline_style,
771 window,
772 );
773 }
774
775 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
776 if last_line_end_x == strikethrough_start.x {
777 strikethrough_start.x -= max_glyph_size.width.half()
778 };
779 window.paint_strikethrough(
780 strikethrough_start,
781 last_line_end_x - strikethrough_start.x,
782 &strikethrough_style,
783 );
784 }
785
786 Ok(())
787 })
788}
789
790fn paint_line_background(
791 origin: Point<Pixels>,
792 layout: &LineLayout,
793 line_height: Pixels,
794 align: TextAlign,
795 align_width: Option<Pixels>,
796 decoration_runs: &[DecorationRun],
797 wrap_boundaries: &[WrapBoundary],
798 window: &mut Window,
799 cx: &mut App,
800) -> Result<()> {
801 let line_bounds = Bounds::new(
802 origin,
803 size(
804 layout.width,
805 line_height * (wrap_boundaries.len() as f32 + 1.),
806 ),
807 );
808 window.paint_layer(line_bounds, |window| {
809 let mut decoration_runs = decoration_runs.iter();
810 let mut wraps = wrap_boundaries.iter().peekable();
811 let mut run_end = 0;
812 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
813 let text_system = cx.text_system().clone();
814 let mut glyph_origin = point(
815 aligned_origin_x(
816 origin,
817 align_width.unwrap_or(layout.width),
818 px(0.0),
819 &align,
820 layout,
821 wraps.peek(),
822 ),
823 origin.y,
824 );
825 let mut prev_glyph_position = Point::default();
826 let mut max_glyph_size = size(px(0.), px(0.));
827 for (run_ix, run) in layout.runs.iter().enumerate() {
828 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
829
830 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
831 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
832
833 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
834 wraps.next();
835 if let Some((background_origin, background_color)) = current_background.as_mut()
836 {
837 if glyph_origin.x == background_origin.x {
838 background_origin.x -= max_glyph_size.width.half()
839 }
840 window.paint_quad(fill(
841 Bounds {
842 origin: *background_origin,
843 size: size(glyph_origin.x - background_origin.x, line_height),
844 },
845 *background_color,
846 ));
847 if glyph.index < run_end {
848 background_origin.x = origin.x;
849 background_origin.y += line_height;
850 } else {
851 current_background = None;
852 }
853 }
854
855 glyph_origin.x = aligned_origin_x(
856 origin,
857 align_width.unwrap_or(layout.width),
858 glyph.position.x,
859 &align,
860 layout,
861 wraps.peek(),
862 );
863 glyph_origin.y += line_height;
864 }
865 prev_glyph_position = glyph.position;
866
867 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
868 if glyph.index >= run_end {
869 let mut style_run = decoration_runs.next();
870
871 while let Some(run) = style_run {
873 if glyph.index < run_end + (run.len as usize) {
874 break;
875 }
876 run_end += run.len as usize;
877 style_run = decoration_runs.next();
878 }
879
880 if let Some(style_run) = style_run {
881 if let Some((_, background_color)) = &mut current_background
882 && style_run.background_color.as_ref() != Some(background_color)
883 {
884 finished_background = current_background.take();
885 }
886 if let Some(run_background) = style_run.background_color {
887 current_background.get_or_insert((
888 point(glyph_origin.x, glyph_origin.y),
889 run_background,
890 ));
891 }
892 run_end += style_run.len as usize;
893 } else {
894 run_end = layout.len;
895 finished_background = current_background.take();
896 }
897 }
898
899 if let Some((mut background_origin, background_color)) = finished_background {
900 let mut width = glyph_origin.x - background_origin.x;
901 if background_origin.x == glyph_origin.x {
902 background_origin.x -= max_glyph_size.width.half();
903 };
904 window.paint_quad(fill(
905 Bounds {
906 origin: background_origin,
907 size: size(width, line_height),
908 },
909 background_color,
910 ));
911 }
912 }
913 }
914
915 let mut last_line_end_x = origin.x + layout.width;
916 if let Some(boundary) = wrap_boundaries.last() {
917 let run = &layout.runs[boundary.run_ix];
918 let glyph = &run.glyphs[boundary.glyph_ix];
919 last_line_end_x -= glyph.position.x;
920 }
921
922 if let Some((mut background_origin, background_color)) = current_background.take() {
923 if last_line_end_x == background_origin.x {
924 background_origin.x -= max_glyph_size.width.half()
925 };
926 window.paint_quad(fill(
927 Bounds {
928 origin: background_origin,
929 size: size(last_line_end_x - background_origin.x, line_height),
930 },
931 background_color,
932 ));
933 }
934
935 Ok(())
936 })
937}
938
939fn aligned_origin_x(
940 origin: Point<Pixels>,
941 align_width: Pixels,
942 last_glyph_x: Pixels,
943 align: &TextAlign,
944 layout: &LineLayout,
945 wrap_boundary: Option<&&WrapBoundary>,
946) -> Pixels {
947 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
948 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
949 } else {
950 layout.width
951 };
952
953 let line_width = end_of_line - last_glyph_x;
954
955 match align {
956 TextAlign::Left => origin.x,
957 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
958 TextAlign::Right => origin.x + align_width - line_width,
959 }
960}
961
962#[cfg(test)]
963mod tests {
964 use super::*;
965 use crate::{
966 AppContext as _, Context, FontId, GlyphId, IntoElement, Render, ShapedGlyph, ShapedRun,
967 Styled, TestAppContext, TextRun, Underline, canvas, font, hsla,
968 };
969 use std::rc::Rc;
970
971 fn make_shaped_line(
974 text: &str,
975 glyphs: &[(usize, f32)],
976 width: f32,
977 decorations: &[DecorationRun],
978 ) -> ShapedLine {
979 let shaped_glyphs: Vec<ShapedGlyph> = glyphs
980 .iter()
981 .map(|&(index, x)| ShapedGlyph {
982 id: GlyphId(0),
983 position: point(px(x), px(0.0)),
984 index,
985 is_emoji: false,
986 })
987 .collect();
988
989 ShapedLine {
990 layout: Arc::new(LineLayout {
991 font_size: px(16.0),
992 width: px(width),
993 ascent: px(12.0),
994 descent: px(4.0),
995 runs: vec![ShapedRun {
996 font_id: FontId(0),
997 glyphs: shaped_glyphs,
998 }],
999 len: text.len(),
1000 }),
1001 text: SharedString::new(text),
1002 decoration_runs: SmallVec::from(decorations.to_vec()),
1003 }
1004 }
1005
1006 #[gpui::test]
1007 fn test_underline_handler_matches_default_paint(cx: &mut TestAppContext) {
1008 test_underline_handler_at_scales(cx, |window, cx| {
1009 let first_style = UnderlineStyle {
1010 thickness: px(1.),
1011 color: Some(hsla(0., 1., 0.5, 1.)),
1012 wavy: true,
1013 };
1014 let last_style = UnderlineStyle {
1015 color: Some(hsla(0.5, 1., 0.5, 1.)),
1016 wavy: false,
1017 ..first_style
1018 };
1019 let fallback_style = UnderlineStyle {
1020 color: Some(black()),
1021 ..first_style
1022 };
1023 let decoration = DecorationRun {
1024 len: 1,
1025 color: black(),
1026 background_color: None,
1027 underline: Some(first_style),
1028 strikethrough: None,
1029 };
1030 let line = underline_test_line(
1031 "aébcde",
1032 &[
1033 decoration.clone(),
1034 DecorationRun {
1035 len: 2,
1036 ..decoration.clone()
1037 },
1038 DecorationRun {
1039 underline: None,
1040 ..decoration.clone()
1041 },
1042 DecorationRun {
1043 underline: Some(UnderlineStyle {
1044 color: None,
1045 ..first_style
1046 }),
1047 ..decoration.clone()
1048 },
1049 DecorationRun {
1050 underline: Some(last_style),
1051 ..decoration.clone()
1052 },
1053 DecorationRun {
1054 underline: Some(last_style),
1055 ..decoration
1056 },
1057 ],
1058 false,
1059 window,
1060 );
1061 assert_eq!(line.width(), px(48.));
1062 for origin_x in [-3.25, 0., 4.25] {
1063 for (align, align_width, offset) in [
1064 (TextAlign::Left, None, 0.),
1065 (TextAlign::Center, None, 0.),
1066 (TextAlign::Right, None, 0.),
1067 (TextAlign::Left, Some(px(96.)), 0.),
1068 (TextAlign::Center, Some(px(96.)), 24.),
1069 (TextAlign::Right, Some(px(96.)), 48.),
1070 ] {
1071 let origin = point(px(origin_x), px(12.25));
1072 let line_height = px(20.);
1073 window.next_frame.scene.clear();
1074 line.paint(origin, line_height, align, align_width, window, cx)
1075 .unwrap();
1076 let original = window.next_frame.scene.underlines.clone();
1077 window.next_frame.scene.clear();
1078 line.layout
1079 .paint(
1080 origin,
1081 line_height,
1082 align,
1083 align_width,
1084 &line.decoration_runs,
1085 window,
1086 cx,
1087 )
1088 .unwrap();
1089 assert_underline_primitives_eq(&window.next_frame.scene.underlines, &original);
1090
1091 window.next_frame.scene.clear();
1092 let mut strokes = Vec::new();
1093 line.paint_with_underline_handler(
1094 origin,
1095 line_height,
1096 align,
1097 align_width,
1098 window,
1099 cx,
1100 |range, origin, width, style, window| {
1101 strokes.push((range, origin, width, *style));
1102 window.paint_underline(origin, width, style);
1103 },
1104 )
1105 .unwrap();
1106 let start = px(origin_x + offset);
1107 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1108 assert_eq!(
1109 strokes,
1110 [
1111 (0..3, point(start, y), px(16.), first_style),
1112 (4..5, point(start + px(24.), y), px(8.), fallback_style),
1113 (5..7, point(start + px(32.), y), px(16.), last_style),
1114 ]
1115 );
1116 assert_underline_primitives_eq(&window.next_frame.scene.underlines, &original);
1117
1118 window.next_frame.scene.clear();
1119 let mut captured = Vec::new();
1120 line.paint_with_underline_handler(
1121 origin,
1122 line_height,
1123 align,
1124 align_width,
1125 window,
1126 cx,
1127 |range, origin, width, style, _| {
1128 captured.push((range, origin, width, *style))
1129 },
1130 )
1131 .unwrap();
1132 assert_eq!(captured, strokes);
1133 assert_eq!(window.next_frame.scene.underlines.len(), 0);
1134 }
1135 }
1136 for text in ["", "abc"] {
1137 let line = underline_test_line(text, &[], false, window);
1138 let mut calls = 0;
1139 line.paint_with_underline_handler(
1140 point(px(4.), px(10.)),
1141 px(20.),
1142 TextAlign::Left,
1143 None,
1144 window,
1145 cx,
1146 |_, _, _, _, _| calls += 1,
1147 )
1148 .unwrap();
1149 assert_eq!(calls, 0);
1150 }
1151 });
1152 }
1153
1154 #[gpui::test]
1155 fn test_underline_handler_reports_zero_advance_geometry(cx: &mut TestAppContext) {
1156 test_underline_handler_at_scales(cx, |window, cx| {
1157 let first_style = UnderlineStyle {
1158 thickness: px(1.),
1159 color: Some(black()),
1160 wavy: true,
1161 };
1162 let last_style = UnderlineStyle {
1163 wavy: false,
1164 ..first_style
1165 };
1166 let decoration = DecorationRun {
1167 len: 1,
1168 color: black(),
1169 background_color: None,
1170 underline: Some(first_style),
1171 strikethrough: None,
1172 };
1173 let line = underline_test_line(
1174 "ab",
1175 &[
1176 decoration.clone(),
1177 DecorationRun {
1178 underline: Some(last_style),
1179 ..decoration
1180 },
1181 ],
1182 true,
1183 window,
1184 );
1185 let half_width = cx
1186 .text_system()
1187 .bounding_box(line.runs[0].font_id, line.font_size)
1188 .size
1189 .width
1190 / 2.;
1191 let origin = point(px(40.25), px(10.25));
1192 let line_height = px(20.);
1193 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1194 for (align, offset) in [
1195 (TextAlign::Left, 0.),
1196 (TextAlign::Center, 16.),
1197 (TextAlign::Right, 32.),
1198 ] {
1199 window.next_frame.scene.clear();
1200 line.paint(origin, line_height, align, Some(px(32.)), window, cx)
1201 .unwrap();
1202 let original = window.next_frame.scene.underlines.clone();
1203 window.next_frame.scene.clear();
1204 let mut strokes = Vec::new();
1205 line.paint_with_underline_handler(
1206 origin,
1207 line_height,
1208 align,
1209 Some(px(32.)),
1210 window,
1211 cx,
1212 |range, origin, width, style, window| {
1213 strokes.push((range, origin, width, *style));
1214 window.paint_underline(origin, width, style);
1215 },
1216 )
1217 .unwrap();
1218 let end = origin.x + px(offset);
1219 let start = point(end - half_width, y);
1220 let width = end - start.x;
1221 assert_eq!(
1222 strokes,
1223 [
1224 (0..1, start, width, first_style),
1225 (1..2, start, width, last_style),
1226 ]
1227 );
1228 assert_underline_primitives_eq(&window.next_frame.scene.underlines, &original);
1229 }
1230 });
1231 }
1232
1233 #[gpui::test]
1234 fn test_underline_handler_matches_wrapped_paint(cx: &mut TestAppContext) {
1235 test_underline_handler_at_scales(cx, |window, cx| {
1236 let style = UnderlineStyle {
1237 thickness: px(1.),
1238 color: Some(black()),
1239 wavy: true,
1240 };
1241 for zero_advance in [false, true] {
1242 let line = underline_test_line(
1243 "abcd",
1244 &[DecorationRun {
1245 len: 4,
1246 color: black(),
1247 background_color: None,
1248 underline: Some(style),
1249 strikethrough: None,
1250 }],
1251 zero_advance,
1252 window,
1253 );
1254 let half_width = cx
1255 .text_system()
1256 .bounding_box(line.runs[0].font_id, line.font_size)
1257 .size
1258 .width
1259 / 2.;
1260 let origin = point(px(40.25), px(10.25));
1261 let line_height = px(20.);
1262 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1263 let wrapped = WrappedLine {
1264 layout: Arc::new(WrappedLineLayout {
1265 unwrapped_layout: line.layout,
1266 wrap_boundaries: SmallVec::from_buf([WrapBoundary {
1267 run_ix: 0,
1268 glyph_ix: 2,
1269 }]),
1270 wrap_width: Some(px(16.)),
1271 }),
1272 text: line.text,
1273 decoration_runs: line.decoration_runs.into_vec(),
1274 };
1275 window.next_frame.scene.clear();
1276 wrapped
1277 .paint(origin, line_height, TextAlign::Left, None, window, cx)
1278 .unwrap();
1279 let original = window.next_frame.scene.underlines.clone();
1280 window.next_frame.scene.clear();
1281 let mut strokes = Vec::new();
1282 paint_line(
1283 origin,
1284 &wrapped.unwrapped_layout,
1285 line_height,
1286 TextAlign::Left,
1287 Some(px(16.)),
1288 &wrapped.decoration_runs,
1289 &wrapped.wrap_boundaries,
1290 window,
1291 cx,
1292 &mut |range, origin, width, style, window| {
1293 strokes.push((range, origin, width, *style));
1294 window.paint_underline(origin, width, style);
1295 },
1296 )
1297 .unwrap();
1298 let (start, width) = if zero_advance {
1299 let start = origin.x - half_width;
1300 (start, origin.x - start)
1301 } else {
1302 (origin.x, px(16.))
1303 };
1304 assert_eq!(
1305 strokes,
1306 [
1307 (0..4, point(start, y), width, style),
1308 (0..4, point(start, y + line_height), width, style),
1309 ]
1310 );
1311 assert_underline_primitives_eq(&window.next_frame.scene.underlines, &original);
1312 }
1313 });
1314 }
1315
1316 #[test]
1317 fn test_split_at_invariants() {
1318 let line = make_shaped_line(
1320 "abcdef",
1321 &[
1322 (0, 0.0),
1323 (1, 10.0),
1324 (2, 20.0),
1325 (3, 30.0),
1326 (4, 40.0),
1327 (5, 50.0),
1328 ],
1329 60.0,
1330 &[],
1331 );
1332
1333 for i in 0..=6 {
1334 let (left, right) = line.split_at(i);
1335
1336 assert_eq!(
1337 left.width() + right.width(),
1338 line.width(),
1339 "widths must sum at split={i}"
1340 );
1341 assert_eq!(
1342 left.len() + right.len(),
1343 line.len(),
1344 "lengths must sum at split={i}"
1345 );
1346 assert_eq!(
1347 format!("{}{}", left.text.as_ref(), right.text.as_ref()),
1348 "abcdef",
1349 "text must concatenate at split={i}"
1350 );
1351 assert_eq!(left.font_size, line.font_size, "font_size at split={i}");
1352 assert_eq!(right.ascent, line.ascent, "ascent at split={i}");
1353 assert_eq!(right.descent, line.descent, "descent at split={i}");
1354 }
1355
1356 let (left, right) = line.split_at(0);
1358 assert_eq!(left.runs.len(), 0);
1359 assert_eq!(right.runs[0].glyphs.len(), 6);
1360
1361 let (left, right) = line.split_at(6);
1363 assert_eq!(left.runs[0].glyphs.len(), 6);
1364 assert_eq!(right.runs.len(), 0);
1365 }
1366
1367 #[test]
1368 fn test_split_at_glyph_rebasing() {
1369 let line = ShapedLine {
1374 layout: Arc::new(LineLayout {
1375 font_size: px(16.0),
1376 width: px(60.0),
1377 ascent: px(12.0),
1378 descent: px(4.0),
1379 runs: vec![
1380 ShapedRun {
1381 font_id: FontId(0),
1382 glyphs: vec![
1383 ShapedGlyph {
1384 id: GlyphId(0),
1385 position: point(px(0.0), px(0.0)),
1386 index: 0,
1387 is_emoji: false,
1388 },
1389 ShapedGlyph {
1390 id: GlyphId(0),
1391 position: point(px(10.0), px(0.0)),
1392 index: 1,
1393 is_emoji: false,
1394 },
1395 ShapedGlyph {
1396 id: GlyphId(0),
1397 position: point(px(20.0), px(0.0)),
1398 index: 2,
1399 is_emoji: false,
1400 },
1401 ],
1402 },
1403 ShapedRun {
1404 font_id: FontId(1),
1405 glyphs: vec![
1406 ShapedGlyph {
1407 id: GlyphId(0),
1408 position: point(px(30.0), px(0.0)),
1409 index: 3,
1410 is_emoji: false,
1411 },
1412 ShapedGlyph {
1413 id: GlyphId(0),
1414 position: point(px(40.0), px(0.0)),
1415 index: 4,
1416 is_emoji: false,
1417 },
1418 ShapedGlyph {
1419 id: GlyphId(0),
1420 position: point(px(50.0), px(0.0)),
1421 index: 5,
1422 is_emoji: false,
1423 },
1424 ],
1425 },
1426 ],
1427 len: 6,
1428 }),
1429 text: "abcdef".into(),
1430 decoration_runs: SmallVec::new(),
1431 };
1432
1433 let (first, remainder) = line.split_at(2);
1435 assert_eq!(first.text.as_ref(), "ab");
1436 assert_eq!(first.runs.len(), 1);
1437 assert_eq!(first.runs[0].font_id, FontId(0));
1438
1439 assert_eq!(remainder.text.as_ref(), "cdef");
1441 assert_eq!(remainder.runs.len(), 2);
1442 assert_eq!(remainder.runs[0].font_id, FontId(0));
1443 assert_eq!(remainder.runs[0].glyphs.len(), 1);
1444 assert_eq!(remainder.runs[0].glyphs[0].index, 0);
1445 assert_eq!(remainder.runs[0].glyphs[0].position.x, px(0.0));
1446 assert_eq!(remainder.runs[1].font_id, FontId(1));
1447 assert_eq!(remainder.runs[1].glyphs[0].index, 1);
1448 assert_eq!(remainder.runs[1].glyphs[0].position.x, px(10.0));
1449
1450 let (second, final_part) = remainder.split_at(2);
1452 assert_eq!(second.text.as_ref(), "cd");
1453 assert_eq!(final_part.text.as_ref(), "ef");
1454 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1455 assert_eq!(final_part.runs[0].glyphs[0].position.x, px(0.0));
1456
1457 assert_eq!(
1459 first.width() + second.width() + final_part.width(),
1460 line.width()
1461 );
1462 }
1463
1464 #[test]
1465 fn test_split_at_decorations() {
1466 let red = Hsla {
1469 h: 0.0,
1470 s: 1.0,
1471 l: 0.5,
1472 a: 1.0,
1473 };
1474 let green = Hsla {
1475 h: 0.3,
1476 s: 1.0,
1477 l: 0.5,
1478 a: 1.0,
1479 };
1480 let blue = Hsla {
1481 h: 0.6,
1482 s: 1.0,
1483 l: 0.5,
1484 a: 1.0,
1485 };
1486
1487 let line = make_shaped_line(
1488 "abcdef",
1489 &[
1490 (0, 0.0),
1491 (1, 10.0),
1492 (2, 20.0),
1493 (3, 30.0),
1494 (4, 40.0),
1495 (5, 50.0),
1496 ],
1497 60.0,
1498 &[
1499 DecorationRun {
1500 len: 2,
1501 color: red,
1502 background_color: None,
1503 underline: None,
1504 strikethrough: None,
1505 },
1506 DecorationRun {
1507 len: 3,
1508 color: green,
1509 background_color: None,
1510 underline: None,
1511 strikethrough: None,
1512 },
1513 DecorationRun {
1514 len: 1,
1515 color: blue,
1516 background_color: None,
1517 underline: None,
1518 strikethrough: None,
1519 },
1520 ],
1521 );
1522
1523 let (left, right) = line.split_at(3);
1524
1525 assert_eq!(left.decoration_runs.len(), 2);
1527 assert_eq!(left.decoration_runs[0].len, 2);
1528 assert_eq!(left.decoration_runs[0].color, red);
1529 assert_eq!(left.decoration_runs[1].len, 1);
1530 assert_eq!(left.decoration_runs[1].color, green);
1531
1532 assert_eq!(right.decoration_runs.len(), 2);
1534 assert_eq!(right.decoration_runs[0].len, 2);
1535 assert_eq!(right.decoration_runs[0].color, green);
1536 assert_eq!(right.decoration_runs[1].len, 1);
1537 assert_eq!(right.decoration_runs[1].color, blue);
1538 }
1539
1540 struct UnderlineHandlerTestView(Rc<dyn Fn(&mut Window, &mut App)>);
1541
1542 impl Render for UnderlineHandlerTestView {
1543 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
1544 let paint = self.0.clone();
1545 canvas(
1546 |_, _, _| {},
1547 move |_, _, window, cx| {
1548 window.with_element_opacity(Some(0.5), |window| paint(window, cx));
1549 },
1550 )
1551 .size_full()
1552 }
1553 }
1554
1555 fn test_underline_handler_at_scales(
1556 cx: &mut TestAppContext,
1557 paint: impl Fn(&mut Window, &mut App) + 'static,
1558 ) {
1559 let window = cx.add_window(move |_, _| UnderlineHandlerTestView(Rc::new(paint)));
1560 for scale in [1., 1.25, 1.5, 2., 3.] {
1561 cx.simulate_window_scale_factor_change(window.into(), scale);
1562 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
1563 .unwrap();
1564 }
1565 }
1566
1567 fn underline_test_line(
1568 text: &str,
1569 decorations: &[DecorationRun],
1570 zero_advance: bool,
1571 window: &Window,
1572 ) -> ShapedLine {
1573 let mut line = window.text_system().shape_line(
1574 SharedString::new(text),
1575 px(16.),
1576 &[TextRun {
1577 len: text.len(),
1578 font: font(".ZedMono"),
1579 color: black(),
1580 ..TextRun::default()
1581 }],
1582 None,
1583 );
1584 line.decoration_runs = SmallVec::from(decorations.to_vec());
1585 let layout = &line.layout;
1586 let mut runs = layout.runs.clone();
1587 let advance = if zero_advance { px(0.) } else { px(8.) };
1588 let mut width = px(0.);
1589 for glyph in runs.iter_mut().flat_map(|run| &mut run.glyphs) {
1590 glyph.position.x = width;
1591 width += advance;
1592 }
1593 line.layout = Arc::new(LineLayout {
1594 font_size: layout.font_size,
1595 width,
1596 ascent: layout.ascent,
1597 descent: layout.descent,
1598 runs,
1599 len: layout.len,
1600 });
1601 line
1602 }
1603
1604 fn assert_underline_primitives_eq(actual: &[Underline], expected: &[Underline]) {
1605 assert_eq!(actual.len(), expected.len());
1606 for (actual, expected) in actual.iter().zip(expected) {
1607 assert_eq!(actual.bounds, expected.bounds);
1608 assert_eq!(actual.content_mask, expected.content_mask);
1609 assert_eq!(actual.color, expected.color);
1610 assert_eq!(actual.thickness, expected.thickness);
1611 assert_eq!(actual.wavy, expected.wavy);
1612 assert_eq!(actual.order, expected.order);
1613 assert_eq!(actual.pad, expected.pad);
1614 }
1615 }
1616
1617 #[test]
1618 fn test_cursor_preserves_shaping_metadata_across_runs() {
1619 let line = ShapedLine {
1620 layout: Arc::new(LineLayout {
1621 font_size: px(16.0),
1622 width: px(50.0),
1623 ascent: px(12.0),
1624 descent: px(4.0),
1625 runs: vec![
1626 ShapedRun {
1627 font_id: FontId(3),
1628 glyphs: vec![
1629 ShapedGlyph {
1630 id: GlyphId(11),
1631 position: point(px(0.0), px(1.0)),
1632 index: 0,
1633 is_emoji: true,
1634 },
1635 ShapedGlyph {
1636 id: GlyphId(12),
1637 position: point(px(17.0), px(1.0)),
1638 index: 1,
1639 is_emoji: false,
1640 },
1641 ShapedGlyph {
1642 id: GlyphId(13),
1643 position: point(px(19.0), px(-1.0)),
1644 index: 1,
1645 is_emoji: false,
1646 },
1647 ],
1648 },
1649 ShapedRun {
1650 font_id: FontId(8),
1651 glyphs: vec![
1652 ShapedGlyph {
1653 id: GlyphId(21),
1654 position: point(px(25.0), px(1.0)),
1655 index: 5,
1656 is_emoji: true,
1657 },
1658 ShapedGlyph {
1659 id: GlyphId(22),
1660 position: point(px(41.0), px(1.0)),
1661 index: 7,
1662 is_emoji: false,
1663 },
1664 ],
1665 },
1666 ],
1667 len: 10,
1668 }),
1669 text: "a😀bcdef".into(),
1670 decoration_runs: SmallVec::new(),
1671 };
1672 let mut cursor = line.cursor();
1673 let first = cursor.take_until(5);
1674 assert_eq!(first.text.as_ref(), "a😀");
1675 assert_eq!(first.runs[0].font_id, FontId(3));
1676 assert_eq!(first.runs[0].glyphs[0].id, GlyphId(11));
1677 assert!(first.runs[0].glyphs[0].is_emoji);
1678 assert_eq!(first.runs[0].glyphs[1].index, 1);
1679 assert_eq!(first.runs[0].glyphs[1].position, point(px(17.0), px(1.0)));
1680 assert_eq!(first.runs[0].glyphs.len(), 3);
1681 assert_eq!(first.runs[0].glyphs[2].index, 1);
1682 assert_eq!(first.runs[0].glyphs[2].position, point(px(19.0), px(-1.0)));
1683 assert_eq!(cursor.x_offset(), px(25.0));
1684
1685 let second = cursor.take_until(7);
1686 assert_eq!(second.text.as_ref(), "bc");
1687 assert_eq!(second.runs[0].font_id, FontId(8));
1688 assert_eq!(second.runs[0].glyphs[0].id, GlyphId(21));
1689 assert_eq!(second.runs[0].glyphs[0].index, 0);
1690 assert_eq!(second.runs[0].glyphs[0].position, point(px(0.0), px(1.0)));
1691 assert_eq!(cursor.x_offset(), px(41.0));
1692
1693 let final_part = cursor.take_until(10);
1694 assert_eq!(final_part.text.as_ref(), "def");
1695 assert_eq!(final_part.runs[0].font_id, FontId(8));
1696 assert_eq!(final_part.runs[0].glyphs[0].id, GlyphId(22));
1697 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1698 assert_eq!(
1699 final_part.runs[0].glyphs[0].position,
1700 point(px(0.0), px(1.0))
1701 );
1702 }
1703
1704 #[test]
1705 fn test_cursor_preserves_existing_visual_order_splitting() {
1706 let line = make_shaped_line("abc", &[(0, 0.0), (2, 10.0), (1, 20.0)], 30.0, &[]);
1707 let mut cursor = line.cursor();
1708 let mut remainder = line.clone();
1709 let mut previous_boundary = 0;
1710 for boundary in [0, 1, 2, 3] {
1711 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1712 let actual = cursor.take_until(boundary);
1713 assert_eq!(actual.text, expected.text);
1714 assert_eq!(actual.width(), expected.width());
1715 assert_eq!(actual.runs.len(), expected.runs.len());
1716 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1717 assert_eq!(actual.font_id, expected.font_id);
1718 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1719 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1720 assert_eq!(actual.id, expected.id);
1721 assert_eq!(actual.index, expected.index);
1722 assert_eq!(actual.position, expected.position);
1723 }
1724 }
1725 assert_eq!(cursor.x_offset(), line.x_for_index(boundary));
1726 remainder = rest;
1727 previous_boundary = boundary;
1728 }
1729 }
1730
1731 #[test]
1732 fn test_cursor_partitions_one_decoration_across_three_chunks() {
1733 let line = make_shaped_line(
1734 "abcdef",
1735 &[
1736 (0, 0.0),
1737 (1, 10.0),
1738 (2, 20.0),
1739 (3, 30.0),
1740 (4, 40.0),
1741 (5, 50.0),
1742 ],
1743 60.0,
1744 &[DecorationRun {
1745 len: 6,
1746 color: Hsla {
1747 h: 0.2,
1748 s: 0.4,
1749 l: 0.6,
1750 a: 1.0,
1751 },
1752 background_color: None,
1753 underline: None,
1754 strikethrough: None,
1755 }],
1756 );
1757 let mut cursor = line.cursor();
1758 assert_eq!(cursor.take_until(2).decoration_runs[0].len, 2);
1759 assert_eq!(cursor.take_until(4).decoration_runs[0].len, 2);
1760 assert_eq!(cursor.take_until(6).decoration_runs[0].len, 2);
1761 }
1762
1763 #[test]
1764 fn test_cursor_matches_successive_splits_at_ordered_boundaries() {
1765 let decorations: Vec<_> = [2, 0, 3, 1]
1766 .into_iter()
1767 .map(|len| DecorationRun {
1768 len,
1769 color: Hsla {
1770 h: len as f32 / 10.0,
1771 s: 0.5,
1772 l: 0.5,
1773 a: 1.0,
1774 },
1775 background_color: Some(black()),
1776 underline: None,
1777 strikethrough: None,
1778 })
1779 .collect();
1780 let line = make_shaped_line(
1781 "abcdef",
1782 &[(0, 5.0), (0, 5.0), (2, 15.0), (4, 25.0), (5, 35.0)],
1783 45.0,
1784 &decorations,
1785 );
1786 for first in 0..=line.len() {
1787 for second in first..=line.len() {
1788 let mut cursor = line.cursor();
1789 let mut remainder = line.clone();
1790 let mut previous_boundary = 0;
1791 let mut total_width = px(0.0);
1792 let mut text = String::new();
1793 for boundary in [first, second, line.len(), line.len()] {
1794 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1795 let actual = cursor.take_until(boundary);
1796 assert_eq!(actual.text, expected.text);
1797 assert_eq!(actual.len(), expected.len());
1798 assert_eq!(actual.width(), expected.width());
1799 assert_eq!(actual.runs.len(), expected.runs.len());
1800 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1801 assert_eq!(actual.font_id, expected.font_id);
1802 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1803 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1804 assert_eq!(actual.id, expected.id);
1805 assert_eq!(actual.index, expected.index);
1806 assert_eq!(actual.position, expected.position);
1807 }
1808 }
1809 assert_eq!(actual.decoration_runs.len(), expected.decoration_runs.len());
1810 for (actual, expected) in
1811 actual.decoration_runs.iter().zip(&expected.decoration_runs)
1812 {
1813 assert_eq!(actual.len, expected.len);
1814 assert_eq!(actual.color, expected.color);
1815 assert_eq!(actual.background_color, expected.background_color);
1816 }
1817 total_width += actual.width();
1818 text.push_str(&actual.text);
1819 remainder = rest;
1820 previous_boundary = boundary;
1821 }
1822 assert_eq!(total_width, line.width());
1823 assert_eq!(text, line.text.as_ref());
1824 }
1825 }
1826 }
1827
1828 #[test]
1829 fn test_cursor_empty_chunks_and_repeated_boundaries() {
1830 let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1831 let mut cursor = line.cursor();
1832 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1833 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1834 assert_eq!(cursor.take_until(1).text.as_ref(), "a");
1835 assert_eq!(cursor.take_until(2).text.as_ref(), "b");
1836 assert_eq!(cursor.take_until(2).text.as_ref(), "");
1837 let empty = make_shaped_line("", &[], 0.0, &[]);
1838 let piece = empty.cursor().take_until(0);
1839 assert!(piece.text.is_empty());
1840 assert!(piece.runs.is_empty());
1841 assert_eq!(piece.width(), px(0.0));
1842 }
1843
1844 #[test]
1845 fn test_cursor_rejects_invalid_boundaries() {
1846 let line = make_shaped_line("é", &[(0, 0.0)], 10.0, &[]);
1847 let mut cursor = line.cursor();
1848 assert!(
1849 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1850 cursor.take_until(1);
1851 }))
1852 .is_err()
1853 );
1854 let mut cursor = line.cursor();
1855 cursor.take_until(2);
1856 assert!(
1857 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1858 cursor.take_until(0);
1859 }))
1860 .is_err()
1861 );
1862 let mut cursor = line.cursor();
1863 assert!(
1864 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1865 cursor.take_until(3);
1866 }))
1867 .is_err()
1868 );
1869 }
1870}