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