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
382pub trait LineLayoutExt {
387 fn paint(
393 &self,
394 origin: Point<Pixels>,
395 line_height: Pixels,
396 align: TextAlign,
397 align_width: Option<Pixels>,
398 decoration_runs: &[DecorationRun],
399 window: &mut Window,
400 cx: &mut App,
401 ) -> Result<()>;
402
403 fn paint_background(
409 &self,
410 origin: Point<Pixels>,
411 line_height: Pixels,
412 align: TextAlign,
413 align_width: Option<Pixels>,
414 decoration_runs: &[DecorationRun],
415 window: &mut Window,
416 cx: &mut App,
417 ) -> Result<()>;
418}
419
420impl LineLayoutExt for LineLayout {
421 fn paint(
422 &self,
423 origin: Point<Pixels>,
424 line_height: Pixels,
425 align: TextAlign,
426 align_width: Option<Pixels>,
427 decoration_runs: &[DecorationRun],
428 window: &mut Window,
429 cx: &mut App,
430 ) -> Result<()> {
431 paint_line(
432 origin,
433 self,
434 line_height,
435 align,
436 align_width,
437 decoration_runs,
438 &[],
439 window,
440 cx,
441 &mut |_, origin, width, style, window| window.paint_underline(origin, width, style),
442 )
443 }
444
445 fn paint_background(
446 &self,
447 origin: Point<Pixels>,
448 line_height: Pixels,
449 align: TextAlign,
450 align_width: Option<Pixels>,
451 decoration_runs: &[DecorationRun],
452 window: &mut Window,
453 cx: &mut App,
454 ) -> Result<()> {
455 paint_line_background(
456 origin,
457 self,
458 line_height,
459 align,
460 align_width,
461 decoration_runs,
462 &[],
463 window,
464 cx,
465 )
466 }
467}
468
469#[derive(Default, Debug, Deref, DerefMut)]
471pub struct WrappedLine {
472 #[deref]
473 #[deref_mut]
474 pub(crate) layout: Arc<WrappedLineLayout>,
475 pub text: SharedString,
477 pub(crate) decoration_runs: Vec<DecorationRun>,
478}
479
480impl WrappedLine {
481 #[allow(clippy::len_without_is_empty)]
483 pub fn len(&self) -> usize {
484 self.layout.len()
485 }
486
487 pub fn paint(
489 &self,
490 origin: Point<Pixels>,
491 line_height: Pixels,
492 align: TextAlign,
493 bounds: Option<Bounds<Pixels>>,
494 window: &mut Window,
495 cx: &mut App,
496 ) -> Result<()> {
497 let align_width = match bounds {
498 Some(bounds) => Some(bounds.size.width),
499 None => self.layout.wrap_width,
500 };
501
502 paint_line(
503 origin,
504 &self.layout.unwrapped_layout,
505 line_height,
506 align,
507 align_width,
508 &self.decoration_runs,
509 &self.wrap_boundaries,
510 window,
511 cx,
512 &mut |_, origin, width, style, window| window.paint_underline(origin, width, style),
513 )?;
514
515 Ok(())
516 }
517
518 pub fn paint_background(
520 &self,
521 origin: Point<Pixels>,
522 line_height: Pixels,
523 align: TextAlign,
524 bounds: Option<Bounds<Pixels>>,
525 window: &mut Window,
526 cx: &mut App,
527 ) -> Result<()> {
528 let align_width = match bounds {
529 Some(bounds) => Some(bounds.size.width),
530 None => self.layout.wrap_width,
531 };
532
533 paint_line_background(
534 origin,
535 &self.layout.unwrapped_layout,
536 line_height,
537 align,
538 align_width,
539 &self.decoration_runs,
540 &self.wrap_boundaries,
541 window,
542 cx,
543 )?;
544
545 Ok(())
546 }
547}
548
549fn paint_line(
550 origin: Point<Pixels>,
551 layout: &LineLayout,
552 line_height: Pixels,
553 align: TextAlign,
554 align_width: Option<Pixels>,
555 decoration_runs: &[DecorationRun],
556 wrap_boundaries: &[WrapBoundary],
557 window: &mut Window,
558 cx: &mut App,
559 paint_underline: &mut dyn FnMut(
560 Range<usize>,
561 Point<Pixels>,
562 Pixels,
563 &UnderlineStyle,
564 &mut Window,
565 ),
566) -> Result<()> {
567 let line_bounds = line_paint_bounds(
568 origin,
569 layout,
570 line_height,
571 align,
572 align_width,
573 wrap_boundaries,
574 );
575 window.paint_layer(line_bounds, |window| {
576 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
577 let baseline_offset = point(px(0.), padding_top + layout.ascent);
578 let underline_y_offset = underline_y_offset(line_height, layout.ascent, layout.descent);
579 let mut decoration_runs = decoration_runs.iter();
580 let mut wraps = wrap_boundaries.iter().peekable();
581 let mut run_end = 0;
582 let mut color = black();
583 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle, Range<usize>)> = None;
584 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
585 let text_system = cx.text_system().clone();
586 let mut glyph_origin = point(
587 aligned_origin_x(
588 origin,
589 align_width.unwrap_or(layout.width),
590 px(0.0),
591 &align,
592 layout,
593 wraps.peek(),
594 ),
595 origin.y,
596 );
597 let mut prev_glyph_position = Point::default();
598 let mut max_glyph_size = size(px(0.), px(0.));
599 let mut first_glyph_x = origin.x;
600 for (run_ix, run) in layout.runs.iter().enumerate() {
601 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
602
603 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
604 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
605 if glyph_ix == 0 && run_ix == 0 {
606 first_glyph_x = glyph_origin.x;
607 }
608
609 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
610 wraps.next();
611 if let Some((underline_origin, underline_style, underline_range)) =
612 current_underline.as_mut()
613 {
614 if glyph_origin.x == underline_origin.x {
615 underline_origin.x -= max_glyph_size.width.half();
616 };
617 paint_underline(
618 underline_range.clone(),
619 *underline_origin,
620 glyph_origin.x - underline_origin.x,
621 underline_style,
622 window,
623 );
624 if glyph.index < run_end {
625 underline_origin.x = origin.x;
626 underline_origin.y += line_height;
627 } else {
628 current_underline = None;
629 }
630 }
631 if let Some((strikethrough_origin, strikethrough_style)) =
632 current_strikethrough.as_mut()
633 {
634 if glyph_origin.x == strikethrough_origin.x {
635 strikethrough_origin.x -= max_glyph_size.width.half();
636 };
637 window.paint_strikethrough(
638 *strikethrough_origin,
639 glyph_origin.x - strikethrough_origin.x,
640 strikethrough_style,
641 );
642 if glyph.index < run_end {
643 strikethrough_origin.x = origin.x;
644 strikethrough_origin.y += line_height;
645 } else {
646 current_strikethrough = None;
647 }
648 }
649
650 glyph_origin.x = aligned_origin_x(
651 origin,
652 align_width.unwrap_or(layout.width),
653 glyph.position.x,
654 &align,
655 layout,
656 wraps.peek(),
657 );
658 glyph_origin.y += line_height;
659 }
660 prev_glyph_position = glyph.position;
661
662 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle, Range<usize>)> =
663 None;
664 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
665 if glyph.index >= run_end {
666 let mut style_run = decoration_runs.next();
667
668 while let Some(run) = style_run {
670 if glyph.index < run_end + (run.len as usize) {
671 break;
672 }
673 run_end += run.len as usize;
674 style_run = decoration_runs.next();
675 }
676
677 if let Some(style_run) = style_run {
678 let style_run_start = run_end;
679 if let Some((_, underline_style, underline_range)) = &mut current_underline
680 {
681 if style_run.underline.as_ref() != Some(underline_style) {
682 finished_underline = current_underline.take();
683 } else {
684 underline_range.end = style_run_start + style_run.len as usize;
685 }
686 }
687 if let Some(run_underline) = style_run.underline.as_ref() {
688 current_underline.get_or_insert((
689 point(glyph_origin.x, glyph_origin.y + underline_y_offset),
690 UnderlineStyle {
691 color: Some(run_underline.color.unwrap_or(style_run.color)),
692 thickness: run_underline.thickness,
693 wavy: run_underline.wavy,
694 },
695 style_run_start..style_run_start + style_run.len as usize,
696 ));
697 }
698 if let Some((_, strikethrough_style)) = &mut current_strikethrough
699 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
700 {
701 finished_strikethrough = current_strikethrough.take();
702 }
703 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
704 current_strikethrough.get_or_insert((
705 point(
706 glyph_origin.x,
707 glyph_origin.y
708 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
709 ),
710 StrikethroughStyle {
711 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
712 thickness: run_strikethrough.thickness,
713 },
714 ));
715 }
716
717 run_end += style_run.len as usize;
718 color = style_run.color;
719 } else {
720 run_end = layout.len;
721 finished_underline = current_underline.take();
722 finished_strikethrough = current_strikethrough.take();
723 }
724 }
725
726 if let Some((mut underline_origin, underline_style, underline_range)) =
727 finished_underline
728 {
729 if underline_origin.x == glyph_origin.x {
730 underline_origin.x -= max_glyph_size.width.half();
731 };
732 paint_underline(
733 underline_range,
734 underline_origin,
735 glyph_origin.x - underline_origin.x,
736 &underline_style,
737 window,
738 );
739 }
740
741 if let Some((mut strikethrough_origin, strikethrough_style)) =
742 finished_strikethrough
743 {
744 if strikethrough_origin.x == glyph_origin.x {
745 strikethrough_origin.x -= max_glyph_size.width.half();
746 };
747 window.paint_strikethrough(
748 strikethrough_origin,
749 glyph_origin.x - strikethrough_origin.x,
750 &strikethrough_style,
751 );
752 }
753
754 let max_glyph_bounds = Bounds {
755 origin: glyph_origin,
756 size: max_glyph_size,
757 };
758
759 let content_mask = window.content_mask();
760 if max_glyph_bounds.intersects(&content_mask.bounds) {
761 let vertical_offset = point(px(0.0), glyph.position.y);
762 if glyph.is_emoji {
763 window.paint_emoji(
764 glyph_origin + baseline_offset + vertical_offset,
765 run.font_id,
766 glyph.id,
767 layout.font_size,
768 )?;
769 } else {
770 window.paint_glyph(
771 glyph_origin + baseline_offset + vertical_offset,
772 run.font_id,
773 glyph.id,
774 layout.font_size,
775 color,
776 )?;
777 }
778 }
779 }
780 }
781
782 let mut last_line_end_x = first_glyph_x + layout.width;
783 if let Some(boundary) = wrap_boundaries.last() {
784 let run = &layout.runs[boundary.run_ix];
785 let glyph = &run.glyphs[boundary.glyph_ix];
786 last_line_end_x -= glyph.position.x;
787 }
788
789 if let Some((mut underline_start, underline_style, underline_range)) =
790 current_underline.take()
791 {
792 if last_line_end_x == underline_start.x {
793 underline_start.x -= max_glyph_size.width.half()
794 };
795 paint_underline(
796 underline_range,
797 underline_start,
798 last_line_end_x - underline_start.x,
799 &underline_style,
800 window,
801 );
802 }
803
804 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
805 if last_line_end_x == strikethrough_start.x {
806 strikethrough_start.x -= max_glyph_size.width.half()
807 };
808 window.paint_strikethrough(
809 strikethrough_start,
810 last_line_end_x - strikethrough_start.x,
811 &strikethrough_style,
812 );
813 }
814
815 Ok(())
816 })
817}
818
819fn paint_line_background(
820 origin: Point<Pixels>,
821 layout: &LineLayout,
822 line_height: Pixels,
823 align: TextAlign,
824 align_width: Option<Pixels>,
825 decoration_runs: &[DecorationRun],
826 wrap_boundaries: &[WrapBoundary],
827 window: &mut Window,
828 cx: &mut App,
829) -> Result<()> {
830 let line_bounds = line_paint_bounds(
831 origin,
832 layout,
833 line_height,
834 align,
835 align_width,
836 wrap_boundaries,
837 );
838 window.paint_layer(line_bounds, |window| {
839 let mut decoration_runs = decoration_runs.iter();
840 let mut wraps = wrap_boundaries.iter().peekable();
841 let mut run_end = 0;
842 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
843 let text_system = cx.text_system().clone();
844 let mut glyph_origin = point(
845 aligned_origin_x(
846 origin,
847 align_width.unwrap_or(layout.width),
848 px(0.0),
849 &align,
850 layout,
851 wraps.peek(),
852 ),
853 origin.y,
854 );
855 let mut prev_glyph_position = Point::default();
856 let mut max_glyph_size = size(px(0.), px(0.));
857 for (run_ix, run) in layout.runs.iter().enumerate() {
858 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
859
860 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
861 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
862
863 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
864 wraps.next();
865 if let Some((background_origin, background_color)) = current_background.as_mut()
866 {
867 if glyph_origin.x == background_origin.x {
868 background_origin.x -= max_glyph_size.width.half()
869 }
870 window.paint_quad(fill(
871 Bounds {
872 origin: *background_origin,
873 size: size(glyph_origin.x - background_origin.x, line_height),
874 },
875 *background_color,
876 ));
877 if glyph.index < run_end {
878 background_origin.x = origin.x;
879 background_origin.y += line_height;
880 } else {
881 current_background = None;
882 }
883 }
884
885 glyph_origin.x = aligned_origin_x(
886 origin,
887 align_width.unwrap_or(layout.width),
888 glyph.position.x,
889 &align,
890 layout,
891 wraps.peek(),
892 );
893 glyph_origin.y += line_height;
894 }
895 prev_glyph_position = glyph.position;
896
897 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
898 if glyph.index >= run_end {
899 let mut style_run = decoration_runs.next();
900
901 while let Some(run) = style_run {
903 if glyph.index < run_end + (run.len as usize) {
904 break;
905 }
906 run_end += run.len as usize;
907 style_run = decoration_runs.next();
908 }
909
910 if let Some(style_run) = style_run {
911 if let Some((_, background_color)) = &mut current_background
912 && style_run.background_color.as_ref() != Some(background_color)
913 {
914 finished_background = current_background.take();
915 }
916 if let Some(run_background) = style_run.background_color {
917 current_background.get_or_insert((
918 point(glyph_origin.x, glyph_origin.y),
919 run_background,
920 ));
921 }
922 run_end += style_run.len as usize;
923 } else {
924 run_end = layout.len;
925 finished_background = current_background.take();
926 }
927 }
928
929 if let Some((mut background_origin, background_color)) = finished_background {
930 let mut width = glyph_origin.x - background_origin.x;
931 if background_origin.x == glyph_origin.x {
932 background_origin.x -= max_glyph_size.width.half();
933 };
934 window.paint_quad(fill(
935 Bounds {
936 origin: background_origin,
937 size: size(width, line_height),
938 },
939 background_color,
940 ));
941 }
942 }
943 }
944
945 let mut last_line_end_x = origin.x + layout.width;
946 if let Some(boundary) = wrap_boundaries.last() {
947 let run = &layout.runs[boundary.run_ix];
948 let glyph = &run.glyphs[boundary.glyph_ix];
949 last_line_end_x -= glyph.position.x;
950 }
951
952 if let Some((mut background_origin, background_color)) = current_background.take() {
953 if last_line_end_x == background_origin.x {
954 background_origin.x -= max_glyph_size.width.half()
955 };
956 window.paint_quad(fill(
957 Bounds {
958 origin: background_origin,
959 size: size(last_line_end_x - background_origin.x, line_height),
960 },
961 background_color,
962 ));
963 }
964
965 Ok(())
966 })
967}
968
969fn line_paint_bounds(
970 origin: Point<Pixels>,
971 layout: &LineLayout,
972 line_height: Pixels,
973 align: TextAlign,
974 align_width: Option<Pixels>,
975 wrap_boundaries: &[WrapBoundary],
976) -> Bounds<Pixels> {
977 let mut bounds = Bounds::new(
978 origin,
979 size(
980 layout.width,
981 line_height * (wrap_boundaries.len() as f32 + 1.),
982 ),
983 );
984 if align == TextAlign::Left || layout.len == 0 {
985 return bounds;
986 }
987
988 let align_width = align_width.unwrap_or(layout.width);
989 let mut row_start = Pixels::ZERO;
990 let row_ends = wrap_boundaries
991 .iter()
992 .map(|boundary| {
993 layout.runs[boundary.run_ix].glyphs[boundary.glyph_ix]
994 .position
995 .x
996 })
997 .chain([layout.width]);
998 for (row, row_end) in row_ends.enumerate() {
999 let width = row_end - row_start;
1000 let offset = match align {
1001 TextAlign::Left => Pixels::ZERO,
1002 TextAlign::Center => (align_width - width) / 2.,
1003 TextAlign::Right => align_width - width,
1004 };
1005 bounds = bounds.union(&Bounds::new(
1006 point(origin.x + offset, origin.y + line_height * row as f32),
1007 size(width, line_height),
1008 ));
1009 row_start = row_end;
1010 }
1011 bounds
1012}
1013
1014fn aligned_origin_x(
1015 origin: Point<Pixels>,
1016 align_width: Pixels,
1017 last_glyph_x: Pixels,
1018 align: &TextAlign,
1019 layout: &LineLayout,
1020 wrap_boundary: Option<&&WrapBoundary>,
1021) -> Pixels {
1022 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
1023 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
1024 } else {
1025 layout.width
1026 };
1027
1028 let line_width = end_of_line - last_glyph_x;
1029
1030 match align {
1031 TextAlign::Left => origin.x,
1032 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
1033 TextAlign::Right => origin.x + align_width - line_width,
1034 }
1035}
1036
1037#[cfg(test)]
1038mod tests {
1039 use super::*;
1040 use crate::{
1041 AppContext as _, Context, FontId, GlyphId, IntoElement, Render, ShapedGlyph, ShapedRun,
1042 Styled, TestAppContext, TextRun, Underline, canvas, font, hsla,
1043 };
1044 use std::rc::Rc;
1045
1046 fn make_shaped_line(
1049 text: &str,
1050 glyphs: &[(usize, f32)],
1051 width: f32,
1052 decorations: &[DecorationRun],
1053 ) -> ShapedLine {
1054 let shaped_glyphs: Vec<ShapedGlyph> = glyphs
1055 .iter()
1056 .map(|&(index, x)| ShapedGlyph {
1057 id: GlyphId(0),
1058 position: point(px(x), px(0.0)),
1059 index,
1060 is_emoji: false,
1061 })
1062 .collect();
1063
1064 ShapedLine {
1065 layout: Arc::new(LineLayout {
1066 font_size: px(16.0),
1067 width: px(width),
1068 ascent: px(12.0),
1069 descent: px(4.0),
1070 runs: vec![ShapedRun {
1071 font_id: FontId(0),
1072 glyphs: shaped_glyphs,
1073 }],
1074 len: text.len(),
1075 }),
1076 text: SharedString::new(text),
1077 decoration_runs: SmallVec::from(decorations.to_vec()),
1078 }
1079 }
1080
1081 #[test]
1082 fn test_aligned_line_paint_bounds() {
1083 let line = make_shaped_line("abcd", &[(0, 0.), (1, 10.), (2, 20.), (3, 30.)], 40., &[]);
1084 let origin = point(px(10.), px(20.));
1085 let line_height = px(16.);
1086 for (wrapped, align_width, expected_edges) in [
1087 (false, None, [(10., 50.), (10., 50.), (10., 50.)]),
1088 (true, None, [(10., 50.), (10., 50.), (10., 50.)]),
1089 (false, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1090 (true, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1091 (false, Some(100.), [(10., 50.), (10., 80.), (10., 110.)]),
1092 (true, Some(100.), [(10., 50.), (10., 70.), (10., 110.)]),
1093 (false, Some(10.), [(10., 50.), (-5., 50.), (-20., 50.)]),
1094 (true, Some(10.), [(10., 50.), (5., 50.), (0., 50.)]),
1095 (false, Some(0.), [(10., 50.), (-10., 50.), (-30., 50.)]),
1096 (true, Some(0.), [(10., 50.), (0., 50.), (-10., 50.)]),
1097 ] {
1098 let boundaries = if wrapped {
1099 vec![WrapBoundary {
1100 run_ix: 0,
1101 glyph_ix: 2,
1102 }]
1103 } else {
1104 Vec::new()
1105 };
1106 for (align, (left, right)) in [TextAlign::Left, TextAlign::Center, TextAlign::Right]
1107 .into_iter()
1108 .zip(expected_edges)
1109 {
1110 assert_eq!(
1111 line_paint_bounds(
1112 origin,
1113 &line.layout,
1114 line_height,
1115 align,
1116 align_width.map(px),
1117 &boundaries,
1118 ),
1119 Bounds::new(
1120 point(px(left), origin.y),
1121 size(px(right - left), px(if wrapped { 32. } else { 16. })),
1122 ),
1123 "wrapped={wrapped}, align={align:?}, align_width={align_width:?}",
1124 );
1125 }
1126 }
1127 let empty = make_shaped_line("", &[], 0., &[]);
1128 for align in [TextAlign::Left, TextAlign::Center, TextAlign::Right] {
1129 assert_eq!(
1130 line_paint_bounds(
1131 origin,
1132 &empty.layout,
1133 line_height,
1134 align,
1135 Some(px(100.)),
1136 &[]
1137 ),
1138 Bounds::new(origin, size(Pixels::ZERO, line_height)),
1139 );
1140 }
1141 }
1142
1143 #[gpui::test]
1144 fn test_underline_handler_matches_default_paint(cx: &mut TestAppContext) {
1145 test_underline_handler_at_scales(cx, |window, cx| {
1146 let first_style = UnderlineStyle {
1147 thickness: px(1.),
1148 color: Some(hsla(0., 1., 0.5, 1.)),
1149 wavy: true,
1150 };
1151 let last_style = UnderlineStyle {
1152 color: Some(hsla(0.5, 1., 0.5, 1.)),
1153 wavy: false,
1154 ..first_style
1155 };
1156 let fallback_style = UnderlineStyle {
1157 color: Some(black()),
1158 ..first_style
1159 };
1160 let decoration = DecorationRun {
1161 len: 1,
1162 color: black(),
1163 background_color: None,
1164 underline: Some(first_style),
1165 strikethrough: None,
1166 };
1167 let line = underline_test_line(
1168 "aébcde",
1169 &[
1170 decoration.clone(),
1171 DecorationRun {
1172 len: 2,
1173 ..decoration.clone()
1174 },
1175 DecorationRun {
1176 underline: None,
1177 ..decoration.clone()
1178 },
1179 DecorationRun {
1180 underline: Some(UnderlineStyle {
1181 color: None,
1182 ..first_style
1183 }),
1184 ..decoration.clone()
1185 },
1186 DecorationRun {
1187 underline: Some(last_style),
1188 ..decoration.clone()
1189 },
1190 DecorationRun {
1191 underline: Some(last_style),
1192 ..decoration
1193 },
1194 ],
1195 false,
1196 window,
1197 );
1198 assert_eq!(line.width(), px(48.));
1199 for origin_x in [-3.25, 0., 4.25] {
1200 for (align, align_width, offset) in [
1201 (TextAlign::Left, None, 0.),
1202 (TextAlign::Center, None, 0.),
1203 (TextAlign::Right, None, 0.),
1204 (TextAlign::Left, Some(px(96.)), 0.),
1205 (TextAlign::Center, Some(px(96.)), 24.),
1206 (TextAlign::Right, Some(px(96.)), 48.),
1207 ] {
1208 let origin = point(px(origin_x), px(12.25));
1209 let line_height = px(20.);
1210 window.frame_state.next_frame.scene.clear();
1211 line.paint(origin, line_height, align, align_width, window, cx)
1212 .unwrap();
1213 let original = window.frame_state.next_frame.scene.underlines.clone();
1214 window.frame_state.next_frame.scene.clear();
1215 line.layout
1216 .paint(
1217 origin,
1218 line_height,
1219 align,
1220 align_width,
1221 &line.decoration_runs,
1222 window,
1223 cx,
1224 )
1225 .unwrap();
1226 assert_underline_primitives_eq(
1227 &window.frame_state.next_frame.scene.underlines,
1228 &original,
1229 );
1230
1231 window.frame_state.next_frame.scene.clear();
1232 let mut strokes = Vec::new();
1233 line.paint_with_underline_handler(
1234 origin,
1235 line_height,
1236 align,
1237 align_width,
1238 window,
1239 cx,
1240 |range, origin, width, style, window| {
1241 strokes.push((range, origin, width, *style));
1242 window.paint_underline(origin, width, style);
1243 },
1244 )
1245 .unwrap();
1246 let start = px(origin_x + offset);
1247 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1248 assert_eq!(
1249 strokes,
1250 [
1251 (0..3, point(start, y), px(16.), first_style),
1252 (4..5, point(start + px(24.), y), px(8.), fallback_style),
1253 (5..7, point(start + px(32.), y), px(16.), last_style),
1254 ]
1255 );
1256 assert_underline_primitives_eq(
1257 &window.frame_state.next_frame.scene.underlines,
1258 &original,
1259 );
1260
1261 window.frame_state.next_frame.scene.clear();
1262 let mut captured = Vec::new();
1263 line.paint_with_underline_handler(
1264 origin,
1265 line_height,
1266 align,
1267 align_width,
1268 window,
1269 cx,
1270 |range, origin, width, style, _| {
1271 captured.push((range, origin, width, *style))
1272 },
1273 )
1274 .unwrap();
1275 assert_eq!(captured, strokes);
1276 assert_eq!(window.frame_state.next_frame.scene.underlines.len(), 0);
1277 }
1278 }
1279 for text in ["", "abc"] {
1280 let line = underline_test_line(text, &[], false, window);
1281 let mut calls = 0;
1282 line.paint_with_underline_handler(
1283 point(px(4.), px(10.)),
1284 px(20.),
1285 TextAlign::Left,
1286 None,
1287 window,
1288 cx,
1289 |_, _, _, _, _| calls += 1,
1290 )
1291 .unwrap();
1292 assert_eq!(calls, 0);
1293 }
1294 });
1295 }
1296
1297 #[gpui::test]
1298 fn test_underline_handler_reports_zero_advance_geometry(cx: &mut TestAppContext) {
1299 test_underline_handler_at_scales(cx, |window, cx| {
1300 let first_style = UnderlineStyle {
1301 thickness: px(1.),
1302 color: Some(black()),
1303 wavy: true,
1304 };
1305 let last_style = UnderlineStyle {
1306 wavy: false,
1307 ..first_style
1308 };
1309 let decoration = DecorationRun {
1310 len: 1,
1311 color: black(),
1312 background_color: None,
1313 underline: Some(first_style),
1314 strikethrough: None,
1315 };
1316 let line = underline_test_line(
1317 "ab",
1318 &[
1319 decoration.clone(),
1320 DecorationRun {
1321 underline: Some(last_style),
1322 ..decoration
1323 },
1324 ],
1325 true,
1326 window,
1327 );
1328 let half_width = cx
1329 .text_system()
1330 .bounding_box(line.runs[0].font_id, line.font_size)
1331 .size
1332 .width
1333 / 2.;
1334 let origin = point(px(40.25), px(10.25));
1335 let line_height = px(20.);
1336 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1337 for (align, offset) in [
1338 (TextAlign::Left, 0.),
1339 (TextAlign::Center, 16.),
1340 (TextAlign::Right, 32.),
1341 ] {
1342 window.frame_state.next_frame.scene.clear();
1343 line.paint(origin, line_height, align, Some(px(32.)), window, cx)
1344 .unwrap();
1345 let original = window.frame_state.next_frame.scene.underlines.clone();
1346 window.frame_state.next_frame.scene.clear();
1347 let mut strokes = Vec::new();
1348 line.paint_with_underline_handler(
1349 origin,
1350 line_height,
1351 align,
1352 Some(px(32.)),
1353 window,
1354 cx,
1355 |range, origin, width, style, window| {
1356 strokes.push((range, origin, width, *style));
1357 window.paint_underline(origin, width, style);
1358 },
1359 )
1360 .unwrap();
1361 let end = origin.x + px(offset);
1362 let start = point(end - half_width, y);
1363 let width = end - start.x;
1364 assert_eq!(
1365 strokes,
1366 [
1367 (0..1, start, width, first_style),
1368 (1..2, start, width, last_style),
1369 ]
1370 );
1371 assert_underline_primitives_eq(
1372 &window.frame_state.next_frame.scene.underlines,
1373 &original,
1374 );
1375 }
1376 });
1377 }
1378
1379 #[gpui::test]
1380 fn test_underline_handler_matches_wrapped_paint(cx: &mut TestAppContext) {
1381 test_underline_handler_at_scales(cx, |window, cx| {
1382 let style = UnderlineStyle {
1383 thickness: px(1.),
1384 color: Some(black()),
1385 wavy: true,
1386 };
1387 for zero_advance in [false, true] {
1388 let line = underline_test_line(
1389 "abcd",
1390 &[DecorationRun {
1391 len: 4,
1392 color: black(),
1393 background_color: None,
1394 underline: Some(style),
1395 strikethrough: None,
1396 }],
1397 zero_advance,
1398 window,
1399 );
1400 let half_width = cx
1401 .text_system()
1402 .bounding_box(line.runs[0].font_id, line.font_size)
1403 .size
1404 .width
1405 / 2.;
1406 let origin = point(px(40.25), px(10.25));
1407 let line_height = px(20.);
1408 let y = origin.y + underline_y_offset(line_height, line.ascent, line.descent);
1409 let wrapped = WrappedLine {
1410 layout: Arc::new(WrappedLineLayout {
1411 unwrapped_layout: line.layout,
1412 wrap_boundaries: SmallVec::from_buf([WrapBoundary {
1413 run_ix: 0,
1414 glyph_ix: 2,
1415 }]),
1416 wrap_width: Some(px(16.)),
1417 }),
1418 text: line.text,
1419 decoration_runs: line.decoration_runs.into_vec(),
1420 };
1421 window.frame_state.next_frame.scene.clear();
1422 wrapped
1423 .paint(origin, line_height, TextAlign::Left, None, window, cx)
1424 .unwrap();
1425 let original = window.frame_state.next_frame.scene.underlines.clone();
1426 window.frame_state.next_frame.scene.clear();
1427 let mut strokes = Vec::new();
1428 paint_line(
1429 origin,
1430 &wrapped.unwrapped_layout,
1431 line_height,
1432 TextAlign::Left,
1433 Some(px(16.)),
1434 &wrapped.decoration_runs,
1435 &wrapped.wrap_boundaries,
1436 window,
1437 cx,
1438 &mut |range, origin, width, style, window| {
1439 strokes.push((range, origin, width, *style));
1440 window.paint_underline(origin, width, style);
1441 },
1442 )
1443 .unwrap();
1444 let (start, width) = if zero_advance {
1445 let start = origin.x - half_width;
1446 (start, origin.x - start)
1447 } else {
1448 (origin.x, px(16.))
1449 };
1450 assert_eq!(
1451 strokes,
1452 [
1453 (0..4, point(start, y), width, style),
1454 (0..4, point(start, y + line_height), width, style),
1455 ]
1456 );
1457 assert_underline_primitives_eq(
1458 &window.frame_state.next_frame.scene.underlines,
1459 &original,
1460 );
1461 }
1462 });
1463 }
1464
1465 #[test]
1466 fn test_split_at_invariants() {
1467 let line = make_shaped_line(
1469 "abcdef",
1470 &[
1471 (0, 0.0),
1472 (1, 10.0),
1473 (2, 20.0),
1474 (3, 30.0),
1475 (4, 40.0),
1476 (5, 50.0),
1477 ],
1478 60.0,
1479 &[],
1480 );
1481
1482 for i in 0..=6 {
1483 let (left, right) = line.split_at(i);
1484
1485 assert_eq!(
1486 left.width() + right.width(),
1487 line.width(),
1488 "widths must sum at split={i}"
1489 );
1490 assert_eq!(
1491 left.len() + right.len(),
1492 line.len(),
1493 "lengths must sum at split={i}"
1494 );
1495 assert_eq!(
1496 format!("{}{}", left.text.as_ref(), right.text.as_ref()),
1497 "abcdef",
1498 "text must concatenate at split={i}"
1499 );
1500 assert_eq!(left.font_size, line.font_size, "font_size at split={i}");
1501 assert_eq!(right.ascent, line.ascent, "ascent at split={i}");
1502 assert_eq!(right.descent, line.descent, "descent at split={i}");
1503 }
1504
1505 let (left, right) = line.split_at(0);
1507 assert_eq!(left.runs.len(), 0);
1508 assert_eq!(right.runs[0].glyphs.len(), 6);
1509
1510 let (left, right) = line.split_at(6);
1512 assert_eq!(left.runs[0].glyphs.len(), 6);
1513 assert_eq!(right.runs.len(), 0);
1514 }
1515
1516 #[test]
1517 fn test_split_at_glyph_rebasing() {
1518 let line = ShapedLine {
1523 layout: Arc::new(LineLayout {
1524 font_size: px(16.0),
1525 width: px(60.0),
1526 ascent: px(12.0),
1527 descent: px(4.0),
1528 runs: vec![
1529 ShapedRun {
1530 font_id: FontId(0),
1531 glyphs: vec![
1532 ShapedGlyph {
1533 id: GlyphId(0),
1534 position: point(px(0.0), px(0.0)),
1535 index: 0,
1536 is_emoji: false,
1537 },
1538 ShapedGlyph {
1539 id: GlyphId(0),
1540 position: point(px(10.0), px(0.0)),
1541 index: 1,
1542 is_emoji: false,
1543 },
1544 ShapedGlyph {
1545 id: GlyphId(0),
1546 position: point(px(20.0), px(0.0)),
1547 index: 2,
1548 is_emoji: false,
1549 },
1550 ],
1551 },
1552 ShapedRun {
1553 font_id: FontId(1),
1554 glyphs: vec![
1555 ShapedGlyph {
1556 id: GlyphId(0),
1557 position: point(px(30.0), px(0.0)),
1558 index: 3,
1559 is_emoji: false,
1560 },
1561 ShapedGlyph {
1562 id: GlyphId(0),
1563 position: point(px(40.0), px(0.0)),
1564 index: 4,
1565 is_emoji: false,
1566 },
1567 ShapedGlyph {
1568 id: GlyphId(0),
1569 position: point(px(50.0), px(0.0)),
1570 index: 5,
1571 is_emoji: false,
1572 },
1573 ],
1574 },
1575 ],
1576 len: 6,
1577 }),
1578 text: "abcdef".into(),
1579 decoration_runs: SmallVec::new(),
1580 };
1581
1582 let (first, remainder) = line.split_at(2);
1584 assert_eq!(first.text.as_ref(), "ab");
1585 assert_eq!(first.runs.len(), 1);
1586 assert_eq!(first.runs[0].font_id, FontId(0));
1587
1588 assert_eq!(remainder.text.as_ref(), "cdef");
1590 assert_eq!(remainder.runs.len(), 2);
1591 assert_eq!(remainder.runs[0].font_id, FontId(0));
1592 assert_eq!(remainder.runs[0].glyphs.len(), 1);
1593 assert_eq!(remainder.runs[0].glyphs[0].index, 0);
1594 assert_eq!(remainder.runs[0].glyphs[0].position.x, px(0.0));
1595 assert_eq!(remainder.runs[1].font_id, FontId(1));
1596 assert_eq!(remainder.runs[1].glyphs[0].index, 1);
1597 assert_eq!(remainder.runs[1].glyphs[0].position.x, px(10.0));
1598
1599 let (second, final_part) = remainder.split_at(2);
1601 assert_eq!(second.text.as_ref(), "cd");
1602 assert_eq!(final_part.text.as_ref(), "ef");
1603 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1604 assert_eq!(final_part.runs[0].glyphs[0].position.x, px(0.0));
1605
1606 assert_eq!(
1608 first.width() + second.width() + final_part.width(),
1609 line.width()
1610 );
1611 }
1612
1613 #[test]
1614 fn test_split_at_decorations() {
1615 let red = Hsla {
1618 h: 0.0,
1619 s: 1.0,
1620 l: 0.5,
1621 a: 1.0,
1622 };
1623 let green = Hsla {
1624 h: 0.3,
1625 s: 1.0,
1626 l: 0.5,
1627 a: 1.0,
1628 };
1629 let blue = Hsla {
1630 h: 0.6,
1631 s: 1.0,
1632 l: 0.5,
1633 a: 1.0,
1634 };
1635
1636 let line = make_shaped_line(
1637 "abcdef",
1638 &[
1639 (0, 0.0),
1640 (1, 10.0),
1641 (2, 20.0),
1642 (3, 30.0),
1643 (4, 40.0),
1644 (5, 50.0),
1645 ],
1646 60.0,
1647 &[
1648 DecorationRun {
1649 len: 2,
1650 color: red,
1651 background_color: None,
1652 underline: None,
1653 strikethrough: None,
1654 },
1655 DecorationRun {
1656 len: 3,
1657 color: green,
1658 background_color: None,
1659 underline: None,
1660 strikethrough: None,
1661 },
1662 DecorationRun {
1663 len: 1,
1664 color: blue,
1665 background_color: None,
1666 underline: None,
1667 strikethrough: None,
1668 },
1669 ],
1670 );
1671
1672 let (left, right) = line.split_at(3);
1673
1674 assert_eq!(left.decoration_runs.len(), 2);
1676 assert_eq!(left.decoration_runs[0].len, 2);
1677 assert_eq!(left.decoration_runs[0].color, red);
1678 assert_eq!(left.decoration_runs[1].len, 1);
1679 assert_eq!(left.decoration_runs[1].color, green);
1680
1681 assert_eq!(right.decoration_runs.len(), 2);
1683 assert_eq!(right.decoration_runs[0].len, 2);
1684 assert_eq!(right.decoration_runs[0].color, green);
1685 assert_eq!(right.decoration_runs[1].len, 1);
1686 assert_eq!(right.decoration_runs[1].color, blue);
1687 }
1688
1689 struct UnderlineHandlerTestView(Rc<dyn Fn(&mut Window, &mut App)>);
1690
1691 impl Render for UnderlineHandlerTestView {
1692 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
1693 let paint = self.0.clone();
1694 canvas(
1695 |_, _, _| {},
1696 move |_, _, window, cx| {
1697 window.with_element_opacity(Some(0.5), |window| paint(window, cx));
1698 },
1699 )
1700 .size_full()
1701 }
1702 }
1703
1704 fn test_underline_handler_at_scales(
1705 cx: &mut TestAppContext,
1706 paint: impl Fn(&mut Window, &mut App) + 'static,
1707 ) {
1708 let window = cx.add_window(move |_, _| UnderlineHandlerTestView(Rc::new(paint)));
1709 for scale in [1., 1.25, 1.5, 2., 3.] {
1710 cx.simulate_window_scale_factor_change(window.into(), scale);
1711 cx.update_window(window.into(), |_, window, cx| window.draw(cx).clear(cx))
1712 .unwrap();
1713 }
1714 }
1715
1716 fn underline_test_line(
1717 text: &str,
1718 decorations: &[DecorationRun],
1719 zero_advance: bool,
1720 window: &Window,
1721 ) -> ShapedLine {
1722 let mut line = window.text_system().shape_line(
1723 SharedString::new(text),
1724 px(16.),
1725 &[TextRun {
1726 len: text.len(),
1727 font: font(".ZedMono"),
1728 color: black(),
1729 ..TextRun::default()
1730 }],
1731 None,
1732 );
1733 line.decoration_runs = SmallVec::from(decorations.to_vec());
1734 let layout = &line.layout;
1735 let mut runs = layout.runs.clone();
1736 let advance = if zero_advance { px(0.) } else { px(8.) };
1737 let mut width = px(0.);
1738 for glyph in runs.iter_mut().flat_map(|run| &mut run.glyphs) {
1739 glyph.position.x = width;
1740 width += advance;
1741 }
1742 line.layout = Arc::new(LineLayout {
1743 font_size: layout.font_size,
1744 width,
1745 ascent: layout.ascent,
1746 descent: layout.descent,
1747 runs,
1748 len: layout.len,
1749 });
1750 line
1751 }
1752
1753 fn assert_underline_primitives_eq(actual: &[Underline], expected: &[Underline]) {
1754 assert_eq!(actual.len(), expected.len());
1755 for (actual, expected) in actual.iter().zip(expected) {
1756 assert_eq!(actual.bounds, expected.bounds);
1757 assert_eq!(actual.content_mask, expected.content_mask);
1758 assert_eq!(actual.color, expected.color);
1759 assert_eq!(actual.thickness, expected.thickness);
1760 assert_eq!(actual.wavy, expected.wavy);
1761 assert_eq!(actual.order, expected.order);
1762 assert_eq!(actual.pad, expected.pad);
1763 }
1764 }
1765
1766 #[test]
1767 fn test_cursor_preserves_shaping_metadata_across_runs() {
1768 let line = ShapedLine {
1769 layout: Arc::new(LineLayout {
1770 font_size: px(16.0),
1771 width: px(50.0),
1772 ascent: px(12.0),
1773 descent: px(4.0),
1774 runs: vec![
1775 ShapedRun {
1776 font_id: FontId(3),
1777 glyphs: vec![
1778 ShapedGlyph {
1779 id: GlyphId(11),
1780 position: point(px(0.0), px(1.0)),
1781 index: 0,
1782 is_emoji: true,
1783 },
1784 ShapedGlyph {
1785 id: GlyphId(12),
1786 position: point(px(17.0), px(1.0)),
1787 index: 1,
1788 is_emoji: false,
1789 },
1790 ShapedGlyph {
1791 id: GlyphId(13),
1792 position: point(px(19.0), px(-1.0)),
1793 index: 1,
1794 is_emoji: false,
1795 },
1796 ],
1797 },
1798 ShapedRun {
1799 font_id: FontId(8),
1800 glyphs: vec![
1801 ShapedGlyph {
1802 id: GlyphId(21),
1803 position: point(px(25.0), px(1.0)),
1804 index: 5,
1805 is_emoji: true,
1806 },
1807 ShapedGlyph {
1808 id: GlyphId(22),
1809 position: point(px(41.0), px(1.0)),
1810 index: 7,
1811 is_emoji: false,
1812 },
1813 ],
1814 },
1815 ],
1816 len: 10,
1817 }),
1818 text: "a😀bcdef".into(),
1819 decoration_runs: SmallVec::new(),
1820 };
1821 let mut cursor = line.cursor();
1822 let first = cursor.take_until(5);
1823 assert_eq!(first.text.as_ref(), "a😀");
1824 assert_eq!(first.runs[0].font_id, FontId(3));
1825 assert_eq!(first.runs[0].glyphs[0].id, GlyphId(11));
1826 assert!(first.runs[0].glyphs[0].is_emoji);
1827 assert_eq!(first.runs[0].glyphs[1].index, 1);
1828 assert_eq!(first.runs[0].glyphs[1].position, point(px(17.0), px(1.0)));
1829 assert_eq!(first.runs[0].glyphs.len(), 3);
1830 assert_eq!(first.runs[0].glyphs[2].index, 1);
1831 assert_eq!(first.runs[0].glyphs[2].position, point(px(19.0), px(-1.0)));
1832 assert_eq!(cursor.x_offset(), px(25.0));
1833
1834 let second = cursor.take_until(7);
1835 assert_eq!(second.text.as_ref(), "bc");
1836 assert_eq!(second.runs[0].font_id, FontId(8));
1837 assert_eq!(second.runs[0].glyphs[0].id, GlyphId(21));
1838 assert_eq!(second.runs[0].glyphs[0].index, 0);
1839 assert_eq!(second.runs[0].glyphs[0].position, point(px(0.0), px(1.0)));
1840 assert_eq!(cursor.x_offset(), px(41.0));
1841
1842 let final_part = cursor.take_until(10);
1843 assert_eq!(final_part.text.as_ref(), "def");
1844 assert_eq!(final_part.runs[0].font_id, FontId(8));
1845 assert_eq!(final_part.runs[0].glyphs[0].id, GlyphId(22));
1846 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1847 assert_eq!(
1848 final_part.runs[0].glyphs[0].position,
1849 point(px(0.0), px(1.0))
1850 );
1851 }
1852
1853 #[test]
1854 fn test_cursor_preserves_existing_visual_order_splitting() {
1855 let line = make_shaped_line("abc", &[(0, 0.0), (2, 10.0), (1, 20.0)], 30.0, &[]);
1856 let mut cursor = line.cursor();
1857 let mut remainder = line.clone();
1858 let mut previous_boundary = 0;
1859 for boundary in [0, 1, 2, 3] {
1860 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1861 let actual = cursor.take_until(boundary);
1862 assert_eq!(actual.text, expected.text);
1863 assert_eq!(actual.width(), expected.width());
1864 assert_eq!(actual.runs.len(), expected.runs.len());
1865 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1866 assert_eq!(actual.font_id, expected.font_id);
1867 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1868 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1869 assert_eq!(actual.id, expected.id);
1870 assert_eq!(actual.index, expected.index);
1871 assert_eq!(actual.position, expected.position);
1872 }
1873 }
1874 assert_eq!(cursor.x_offset(), line.x_for_index(boundary));
1875 remainder = rest;
1876 previous_boundary = boundary;
1877 }
1878 }
1879
1880 #[test]
1881 fn test_cursor_partitions_one_decoration_across_three_chunks() {
1882 let line = make_shaped_line(
1883 "abcdef",
1884 &[
1885 (0, 0.0),
1886 (1, 10.0),
1887 (2, 20.0),
1888 (3, 30.0),
1889 (4, 40.0),
1890 (5, 50.0),
1891 ],
1892 60.0,
1893 &[DecorationRun {
1894 len: 6,
1895 color: Hsla {
1896 h: 0.2,
1897 s: 0.4,
1898 l: 0.6,
1899 a: 1.0,
1900 },
1901 background_color: None,
1902 underline: None,
1903 strikethrough: None,
1904 }],
1905 );
1906 let mut cursor = line.cursor();
1907 assert_eq!(cursor.take_until(2).decoration_runs[0].len, 2);
1908 assert_eq!(cursor.take_until(4).decoration_runs[0].len, 2);
1909 assert_eq!(cursor.take_until(6).decoration_runs[0].len, 2);
1910 }
1911
1912 #[test]
1913 fn test_cursor_matches_successive_splits_at_ordered_boundaries() {
1914 let decorations: Vec<_> = [2, 0, 3, 1]
1915 .into_iter()
1916 .map(|len| DecorationRun {
1917 len,
1918 color: Hsla {
1919 h: len as f32 / 10.0,
1920 s: 0.5,
1921 l: 0.5,
1922 a: 1.0,
1923 },
1924 background_color: Some(black()),
1925 underline: None,
1926 strikethrough: None,
1927 })
1928 .collect();
1929 let line = make_shaped_line(
1930 "abcdef",
1931 &[(0, 5.0), (0, 5.0), (2, 15.0), (4, 25.0), (5, 35.0)],
1932 45.0,
1933 &decorations,
1934 );
1935 for first in 0..=line.len() {
1936 for second in first..=line.len() {
1937 let mut cursor = line.cursor();
1938 let mut remainder = line.clone();
1939 let mut previous_boundary = 0;
1940 let mut total_width = px(0.0);
1941 let mut text = String::new();
1942 for boundary in [first, second, line.len(), line.len()] {
1943 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1944 let actual = cursor.take_until(boundary);
1945 assert_eq!(actual.text, expected.text);
1946 assert_eq!(actual.len(), expected.len());
1947 assert_eq!(actual.width(), expected.width());
1948 assert_eq!(actual.runs.len(), expected.runs.len());
1949 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1950 assert_eq!(actual.font_id, expected.font_id);
1951 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1952 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1953 assert_eq!(actual.id, expected.id);
1954 assert_eq!(actual.index, expected.index);
1955 assert_eq!(actual.position, expected.position);
1956 }
1957 }
1958 assert_eq!(actual.decoration_runs.len(), expected.decoration_runs.len());
1959 for (actual, expected) in
1960 actual.decoration_runs.iter().zip(&expected.decoration_runs)
1961 {
1962 assert_eq!(actual.len, expected.len);
1963 assert_eq!(actual.color, expected.color);
1964 assert_eq!(actual.background_color, expected.background_color);
1965 }
1966 total_width += actual.width();
1967 text.push_str(&actual.text);
1968 remainder = rest;
1969 previous_boundary = boundary;
1970 }
1971 assert_eq!(total_width, line.width());
1972 assert_eq!(text, line.text.as_ref());
1973 }
1974 }
1975 }
1976
1977 #[test]
1978 fn test_cursor_empty_chunks_and_repeated_boundaries() {
1979 let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1980 let mut cursor = line.cursor();
1981 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1982 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1983 assert_eq!(cursor.take_until(1).text.as_ref(), "a");
1984 assert_eq!(cursor.take_until(2).text.as_ref(), "b");
1985 assert_eq!(cursor.take_until(2).text.as_ref(), "");
1986 let empty = make_shaped_line("", &[], 0.0, &[]);
1987 let piece = empty.cursor().take_until(0);
1988 assert!(piece.text.is_empty());
1989 assert!(piece.runs.is_empty());
1990 assert_eq!(piece.width(), px(0.0));
1991 }
1992
1993 #[test]
1994 fn test_cursor_rejects_invalid_boundaries() {
1995 let line = make_shaped_line("é", &[(0, 0.0)], 10.0, &[]);
1996 let mut cursor = line.cursor();
1997 assert!(
1998 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1999 cursor.take_until(1);
2000 }))
2001 .is_err()
2002 );
2003 let mut cursor = line.cursor();
2004 cursor.take_until(2);
2005 assert!(
2006 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2007 cursor.take_until(0);
2008 }))
2009 .is_err()
2010 );
2011 let mut cursor = line.cursor();
2012 assert!(
2013 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2014 cursor.take_until(3);
2015 }))
2016 .is_err()
2017 );
2018 }
2019}