1use crate::{
2 App, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result, SharedString, StrikethroughStyle,
3 TextAlign, TextShadow, UnderlineStyle, Window, WrapBoundary, WrappedLineLayout, black, fill,
4 point, px, size,
5};
6use derive_more::{Deref, DerefMut};
7use smallvec::SmallVec;
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
12pub struct DecorationRun {
13 pub len: u32,
15
16 pub color: Hsla,
18
19 pub background_color: Option<Hsla>,
21
22 pub underline: Option<UnderlineStyle>,
24
25 pub strikethrough: Option<StrikethroughStyle>,
27}
28
29#[derive(Clone, Default, Debug, Deref, DerefMut)]
31pub struct ShapedLine {
32 #[deref]
33 #[deref_mut]
34 pub(crate) layout: Arc<LineLayout>,
35 pub text: SharedString,
37 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
38}
39
40impl ShapedLine {
41 pub fn cursor(&self) -> Option<ShapedLineCursor<'_>> {
47 if self.len() != self.text.len() {
48 return None;
49 }
50 let byte_ordered = self
51 .layout
52 .runs
53 .iter()
54 .flat_map(|run| run.glyphs.iter().map(|glyph| glyph.index))
55 .is_sorted();
56 Some(ShapedLineCursor {
57 line: self,
58 unordered_remainder: (!byte_ordered).then(|| self.clone()),
59 byte_index: 0,
60 run_index: 0,
61 glyph_index: 0,
62 decoration_index: 0,
63 decoration_offset: 0,
64 x_offset: px(0.),
65 })
66 }
67
68 #[allow(clippy::len_without_is_empty)]
70 pub fn len(&self) -> usize {
71 self.layout.len
72 }
73
74 pub fn with_len(mut self, len: usize) -> Self {
77 let layout = self.layout.as_ref();
78 self.layout = Arc::new(LineLayout {
79 font_size: layout.font_size,
80 width: layout.width,
81 ascent: layout.ascent,
82 descent: layout.descent,
83 runs: layout.runs.clone(),
84 len,
85 });
86 self
87 }
88
89 pub fn paint(
91 &self,
92 origin: Point<Pixels>,
93 line_height: Pixels,
94 window: &mut Window,
95 cx: &mut App,
96 ) -> Result<()> {
97 paint_line(
98 origin,
99 &self.layout,
100 line_height,
101 TextAlign::default(),
102 None,
103 &self.decoration_runs,
104 &[],
105 window,
106 cx,
107 )?;
108
109 Ok(())
110 }
111
112 pub fn paint_background(
114 &self,
115 origin: Point<Pixels>,
116 line_height: Pixels,
117 window: &mut Window,
118 cx: &mut App,
119 ) -> Result<()> {
120 paint_line_background(
121 origin,
122 &self.layout,
123 line_height,
124 TextAlign::default(),
125 None,
126 &self.decoration_runs,
127 &[],
128 window,
129 cx,
130 )?;
131
132 Ok(())
133 }
134
135 pub fn paint_shadow(
137 &self,
138 origin: Point<Pixels>,
139 line_height: Pixels,
140 shadow: &TextShadow,
141 window: &mut Window,
142 cx: &mut App,
143 ) -> Result<()> {
144 let shadow_runs: SmallVec<[DecorationRun; 32]> = self
145 .decoration_runs
146 .iter()
147 .map(|run| DecorationRun {
148 len: run.len,
149 color: shadow.color,
150 background_color: None,
151 underline: None,
152 strikethrough: None,
153 })
154 .collect();
155 let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
156 paint_line(
157 shadow_origin,
158 &self.layout,
159 line_height,
160 TextAlign::default(),
161 None,
162 &shadow_runs,
163 &[],
164 window,
165 cx,
166 )
167 }
168
169 pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) {
179 let (left_layout, right_layout) = self.layout.split_at(byte_index);
180
181 let mut left_decorations = SmallVec::new();
183 let mut right_decorations = SmallVec::new();
184 let mut decoration_offset = 0u32;
185 let split_point = byte_index as u32;
186
187 for decoration in &self.decoration_runs {
188 let run_end = decoration_offset + decoration.len;
189
190 if run_end <= split_point {
191 left_decorations.push(decoration.clone());
192 } else if decoration_offset >= split_point {
193 right_decorations.push(decoration.clone());
194 } else {
195 let left_len = split_point - decoration_offset;
196 let right_len = run_end - split_point;
197 left_decorations.push(DecorationRun {
198 len: left_len,
199 color: decoration.color,
200 background_color: decoration.background_color,
201 underline: decoration.underline,
202 strikethrough: decoration.strikethrough,
203 });
204 right_decorations.push(DecorationRun {
205 len: right_len,
206 color: decoration.color,
207 background_color: decoration.background_color,
208 underline: decoration.underline,
209 strikethrough: decoration.strikethrough,
210 });
211 }
212
213 decoration_offset = run_end;
214 }
215
216 let left_text = if byte_index == self.text.len() {
218 self.text.clone()
219 } else {
220 SharedString::new(&self.text[..byte_index])
221 };
222 let right_text = if byte_index == 0 {
223 self.text.clone()
224 } else {
225 SharedString::new(&self.text[byte_index..])
226 };
227
228 let left = ShapedLine {
229 layout: Arc::new(left_layout),
230 text: left_text,
231 decoration_runs: left_decorations,
232 };
233
234 let right = ShapedLine {
235 layout: Arc::new(right_layout),
236 text: right_text,
237 decoration_runs: right_decorations,
238 };
239
240 (left, right)
241 }
242}
243
244pub struct ShapedLineCursor<'a> {
251 line: &'a ShapedLine,
252 unordered_remainder: Option<ShapedLine>,
254 byte_index: usize,
255 run_index: usize,
256 glyph_index: usize,
257 decoration_index: usize,
258 decoration_offset: u32,
259 x_offset: Pixels,
260}
261
262impl<'a> ShapedLineCursor<'a> {
263 pub fn take_until(&mut self, byte_index: usize) -> ShapedLine {
268 assert!(
269 byte_index >= self.byte_index,
270 "split boundary moved backwards"
271 );
272 assert!(
273 byte_index <= self.line.len(),
274 "split boundary exceeds line length"
275 );
276 assert!(
277 self.line.text.is_char_boundary(byte_index),
278 "split boundary is not a UTF-8 character boundary"
279 );
280 let previous_index = self.byte_index;
281 let previous_x = self.x_offset;
282 if let Some(remainder) = &mut self.unordered_remainder {
283 let (piece, rest) = remainder.split_at(byte_index - previous_index);
284 *remainder = rest;
285 self.byte_index = byte_index;
286 self.x_offset = self.line.layout.x_for_index(byte_index);
287 return piece;
288 }
289 let mut runs = Vec::new();
290 let mut next_x = self.line.layout.width;
291 while let Some(run) = self.line.layout.runs.get(self.run_index) {
292 let start = self.glyph_index;
293 while let Some(glyph) = run.glyphs.get(self.glyph_index) {
294 if glyph.index >= byte_index {
295 break;
296 }
297 self.glyph_index += 1;
298 }
299 let end = self.glyph_index;
300 if start < end {
301 runs.push(crate::ShapedRun {
302 font_id: run.font_id,
303 glyphs: run.glyphs[start..end]
304 .iter()
305 .map(|glyph| crate::ShapedGlyph {
306 id: glyph.id,
307 position: point(glyph.position.x - previous_x, glyph.position.y),
308 index: glyph.index - previous_index,
309 is_emoji: glyph.is_emoji,
310 })
311 .collect(),
312 });
313 }
314 if let Some(glyph) = run.glyphs.get(self.glyph_index) {
315 next_x = glyph.position.x;
316 break;
317 }
318 self.run_index += 1;
319 self.glyph_index = 0;
320 }
321 let mut decorations = SmallVec::new();
322 while let Some(decoration) = self.line.decoration_runs.get(self.decoration_index)
323 && (self.decoration_offset < byte_index as u32
324 || (decoration.len == 0 && self.decoration_offset == byte_index as u32))
325 {
326 let end = self.decoration_offset + decoration.len;
327 let start = self.decoration_offset.max(previous_index as u32);
328 let len = end.min(byte_index as u32) - start;
329 if len > 0 || decoration.len == 0 {
330 decorations.push(DecorationRun {
331 len,
332 color: decoration.color,
333 background_color: decoration.background_color,
334 underline: decoration.underline,
335 strikethrough: decoration.strikethrough,
336 });
337 }
338 if end <= byte_index as u32 {
339 self.decoration_index += 1;
340 self.decoration_offset = end;
341 } else {
342 break;
343 }
344 }
345 self.byte_index = byte_index;
346 self.x_offset = next_x;
347 ShapedLine {
348 layout: Arc::new(LineLayout {
349 font_size: self.line.layout.font_size,
350 width: next_x - previous_x,
351 ascent: self.line.layout.ascent,
352 descent: self.line.layout.descent,
353 runs,
354 len: byte_index - previous_index,
355 }),
356 text: SharedString::new(&self.line.text[previous_index..byte_index]),
357 decoration_runs: decorations,
358 }
359 }
360
361 pub fn x_offset(&self) -> Pixels {
363 self.x_offset
364 }
365}
366
367impl LineLayout {
368 pub fn paint(
374 &self,
375 origin: Point<Pixels>,
376 line_height: Pixels,
377 align: TextAlign,
378 align_width: Option<Pixels>,
379 decoration_runs: &[DecorationRun],
380 window: &mut Window,
381 cx: &mut App,
382 ) -> Result<()> {
383 paint_line(
384 origin,
385 self,
386 line_height,
387 align,
388 align_width,
389 decoration_runs,
390 &[],
391 window,
392 cx,
393 )
394 }
395
396 pub fn paint_background(
402 &self,
403 origin: Point<Pixels>,
404 line_height: Pixels,
405 align: TextAlign,
406 align_width: Option<Pixels>,
407 decoration_runs: &[DecorationRun],
408 window: &mut Window,
409 cx: &mut App,
410 ) -> Result<()> {
411 paint_line_background(
412 origin,
413 self,
414 line_height,
415 align,
416 align_width,
417 decoration_runs,
418 &[],
419 window,
420 cx,
421 )
422 }
423}
424
425#[derive(Clone, Default, Debug, Deref, DerefMut)]
427pub struct WrappedLine {
428 #[deref]
429 #[deref_mut]
430 pub(crate) layout: Arc<WrappedLineLayout>,
431 pub text: SharedString,
433 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
434}
435
436impl WrappedLine {
437 #[allow(clippy::len_without_is_empty)]
439 pub fn len(&self) -> usize {
440 self.layout.len()
441 }
442
443 pub fn paint(
445 &self,
446 origin: Point<Pixels>,
447 line_height: Pixels,
448 align: TextAlign,
449 bounds: Option<Bounds<Pixels>>,
450 window: &mut Window,
451 cx: &mut App,
452 ) -> Result<()> {
453 let align_width = match bounds {
454 Some(bounds) => Some(bounds.size.width),
455 None => self.layout.wrap_width,
456 };
457
458 paint_line(
459 origin,
460 &self.layout.unwrapped_layout,
461 line_height,
462 align,
463 align_width,
464 &self.decoration_runs,
465 &self.wrap_boundaries,
466 window,
467 cx,
468 )?;
469
470 Ok(())
471 }
472
473 pub fn paint_background(
475 &self,
476 origin: Point<Pixels>,
477 line_height: Pixels,
478 align: TextAlign,
479 bounds: Option<Bounds<Pixels>>,
480 window: &mut Window,
481 cx: &mut App,
482 ) -> Result<()> {
483 let align_width = match bounds {
484 Some(bounds) => Some(bounds.size.width),
485 None => self.layout.wrap_width,
486 };
487
488 paint_line_background(
489 origin,
490 &self.layout.unwrapped_layout,
491 line_height,
492 align,
493 align_width,
494 &self.decoration_runs,
495 &self.wrap_boundaries,
496 window,
497 cx,
498 )?;
499
500 Ok(())
501 }
502
503 pub fn paint_shadow(
505 &self,
506 origin: Point<Pixels>,
507 line_height: Pixels,
508 align: TextAlign,
509 bounds: Option<Bounds<Pixels>>,
510 shadow: &TextShadow,
511 window: &mut Window,
512 cx: &mut App,
513 ) -> Result<()> {
514 let align_width = match bounds {
515 Some(bounds) => Some(bounds.size.width),
516 None => self.layout.wrap_width,
517 };
518 let shadow_runs: SmallVec<[DecorationRun; 32]> = self
519 .decoration_runs
520 .iter()
521 .map(|run| DecorationRun {
522 len: run.len,
523 color: shadow.color,
524 background_color: None,
525 underline: None,
526 strikethrough: None,
527 })
528 .collect();
529 let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
530 paint_line(
531 shadow_origin,
532 &self.layout.unwrapped_layout,
533 line_height,
534 align,
535 align_width,
536 &shadow_runs,
537 &self.wrap_boundaries,
538 window,
539 cx,
540 )
541 }
542}
543
544fn paint_line(
545 origin: Point<Pixels>,
546 layout: &LineLayout,
547 line_height: Pixels,
548 align: TextAlign,
549 align_width: Option<Pixels>,
550 decoration_runs: &[DecorationRun],
551 wrap_boundaries: &[WrapBoundary],
552 window: &mut Window,
553 cx: &mut App,
554) -> Result<()> {
555 let line_bounds = line_paint_bounds(
556 origin,
557 layout,
558 line_height,
559 align,
560 align_width,
561 wrap_boundaries,
562 );
563 window.paint_layer(line_bounds, |window| {
564 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
565 let baseline_offset = point(px(0.), padding_top + layout.ascent);
566 let mut decoration_runs = decoration_runs.iter();
567 let mut wraps = wrap_boundaries.iter().peekable();
568 let mut run_end = 0;
569 let mut color = black();
570 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
571 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
572 let text_system = cx.text_system().clone();
573 let mut glyph_origin = point(
574 aligned_origin_x(
575 origin,
576 align_width.unwrap_or(layout.width),
577 px(0.0),
578 &align,
579 layout,
580 wraps.peek(),
581 ),
582 origin.y,
583 );
584 let mut prev_glyph_position = Point::default();
585 let mut max_glyph_size = size(px(0.), px(0.));
586 let mut first_glyph_x = origin.x;
587 for (run_ix, run) in layout.runs.iter().enumerate() {
588 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
589
590 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
591 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
592 if glyph_ix == 0 && run_ix == 0 {
593 first_glyph_x = glyph_origin.x;
594 }
595
596 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
597 wraps.next();
598 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
599 if glyph_origin.x == underline_origin.x {
600 underline_origin.x -= max_glyph_size.width.half();
601 };
602 window.paint_underline(
603 *underline_origin,
604 glyph_origin.x - underline_origin.x,
605 underline_style,
606 );
607 if glyph.index < run_end {
608 underline_origin.x = origin.x;
609 underline_origin.y += line_height;
610 } else {
611 current_underline = None;
612 }
613 }
614 if let Some((strikethrough_origin, strikethrough_style)) =
615 current_strikethrough.as_mut()
616 {
617 if glyph_origin.x == strikethrough_origin.x {
618 strikethrough_origin.x -= max_glyph_size.width.half();
619 };
620 window.paint_strikethrough(
621 *strikethrough_origin,
622 glyph_origin.x - strikethrough_origin.x,
623 strikethrough_style,
624 );
625 if glyph.index < run_end {
626 strikethrough_origin.x = origin.x;
627 strikethrough_origin.y += line_height;
628 } else {
629 current_strikethrough = None;
630 }
631 }
632
633 glyph_origin.x = aligned_origin_x(
634 origin,
635 align_width.unwrap_or(layout.width),
636 glyph.position.x,
637 &align,
638 layout,
639 wraps.peek(),
640 );
641 glyph_origin.y += line_height;
642 }
643 prev_glyph_position = glyph.position;
644
645 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
646 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
647 if glyph.index >= run_end {
648 let mut style_run = decoration_runs.next();
649
650 while let Some(run) = style_run {
652 if glyph.index < run_end + (run.len as usize) {
653 break;
654 }
655 run_end += run.len as usize;
656 style_run = decoration_runs.next();
657 }
658
659 if let Some(style_run) = style_run {
660 if let Some((_, underline_style)) = &mut current_underline
661 && style_run.underline.as_ref() != Some(underline_style)
662 {
663 finished_underline = current_underline.take();
664 }
665 if let Some(run_underline) = style_run.underline.as_ref() {
666 current_underline.get_or_insert((
667 point(
668 glyph_origin.x,
669 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
670 ),
671 UnderlineStyle {
672 color: Some(run_underline.color.unwrap_or(style_run.color)),
673 thickness: run_underline.thickness,
674 wavy: run_underline.wavy,
675 },
676 ));
677 }
678 if let Some((_, strikethrough_style)) = &mut current_strikethrough
679 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
680 {
681 finished_strikethrough = current_strikethrough.take();
682 }
683 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
684 current_strikethrough.get_or_insert((
685 point(
686 glyph_origin.x,
687 glyph_origin.y
688 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
689 ),
690 StrikethroughStyle {
691 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
692 thickness: run_strikethrough.thickness,
693 },
694 ));
695 }
696
697 run_end += style_run.len as usize;
698 color = style_run.color;
699 } else {
700 run_end = layout.len;
701 finished_underline = current_underline.take();
702 finished_strikethrough = current_strikethrough.take();
703 }
704 }
705
706 if let Some((mut underline_origin, underline_style)) = finished_underline {
707 if underline_origin.x == glyph_origin.x {
708 underline_origin.x -= max_glyph_size.width.half();
709 };
710 window.paint_underline(
711 underline_origin,
712 glyph_origin.x - underline_origin.x,
713 &underline_style,
714 );
715 }
716
717 if let Some((mut strikethrough_origin, strikethrough_style)) =
718 finished_strikethrough
719 {
720 if strikethrough_origin.x == glyph_origin.x {
721 strikethrough_origin.x -= max_glyph_size.width.half();
722 };
723 window.paint_strikethrough(
724 strikethrough_origin,
725 glyph_origin.x - strikethrough_origin.x,
726 &strikethrough_style,
727 );
728 }
729
730 let max_glyph_bounds = Bounds {
731 origin: glyph_origin,
732 size: max_glyph_size,
733 };
734
735 let content_mask = window.content_mask();
736 if max_glyph_bounds.intersects(&content_mask.bounds) {
737 if glyph.is_emoji {
738 window.paint_emoji(
739 glyph_origin + baseline_offset,
740 run.font_id,
741 glyph.id,
742 layout.font_size,
743 )?;
744 } else {
745 window.paint_glyph(
746 glyph_origin + baseline_offset,
747 run.font_id,
748 glyph.id,
749 layout.font_size,
750 color,
751 )?;
752 }
753 }
754 }
755 }
756
757 let mut last_line_end_x = first_glyph_x + layout.width;
758 if let Some(boundary_x) = wrap_boundaries
759 .last()
760 .and_then(|boundary| wrap_boundary_x(layout, boundary))
761 {
762 last_line_end_x -= boundary_x;
763 }
764
765 if let Some((mut underline_start, underline_style)) = current_underline.take() {
766 if last_line_end_x == underline_start.x {
767 underline_start.x -= max_glyph_size.width.half()
768 };
769 window.paint_underline(
770 underline_start,
771 last_line_end_x - underline_start.x,
772 &underline_style,
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_x) = wrap_boundaries
919 .last()
920 .and_then(|boundary| wrap_boundary_x(layout, boundary))
921 {
922 last_line_end_x -= boundary_x;
923 }
924
925 if let Some((mut background_origin, background_color)) = current_background.take() {
926 if last_line_end_x == background_origin.x {
927 background_origin.x -= max_glyph_size.width.half()
928 };
929 window.paint_quad(fill(
930 Bounds {
931 origin: background_origin,
932 size: size(last_line_end_x - background_origin.x, line_height),
933 },
934 background_color,
935 ));
936 }
937
938 Ok(())
939 })
940}
941
942fn line_paint_bounds(
943 origin: Point<Pixels>,
944 layout: &LineLayout,
945 line_height: Pixels,
946 align: TextAlign,
947 align_width: Option<Pixels>,
948 wrap_boundaries: &[WrapBoundary],
949) -> Bounds<Pixels> {
950 let mut bounds = Bounds::new(
951 origin,
952 size(
953 layout.width,
954 line_height * (wrap_boundaries.len() as f32 + 1.),
955 ),
956 );
957 if align == TextAlign::Left || layout.len == 0 {
958 return bounds;
959 }
960
961 let align_width = align_width.unwrap_or(layout.width);
962 let mut row_start = Pixels::ZERO;
963 let row_ends = wrap_boundaries
964 .iter()
965 .filter_map(|boundary| wrap_boundary_x(layout, boundary))
966 .chain([layout.width]);
967 for (row, row_end) in row_ends.enumerate() {
968 let width = row_end - row_start;
969 let offset = match align {
970 TextAlign::Left => Pixels::ZERO,
971 TextAlign::Center => (align_width - width) / 2.,
972 TextAlign::Right => align_width - width,
973 };
974 bounds = bounds.union(&Bounds::new(
975 point(origin.x + offset, origin.y + line_height * row as f32),
976 size(width, line_height),
977 ));
978 row_start = row_end;
979 }
980 bounds
981}
982
983fn wrap_boundary_x(layout: &LineLayout, boundary: &WrapBoundary) -> Option<Pixels> {
986 layout
987 .runs
988 .get(boundary.run_ix)
989 .and_then(|run| run.glyphs.get(boundary.glyph_ix))
990 .map(|glyph| glyph.position.x)
991}
992
993fn aligned_origin_x(
994 origin: Point<Pixels>,
995 align_width: Pixels,
996 last_glyph_x: Pixels,
997 align: &TextAlign,
998 layout: &LineLayout,
999 wrap_boundary: Option<&&WrapBoundary>,
1000) -> Pixels {
1001 let end_of_line = wrap_boundary
1002 .and_then(|boundary| wrap_boundary_x(layout, boundary))
1003 .unwrap_or(layout.width);
1004
1005 let line_width = end_of_line - last_glyph_x;
1006
1007 match align {
1008 TextAlign::Left => origin.x,
1009 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
1010 TextAlign::Right => origin.x + align_width - line_width,
1011 }
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016 use super::*;
1017 use crate::{FontId, GlyphId, ShapedGlyph, ShapedRun};
1018
1019 fn make_shaped_line(
1022 text: &str,
1023 glyphs: &[(usize, f32)],
1024 width: f32,
1025 decorations: &[DecorationRun],
1026 ) -> ShapedLine {
1027 let shaped_glyphs: Vec<ShapedGlyph> = glyphs
1028 .iter()
1029 .map(|&(index, x)| ShapedGlyph {
1030 id: GlyphId(0),
1031 position: point(px(x), px(0.0)),
1032 index,
1033 is_emoji: false,
1034 })
1035 .collect();
1036
1037 ShapedLine {
1038 layout: Arc::new(LineLayout {
1039 font_size: px(16.0),
1040 width: px(width),
1041 ascent: px(12.0),
1042 descent: px(4.0),
1043 runs: vec![ShapedRun {
1044 font_id: FontId(0),
1045 glyphs: shaped_glyphs,
1046 }],
1047 len: text.len(),
1048 }),
1049 text: SharedString::new(text),
1050 decoration_runs: SmallVec::from(decorations.to_vec()),
1051 }
1052 }
1053
1054 #[test]
1055 fn test_aligned_line_paint_bounds() {
1056 let line = make_shaped_line("abcd", &[(0, 0.), (1, 10.), (2, 20.), (3, 30.)], 40., &[]);
1057 let origin = point(px(10.), px(20.));
1058 let line_height = px(16.);
1059 for (wrapped, align_width, expected_edges) in [
1060 (false, None, [(10., 50.), (10., 50.), (10., 50.)]),
1061 (true, None, [(10., 50.), (10., 50.), (10., 50.)]),
1062 (false, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1063 (true, Some(40.), [(10., 50.), (10., 50.), (10., 50.)]),
1064 (false, Some(100.), [(10., 50.), (10., 80.), (10., 110.)]),
1065 (true, Some(100.), [(10., 50.), (10., 70.), (10., 110.)]),
1066 (false, Some(10.), [(10., 50.), (-5., 50.), (-20., 50.)]),
1067 (true, Some(10.), [(10., 50.), (5., 50.), (0., 50.)]),
1068 (false, Some(0.), [(10., 50.), (-10., 50.), (-30., 50.)]),
1069 (true, Some(0.), [(10., 50.), (0., 50.), (-10., 50.)]),
1070 ] {
1071 let boundaries = if wrapped {
1072 vec![WrapBoundary {
1073 run_ix: 0,
1074 glyph_ix: 2,
1075 }]
1076 } else {
1077 Vec::new()
1078 };
1079 for (align, (left, right)) in [TextAlign::Left, TextAlign::Center, TextAlign::Right]
1080 .into_iter()
1081 .zip(expected_edges)
1082 {
1083 assert_eq!(
1084 line_paint_bounds(
1085 origin,
1086 &line.layout,
1087 line_height,
1088 align,
1089 align_width.map(px),
1090 &boundaries,
1091 ),
1092 Bounds::new(
1093 point(px(left), origin.y),
1094 size(px(right - left), px(if wrapped { 32. } else { 16. })),
1095 ),
1096 "wrapped={wrapped}, align={align:?}, align_width={align_width:?}",
1097 );
1098 }
1099 }
1100 let empty = make_shaped_line("", &[], 0., &[]);
1101 for align in [TextAlign::Left, TextAlign::Center, TextAlign::Right] {
1102 assert_eq!(
1103 line_paint_bounds(
1104 origin,
1105 &empty.layout,
1106 line_height,
1107 align,
1108 Some(px(100.)),
1109 &[]
1110 ),
1111 Bounds::new(origin, size(Pixels::ZERO, line_height)),
1112 );
1113 }
1114 }
1115
1116 #[test]
1117 fn test_cursor_preserves_shaping_metadata_across_runs() {
1118 let line = ShapedLine {
1119 layout: Arc::new(LineLayout {
1120 font_size: px(16.0),
1121 width: px(50.0),
1122 ascent: px(12.0),
1123 descent: px(4.0),
1124 runs: vec![
1125 ShapedRun {
1126 font_id: FontId(3),
1127 glyphs: vec![
1128 ShapedGlyph {
1129 id: GlyphId(11),
1130 position: point(px(0.0), px(1.0)),
1131 index: 0,
1132 is_emoji: true,
1133 },
1134 ShapedGlyph {
1135 id: GlyphId(12),
1136 position: point(px(17.0), px(1.0)),
1137 index: 1,
1138 is_emoji: false,
1139 },
1140 ShapedGlyph {
1141 id: GlyphId(13),
1142 position: point(px(19.0), px(-1.0)),
1143 index: 1,
1144 is_emoji: false,
1145 },
1146 ],
1147 },
1148 ShapedRun {
1149 font_id: FontId(8),
1150 glyphs: vec![
1151 ShapedGlyph {
1152 id: GlyphId(21),
1153 position: point(px(25.0), px(1.0)),
1154 index: 5,
1155 is_emoji: true,
1156 },
1157 ShapedGlyph {
1158 id: GlyphId(22),
1159 position: point(px(41.0), px(1.0)),
1160 index: 7,
1161 is_emoji: false,
1162 },
1163 ],
1164 },
1165 ],
1166 len: 10,
1167 }),
1168 text: "a😀bcdef".into(),
1169 decoration_runs: SmallVec::new(),
1170 };
1171 let mut cursor = line.cursor().unwrap();
1172 let first = cursor.take_until(5);
1173 assert_eq!(first.text.as_ref(), "a😀");
1174 assert_eq!(first.runs[0].font_id, FontId(3));
1175 assert_eq!(first.runs[0].glyphs[0].id, GlyphId(11));
1176 assert!(first.runs[0].glyphs[0].is_emoji);
1177 assert_eq!(first.runs[0].glyphs[1].index, 1);
1178 assert_eq!(first.runs[0].glyphs[1].position, point(px(17.0), px(1.0)));
1179 assert_eq!(first.runs[0].glyphs.len(), 3);
1180 assert_eq!(first.runs[0].glyphs[2].index, 1);
1181 assert_eq!(first.runs[0].glyphs[2].position, point(px(19.0), px(-1.0)));
1182 assert_eq!(cursor.x_offset(), px(25.0));
1183
1184 let second = cursor.take_until(7);
1185 assert_eq!(second.text.as_ref(), "bc");
1186 assert_eq!(second.runs[0].font_id, FontId(8));
1187 assert_eq!(second.runs[0].glyphs[0].id, GlyphId(21));
1188 assert_eq!(second.runs[0].glyphs[0].index, 0);
1189 assert_eq!(second.runs[0].glyphs[0].position, point(px(0.0), px(1.0)));
1190 assert_eq!(cursor.x_offset(), px(41.0));
1191
1192 let final_part = cursor.take_until(10);
1193 assert_eq!(final_part.text.as_ref(), "def");
1194 assert_eq!(final_part.runs[0].font_id, FontId(8));
1195 assert_eq!(final_part.runs[0].glyphs[0].id, GlyphId(22));
1196 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1197 assert_eq!(
1198 final_part.runs[0].glyphs[0].position,
1199 point(px(0.0), px(1.0))
1200 );
1201 }
1202
1203 #[test]
1204 fn test_cursor_preserves_existing_visual_order_splitting() {
1205 let line = make_shaped_line("abc", &[(0, 0.0), (2, 10.0), (1, 20.0)], 30.0, &[]);
1206 let mut cursor = line.cursor().unwrap();
1207 let mut remainder = line.clone();
1208 let mut previous_boundary = 0;
1209 for boundary in [0, 1, 2, 3] {
1210 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1211 let actual = cursor.take_until(boundary);
1212 assert_eq!(actual.text, expected.text);
1213 assert_eq!(actual.width, expected.width);
1214 assert_eq!(actual.runs.len(), expected.runs.len());
1215 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1216 assert_eq!(actual.font_id, expected.font_id);
1217 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1218 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1219 assert_eq!(actual.id, expected.id);
1220 assert_eq!(actual.index, expected.index);
1221 assert_eq!(actual.position, expected.position);
1222 }
1223 }
1224 assert_eq!(cursor.x_offset(), line.x_for_index(boundary));
1225 remainder = rest;
1226 previous_boundary = boundary;
1227 }
1228 }
1229
1230 #[test]
1231 fn test_cursor_partitions_one_decoration_across_three_chunks() {
1232 let line = make_shaped_line(
1233 "abcdef",
1234 &[
1235 (0, 0.0),
1236 (1, 10.0),
1237 (2, 20.0),
1238 (3, 30.0),
1239 (4, 40.0),
1240 (5, 50.0),
1241 ],
1242 60.0,
1243 &[DecorationRun {
1244 len: 6,
1245 color: Hsla {
1246 h: 0.2,
1247 s: 0.4,
1248 l: 0.6,
1249 a: 1.0,
1250 },
1251 background_color: None,
1252 underline: None,
1253 strikethrough: None,
1254 }],
1255 );
1256 let mut cursor = line.cursor().unwrap();
1257 assert_eq!(cursor.take_until(2).decoration_runs[0].len, 2);
1258 assert_eq!(cursor.take_until(4).decoration_runs[0].len, 2);
1259 assert_eq!(cursor.take_until(6).decoration_runs[0].len, 2);
1260 }
1261
1262 #[test]
1263 fn test_cursor_matches_successive_splits_at_ordered_boundaries() {
1264 let decorations: Vec<_> = [2, 0, 3, 1]
1265 .into_iter()
1266 .map(|len| DecorationRun {
1267 len,
1268 color: Hsla {
1269 h: len as f32 / 10.0,
1270 s: 0.5,
1271 l: 0.5,
1272 a: 1.0,
1273 },
1274 background_color: Some(black()),
1275 underline: None,
1276 strikethrough: None,
1277 })
1278 .collect();
1279 let line = make_shaped_line(
1280 "abcdef",
1281 &[(0, 5.0), (0, 5.0), (2, 15.0), (4, 25.0), (5, 35.0)],
1282 45.0,
1283 &decorations,
1284 );
1285 for first in 0..=line.len() {
1286 for second in first..=line.len() {
1287 let mut cursor = line.cursor().unwrap();
1288 let mut remainder = line.clone();
1289 let mut previous_boundary = 0;
1290 let mut total_width = px(0.0);
1291 let mut text = String::new();
1292 for boundary in [first, second, line.len(), line.len()] {
1293 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1294 let actual = cursor.take_until(boundary);
1295 assert_eq!(actual.text, expected.text);
1296 assert_eq!(actual.len(), expected.len());
1297 assert_eq!(actual.width, expected.width);
1298 assert_eq!(actual.runs.len(), expected.runs.len());
1299 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1300 assert_eq!(actual.font_id, expected.font_id);
1301 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1302 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1303 assert_eq!(actual.id, expected.id);
1304 assert_eq!(actual.index, expected.index);
1305 assert_eq!(actual.position, expected.position);
1306 }
1307 }
1308 assert_eq!(actual.decoration_runs.len(), expected.decoration_runs.len());
1309 for (actual, expected) in
1310 actual.decoration_runs.iter().zip(&expected.decoration_runs)
1311 {
1312 assert_eq!(actual.len, expected.len);
1313 assert_eq!(actual.color, expected.color);
1314 assert_eq!(actual.background_color, expected.background_color);
1315 }
1316 total_width += actual.width;
1317 text.push_str(&actual.text);
1318 remainder = rest;
1319 previous_boundary = boundary;
1320 }
1321 assert_eq!(total_width, line.width);
1322 assert_eq!(text, line.text.as_ref());
1323 }
1324 }
1325 }
1326
1327 #[test]
1328 fn test_cursor_empty_chunks_and_repeated_boundaries() {
1329 let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1330 let mut cursor = line.cursor().unwrap();
1331 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1332 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1333 assert_eq!(cursor.take_until(1).text.as_ref(), "a");
1334 assert_eq!(cursor.take_until(2).text.as_ref(), "b");
1335 assert_eq!(cursor.take_until(2).text.as_ref(), "");
1336 let empty = make_shaped_line("", &[], 0.0, &[]);
1337 let piece = empty.cursor().unwrap().take_until(0);
1338 assert!(piece.text.is_empty());
1339 assert!(piece.runs.is_empty());
1340 assert_eq!(piece.width, px(0.0));
1341 }
1342
1343 #[test]
1344 fn cursor_rejects_lines_with_adjusted_length() {
1345 let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1346 assert!(line.cursor().is_some());
1347 assert!(line.with_len(4).cursor().is_none());
1350 }
1351
1352 #[test]
1353 fn test_cursor_rejects_invalid_boundaries() {
1354 let line = make_shaped_line("é", &[(0, 0.0)], 10.0, &[]);
1355 let mut cursor = line.cursor().unwrap();
1356 assert!(
1357 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1358 cursor.take_until(1);
1359 }))
1360 .is_err()
1361 );
1362 let mut cursor = line.cursor().unwrap();
1363 cursor.take_until(2);
1364 assert!(
1365 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1366 cursor.take_until(0);
1367 }))
1368 .is_err()
1369 );
1370 let mut cursor = line.cursor().unwrap();
1371 assert!(
1372 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1373 cursor.take_until(3);
1374 }))
1375 .is_err()
1376 );
1377 }
1378}