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,
5};
6use derive_more::{Deref, DerefMut};
7use smallvec::SmallVec;
8use std::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 paint_line(
118 origin,
119 &self.layout,
120 line_height,
121 align,
122 align_width,
123 &self.decoration_runs,
124 &[],
125 window,
126 cx,
127 )?;
128
129 Ok(())
130 }
131
132 pub fn paint_background(
134 &self,
135 origin: Point<Pixels>,
136 line_height: Pixels,
137 align: TextAlign,
138 align_width: Option<Pixels>,
139 window: &mut Window,
140 cx: &mut App,
141 ) -> Result<()> {
142 paint_line_background(
143 origin,
144 &self.layout,
145 line_height,
146 align,
147 align_width,
148 &self.decoration_runs,
149 &[],
150 window,
151 cx,
152 )?;
153
154 Ok(())
155 }
156
157 pub fn split_at(&self, byte_index: usize) -> (ShapedLine, ShapedLine) {
167 let (left_layout, right_layout) = self.layout.split_at(byte_index);
168
169 let mut left_decorations = SmallVec::new();
171 let mut right_decorations = SmallVec::new();
172 let mut decoration_offset = 0u32;
173 let split_point = byte_index as u32;
174
175 for decoration in &self.decoration_runs {
176 let run_end = decoration_offset + decoration.len;
177
178 if run_end <= split_point {
179 left_decorations.push(decoration.clone());
180 } else if decoration_offset >= split_point {
181 right_decorations.push(decoration.clone());
182 } else {
183 let left_len = split_point - decoration_offset;
184 let right_len = run_end - split_point;
185 left_decorations.push(DecorationRun {
186 len: left_len,
187 color: decoration.color,
188 background_color: decoration.background_color,
189 underline: decoration.underline,
190 strikethrough: decoration.strikethrough,
191 });
192 right_decorations.push(DecorationRun {
193 len: right_len,
194 color: decoration.color,
195 background_color: decoration.background_color,
196 underline: decoration.underline,
197 strikethrough: decoration.strikethrough,
198 });
199 }
200
201 decoration_offset = run_end;
202 }
203
204 let left_text = if byte_index == self.text.len() {
206 self.text.clone()
207 } else {
208 SharedString::new(&self.text[..byte_index])
209 };
210 let right_text = if byte_index == 0 {
211 self.text.clone()
212 } else {
213 SharedString::new(&self.text[byte_index..])
214 };
215
216 let left = ShapedLine {
217 layout: Arc::new(left_layout),
218 text: left_text,
219 decoration_runs: left_decorations,
220 };
221
222 let right = ShapedLine {
223 layout: Arc::new(right_layout),
224 text: right_text,
225 decoration_runs: right_decorations,
226 };
227
228 (left, right)
229 }
230}
231
232pub struct ShapedLineCursor<'a> {
239 line: &'a ShapedLine,
240 unordered_remainder: Option<ShapedLine>,
242 byte_index: usize,
243 run_index: usize,
244 glyph_index: usize,
245 decoration_index: usize,
246 decoration_offset: u32,
247 x_offset: Pixels,
248}
249
250impl<'a> ShapedLineCursor<'a> {
251 pub fn take_until(&mut self, byte_index: usize) -> ShapedLine {
256 assert!(
257 byte_index >= self.byte_index,
258 "split boundary moved backwards"
259 );
260 assert!(
261 byte_index <= self.line.len(),
262 "split boundary exceeds line length"
263 );
264 assert!(
265 self.line.text.is_char_boundary(byte_index),
266 "split boundary is not a UTF-8 character boundary"
267 );
268 let previous_index = self.byte_index;
269 let previous_x = self.x_offset;
270 if let Some(remainder) = &mut self.unordered_remainder {
271 let (piece, rest) = remainder.split_at(byte_index - previous_index);
272 *remainder = rest;
273 self.byte_index = byte_index;
274 self.x_offset = self.line.layout.x_for_index(byte_index);
275 return piece;
276 }
277 let mut runs = Vec::new();
278 let mut next_x = self.line.layout.width;
279 while let Some(run) = self.line.layout.runs.get(self.run_index) {
280 let start = self.glyph_index;
281 while let Some(glyph) = run.glyphs.get(self.glyph_index) {
282 if glyph.index >= byte_index {
283 break;
284 }
285 self.glyph_index += 1;
286 }
287 let end = self.glyph_index;
288 if start < end {
289 runs.push(crate::ShapedRun {
290 font_id: run.font_id,
291 glyphs: run.glyphs[start..end]
292 .iter()
293 .map(|glyph| crate::ShapedGlyph {
294 id: glyph.id,
295 position: point(glyph.position.x - previous_x, glyph.position.y),
296 index: glyph.index - previous_index,
297 is_emoji: glyph.is_emoji,
298 })
299 .collect(),
300 });
301 }
302 if let Some(glyph) = run.glyphs.get(self.glyph_index) {
303 next_x = glyph.position.x;
304 break;
305 }
306 self.run_index += 1;
307 self.glyph_index = 0;
308 }
309 let mut decorations = SmallVec::new();
310 while let Some(decoration) = self.line.decoration_runs.get(self.decoration_index)
311 && (self.decoration_offset < byte_index as u32
312 || (decoration.len == 0 && self.decoration_offset == byte_index as u32))
313 {
314 let end = self.decoration_offset + decoration.len;
315 let start = self.decoration_offset.max(previous_index as u32);
316 let len = end.min(byte_index as u32) - start;
317 if len > 0 || decoration.len == 0 {
318 decorations.push(DecorationRun {
319 len,
320 color: decoration.color,
321 background_color: decoration.background_color,
322 underline: decoration.underline,
323 strikethrough: decoration.strikethrough,
324 });
325 }
326 if end <= byte_index as u32 {
327 self.decoration_index += 1;
328 self.decoration_offset = end;
329 } else {
330 break;
331 }
332 }
333 self.byte_index = byte_index;
334 self.x_offset = next_x;
335 ShapedLine {
336 layout: Arc::new(LineLayout {
337 font_size: self.line.layout.font_size,
338 width: next_x - previous_x,
339 ascent: self.line.layout.ascent,
340 descent: self.line.layout.descent,
341 runs,
342 len: byte_index - previous_index,
343 }),
344 text: SharedString::new(&self.line.text[previous_index..byte_index]),
345 decoration_runs: decorations,
346 }
347 }
348
349 pub fn x_offset(&self) -> Pixels {
351 self.x_offset
352 }
353}
354
355pub trait LineLayoutExt {
360 fn paint(
366 &self,
367 origin: Point<Pixels>,
368 line_height: Pixels,
369 align: TextAlign,
370 align_width: Option<Pixels>,
371 decoration_runs: &[DecorationRun],
372 window: &mut Window,
373 cx: &mut App,
374 ) -> Result<()>;
375
376 fn paint_background(
382 &self,
383 origin: Point<Pixels>,
384 line_height: Pixels,
385 align: TextAlign,
386 align_width: Option<Pixels>,
387 decoration_runs: &[DecorationRun],
388 window: &mut Window,
389 cx: &mut App,
390 ) -> Result<()>;
391}
392
393impl LineLayoutExt for LineLayout {
394 fn paint(
395 &self,
396 origin: Point<Pixels>,
397 line_height: Pixels,
398 align: TextAlign,
399 align_width: Option<Pixels>,
400 decoration_runs: &[DecorationRun],
401 window: &mut Window,
402 cx: &mut App,
403 ) -> Result<()> {
404 paint_line(
405 origin,
406 self,
407 line_height,
408 align,
409 align_width,
410 decoration_runs,
411 &[],
412 window,
413 cx,
414 )
415 }
416
417 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 )?;
485
486 Ok(())
487 }
488
489 pub fn paint_background(
491 &self,
492 origin: Point<Pixels>,
493 line_height: Pixels,
494 align: TextAlign,
495 bounds: Option<Bounds<Pixels>>,
496 window: &mut Window,
497 cx: &mut App,
498 ) -> Result<()> {
499 let align_width = match bounds {
500 Some(bounds) => Some(bounds.size.width),
501 None => self.layout.wrap_width,
502 };
503
504 paint_line_background(
505 origin,
506 &self.layout.unwrapped_layout,
507 line_height,
508 align,
509 align_width,
510 &self.decoration_runs,
511 &self.wrap_boundaries,
512 window,
513 cx,
514 )?;
515
516 Ok(())
517 }
518}
519
520fn paint_line(
521 origin: Point<Pixels>,
522 layout: &LineLayout,
523 line_height: Pixels,
524 align: TextAlign,
525 align_width: Option<Pixels>,
526 decoration_runs: &[DecorationRun],
527 wrap_boundaries: &[WrapBoundary],
528 window: &mut Window,
529 cx: &mut App,
530) -> Result<()> {
531 let line_bounds = Bounds::new(
532 origin,
533 size(
534 layout.width,
535 line_height * (wrap_boundaries.len() as f32 + 1.),
536 ),
537 );
538 window.paint_layer(line_bounds, |window| {
539 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
540 let baseline_offset = point(px(0.), padding_top + layout.ascent);
541 let mut decoration_runs = decoration_runs.iter();
542 let mut wraps = wrap_boundaries.iter().peekable();
543 let mut run_end = 0;
544 let mut color = black();
545 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
546 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
547 let text_system = cx.text_system().clone();
548 let mut glyph_origin = point(
549 aligned_origin_x(
550 origin,
551 align_width.unwrap_or(layout.width),
552 px(0.0),
553 &align,
554 layout,
555 wraps.peek(),
556 ),
557 origin.y,
558 );
559 let mut prev_glyph_position = Point::default();
560 let mut max_glyph_size = size(px(0.), px(0.));
561 let mut first_glyph_x = origin.x;
562 for (run_ix, run) in layout.runs.iter().enumerate() {
563 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
564
565 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
566 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
567 if glyph_ix == 0 && run_ix == 0 {
568 first_glyph_x = glyph_origin.x;
569 }
570
571 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
572 wraps.next();
573 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
574 if glyph_origin.x == underline_origin.x {
575 underline_origin.x -= max_glyph_size.width.half();
576 };
577 window.paint_underline(
578 *underline_origin,
579 glyph_origin.x - underline_origin.x,
580 underline_style,
581 );
582 if glyph.index < run_end {
583 underline_origin.x = origin.x;
584 underline_origin.y += line_height;
585 } else {
586 current_underline = None;
587 }
588 }
589 if let Some((strikethrough_origin, strikethrough_style)) =
590 current_strikethrough.as_mut()
591 {
592 if glyph_origin.x == strikethrough_origin.x {
593 strikethrough_origin.x -= max_glyph_size.width.half();
594 };
595 window.paint_strikethrough(
596 *strikethrough_origin,
597 glyph_origin.x - strikethrough_origin.x,
598 strikethrough_style,
599 );
600 if glyph.index < run_end {
601 strikethrough_origin.x = origin.x;
602 strikethrough_origin.y += line_height;
603 } else {
604 current_strikethrough = None;
605 }
606 }
607
608 glyph_origin.x = aligned_origin_x(
609 origin,
610 align_width.unwrap_or(layout.width),
611 glyph.position.x,
612 &align,
613 layout,
614 wraps.peek(),
615 );
616 glyph_origin.y += line_height;
617 }
618 prev_glyph_position = glyph.position;
619
620 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
621 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
622 if glyph.index >= run_end {
623 let mut style_run = decoration_runs.next();
624
625 while let Some(run) = style_run {
627 if glyph.index < run_end + (run.len as usize) {
628 break;
629 }
630 run_end += run.len as usize;
631 style_run = decoration_runs.next();
632 }
633
634 if let Some(style_run) = style_run {
635 if let Some((_, underline_style)) = &mut current_underline
636 && style_run.underline.as_ref() != Some(underline_style)
637 {
638 finished_underline = current_underline.take();
639 }
640 if let Some(run_underline) = style_run.underline.as_ref() {
641 current_underline.get_or_insert((
642 point(
643 glyph_origin.x,
644 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
645 ),
646 UnderlineStyle {
647 color: Some(run_underline.color.unwrap_or(style_run.color)),
648 thickness: run_underline.thickness,
649 wavy: run_underline.wavy,
650 },
651 ));
652 }
653 if let Some((_, strikethrough_style)) = &mut current_strikethrough
654 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
655 {
656 finished_strikethrough = current_strikethrough.take();
657 }
658 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
659 current_strikethrough.get_or_insert((
660 point(
661 glyph_origin.x,
662 glyph_origin.y
663 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
664 ),
665 StrikethroughStyle {
666 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
667 thickness: run_strikethrough.thickness,
668 },
669 ));
670 }
671
672 run_end += style_run.len as usize;
673 color = style_run.color;
674 } else {
675 run_end = layout.len;
676 finished_underline = current_underline.take();
677 finished_strikethrough = current_strikethrough.take();
678 }
679 }
680
681 if let Some((mut underline_origin, underline_style)) = finished_underline {
682 if underline_origin.x == glyph_origin.x {
683 underline_origin.x -= max_glyph_size.width.half();
684 };
685 window.paint_underline(
686 underline_origin,
687 glyph_origin.x - underline_origin.x,
688 &underline_style,
689 );
690 }
691
692 if let Some((mut strikethrough_origin, strikethrough_style)) =
693 finished_strikethrough
694 {
695 if strikethrough_origin.x == glyph_origin.x {
696 strikethrough_origin.x -= max_glyph_size.width.half();
697 };
698 window.paint_strikethrough(
699 strikethrough_origin,
700 glyph_origin.x - strikethrough_origin.x,
701 &strikethrough_style,
702 );
703 }
704
705 let max_glyph_bounds = Bounds {
706 origin: glyph_origin,
707 size: max_glyph_size,
708 };
709
710 let content_mask = window.content_mask();
711 if max_glyph_bounds.intersects(&content_mask.bounds) {
712 let vertical_offset = point(px(0.0), glyph.position.y);
713 if glyph.is_emoji {
714 window.paint_emoji(
715 glyph_origin + baseline_offset + vertical_offset,
716 run.font_id,
717 glyph.id,
718 layout.font_size,
719 )?;
720 } else {
721 window.paint_glyph(
722 glyph_origin + baseline_offset + vertical_offset,
723 run.font_id,
724 glyph.id,
725 layout.font_size,
726 color,
727 )?;
728 }
729 }
730 }
731 }
732
733 let mut last_line_end_x = first_glyph_x + layout.width;
734 if let Some(boundary) = wrap_boundaries.last() {
735 let run = &layout.runs[boundary.run_ix];
736 let glyph = &run.glyphs[boundary.glyph_ix];
737 last_line_end_x -= glyph.position.x;
738 }
739
740 if let Some((mut underline_start, underline_style)) = current_underline.take() {
741 if last_line_end_x == underline_start.x {
742 underline_start.x -= max_glyph_size.width.half()
743 };
744 window.paint_underline(
745 underline_start,
746 last_line_end_x - underline_start.x,
747 &underline_style,
748 );
749 }
750
751 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
752 if last_line_end_x == strikethrough_start.x {
753 strikethrough_start.x -= max_glyph_size.width.half()
754 };
755 window.paint_strikethrough(
756 strikethrough_start,
757 last_line_end_x - strikethrough_start.x,
758 &strikethrough_style,
759 );
760 }
761
762 Ok(())
763 })
764}
765
766fn paint_line_background(
767 origin: Point<Pixels>,
768 layout: &LineLayout,
769 line_height: Pixels,
770 align: TextAlign,
771 align_width: Option<Pixels>,
772 decoration_runs: &[DecorationRun],
773 wrap_boundaries: &[WrapBoundary],
774 window: &mut Window,
775 cx: &mut App,
776) -> Result<()> {
777 let line_bounds = Bounds::new(
778 origin,
779 size(
780 layout.width,
781 line_height * (wrap_boundaries.len() as f32 + 1.),
782 ),
783 );
784 window.paint_layer(line_bounds, |window| {
785 let mut decoration_runs = decoration_runs.iter();
786 let mut wraps = wrap_boundaries.iter().peekable();
787 let mut run_end = 0;
788 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
789 let text_system = cx.text_system().clone();
790 let mut glyph_origin = point(
791 aligned_origin_x(
792 origin,
793 align_width.unwrap_or(layout.width),
794 px(0.0),
795 &align,
796 layout,
797 wraps.peek(),
798 ),
799 origin.y,
800 );
801 let mut prev_glyph_position = Point::default();
802 let mut max_glyph_size = size(px(0.), px(0.));
803 for (run_ix, run) in layout.runs.iter().enumerate() {
804 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
805
806 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
807 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
808
809 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
810 wraps.next();
811 if let Some((background_origin, background_color)) = current_background.as_mut()
812 {
813 if glyph_origin.x == background_origin.x {
814 background_origin.x -= max_glyph_size.width.half()
815 }
816 window.paint_quad(fill(
817 Bounds {
818 origin: *background_origin,
819 size: size(glyph_origin.x - background_origin.x, line_height),
820 },
821 *background_color,
822 ));
823 if glyph.index < run_end {
824 background_origin.x = origin.x;
825 background_origin.y += line_height;
826 } else {
827 current_background = None;
828 }
829 }
830
831 glyph_origin.x = aligned_origin_x(
832 origin,
833 align_width.unwrap_or(layout.width),
834 glyph.position.x,
835 &align,
836 layout,
837 wraps.peek(),
838 );
839 glyph_origin.y += line_height;
840 }
841 prev_glyph_position = glyph.position;
842
843 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
844 if glyph.index >= run_end {
845 let mut style_run = decoration_runs.next();
846
847 while let Some(run) = style_run {
849 if glyph.index < run_end + (run.len as usize) {
850 break;
851 }
852 run_end += run.len as usize;
853 style_run = decoration_runs.next();
854 }
855
856 if let Some(style_run) = style_run {
857 if let Some((_, background_color)) = &mut current_background
858 && style_run.background_color.as_ref() != Some(background_color)
859 {
860 finished_background = current_background.take();
861 }
862 if let Some(run_background) = style_run.background_color {
863 current_background.get_or_insert((
864 point(glyph_origin.x, glyph_origin.y),
865 run_background,
866 ));
867 }
868 run_end += style_run.len as usize;
869 } else {
870 run_end = layout.len;
871 finished_background = current_background.take();
872 }
873 }
874
875 if let Some((mut background_origin, background_color)) = finished_background {
876 let mut width = glyph_origin.x - background_origin.x;
877 if background_origin.x == glyph_origin.x {
878 background_origin.x -= max_glyph_size.width.half();
879 };
880 window.paint_quad(fill(
881 Bounds {
882 origin: background_origin,
883 size: size(width, line_height),
884 },
885 background_color,
886 ));
887 }
888 }
889 }
890
891 let mut last_line_end_x = origin.x + layout.width;
892 if let Some(boundary) = wrap_boundaries.last() {
893 let run = &layout.runs[boundary.run_ix];
894 let glyph = &run.glyphs[boundary.glyph_ix];
895 last_line_end_x -= glyph.position.x;
896 }
897
898 if let Some((mut background_origin, background_color)) = current_background.take() {
899 if last_line_end_x == background_origin.x {
900 background_origin.x -= max_glyph_size.width.half()
901 };
902 window.paint_quad(fill(
903 Bounds {
904 origin: background_origin,
905 size: size(last_line_end_x - background_origin.x, line_height),
906 },
907 background_color,
908 ));
909 }
910
911 Ok(())
912 })
913}
914
915fn aligned_origin_x(
916 origin: Point<Pixels>,
917 align_width: Pixels,
918 last_glyph_x: Pixels,
919 align: &TextAlign,
920 layout: &LineLayout,
921 wrap_boundary: Option<&&WrapBoundary>,
922) -> Pixels {
923 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
924 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
925 } else {
926 layout.width
927 };
928
929 let line_width = end_of_line - last_glyph_x;
930
931 match align {
932 TextAlign::Left => origin.x,
933 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
934 TextAlign::Right => origin.x + align_width - line_width,
935 }
936}
937
938#[cfg(test)]
939mod tests {
940 use super::*;
941 use crate::{FontId, GlyphId, ShapedGlyph, ShapedRun};
942
943 fn make_shaped_line(
946 text: &str,
947 glyphs: &[(usize, f32)],
948 width: f32,
949 decorations: &[DecorationRun],
950 ) -> ShapedLine {
951 let shaped_glyphs: Vec<ShapedGlyph> = glyphs
952 .iter()
953 .map(|&(index, x)| ShapedGlyph {
954 id: GlyphId(0),
955 position: point(px(x), px(0.0)),
956 index,
957 is_emoji: false,
958 })
959 .collect();
960
961 ShapedLine {
962 layout: Arc::new(LineLayout {
963 font_size: px(16.0),
964 width: px(width),
965 ascent: px(12.0),
966 descent: px(4.0),
967 runs: vec![ShapedRun {
968 font_id: FontId(0),
969 glyphs: shaped_glyphs,
970 }],
971 len: text.len(),
972 }),
973 text: SharedString::new(text),
974 decoration_runs: SmallVec::from(decorations.to_vec()),
975 }
976 }
977
978 #[test]
979 fn test_split_at_invariants() {
980 let line = make_shaped_line(
982 "abcdef",
983 &[
984 (0, 0.0),
985 (1, 10.0),
986 (2, 20.0),
987 (3, 30.0),
988 (4, 40.0),
989 (5, 50.0),
990 ],
991 60.0,
992 &[],
993 );
994
995 for i in 0..=6 {
996 let (left, right) = line.split_at(i);
997
998 assert_eq!(
999 left.width() + right.width(),
1000 line.width(),
1001 "widths must sum at split={i}"
1002 );
1003 assert_eq!(
1004 left.len() + right.len(),
1005 line.len(),
1006 "lengths must sum at split={i}"
1007 );
1008 assert_eq!(
1009 format!("{}{}", left.text.as_ref(), right.text.as_ref()),
1010 "abcdef",
1011 "text must concatenate at split={i}"
1012 );
1013 assert_eq!(left.font_size, line.font_size, "font_size at split={i}");
1014 assert_eq!(right.ascent, line.ascent, "ascent at split={i}");
1015 assert_eq!(right.descent, line.descent, "descent at split={i}");
1016 }
1017
1018 let (left, right) = line.split_at(0);
1020 assert_eq!(left.runs.len(), 0);
1021 assert_eq!(right.runs[0].glyphs.len(), 6);
1022
1023 let (left, right) = line.split_at(6);
1025 assert_eq!(left.runs[0].glyphs.len(), 6);
1026 assert_eq!(right.runs.len(), 0);
1027 }
1028
1029 #[test]
1030 fn test_split_at_glyph_rebasing() {
1031 let line = ShapedLine {
1036 layout: Arc::new(LineLayout {
1037 font_size: px(16.0),
1038 width: px(60.0),
1039 ascent: px(12.0),
1040 descent: px(4.0),
1041 runs: vec![
1042 ShapedRun {
1043 font_id: FontId(0),
1044 glyphs: vec![
1045 ShapedGlyph {
1046 id: GlyphId(0),
1047 position: point(px(0.0), px(0.0)),
1048 index: 0,
1049 is_emoji: false,
1050 },
1051 ShapedGlyph {
1052 id: GlyphId(0),
1053 position: point(px(10.0), px(0.0)),
1054 index: 1,
1055 is_emoji: false,
1056 },
1057 ShapedGlyph {
1058 id: GlyphId(0),
1059 position: point(px(20.0), px(0.0)),
1060 index: 2,
1061 is_emoji: false,
1062 },
1063 ],
1064 },
1065 ShapedRun {
1066 font_id: FontId(1),
1067 glyphs: vec![
1068 ShapedGlyph {
1069 id: GlyphId(0),
1070 position: point(px(30.0), px(0.0)),
1071 index: 3,
1072 is_emoji: false,
1073 },
1074 ShapedGlyph {
1075 id: GlyphId(0),
1076 position: point(px(40.0), px(0.0)),
1077 index: 4,
1078 is_emoji: false,
1079 },
1080 ShapedGlyph {
1081 id: GlyphId(0),
1082 position: point(px(50.0), px(0.0)),
1083 index: 5,
1084 is_emoji: false,
1085 },
1086 ],
1087 },
1088 ],
1089 len: 6,
1090 }),
1091 text: "abcdef".into(),
1092 decoration_runs: SmallVec::new(),
1093 };
1094
1095 let (first, remainder) = line.split_at(2);
1097 assert_eq!(first.text.as_ref(), "ab");
1098 assert_eq!(first.runs.len(), 1);
1099 assert_eq!(first.runs[0].font_id, FontId(0));
1100
1101 assert_eq!(remainder.text.as_ref(), "cdef");
1103 assert_eq!(remainder.runs.len(), 2);
1104 assert_eq!(remainder.runs[0].font_id, FontId(0));
1105 assert_eq!(remainder.runs[0].glyphs.len(), 1);
1106 assert_eq!(remainder.runs[0].glyphs[0].index, 0);
1107 assert_eq!(remainder.runs[0].glyphs[0].position.x, px(0.0));
1108 assert_eq!(remainder.runs[1].font_id, FontId(1));
1109 assert_eq!(remainder.runs[1].glyphs[0].index, 1);
1110 assert_eq!(remainder.runs[1].glyphs[0].position.x, px(10.0));
1111
1112 let (second, final_part) = remainder.split_at(2);
1114 assert_eq!(second.text.as_ref(), "cd");
1115 assert_eq!(final_part.text.as_ref(), "ef");
1116 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1117 assert_eq!(final_part.runs[0].glyphs[0].position.x, px(0.0));
1118
1119 assert_eq!(
1121 first.width() + second.width() + final_part.width(),
1122 line.width()
1123 );
1124 }
1125
1126 #[test]
1127 fn test_split_at_decorations() {
1128 let red = Hsla {
1131 h: 0.0,
1132 s: 1.0,
1133 l: 0.5,
1134 a: 1.0,
1135 };
1136 let green = Hsla {
1137 h: 0.3,
1138 s: 1.0,
1139 l: 0.5,
1140 a: 1.0,
1141 };
1142 let blue = Hsla {
1143 h: 0.6,
1144 s: 1.0,
1145 l: 0.5,
1146 a: 1.0,
1147 };
1148
1149 let line = make_shaped_line(
1150 "abcdef",
1151 &[
1152 (0, 0.0),
1153 (1, 10.0),
1154 (2, 20.0),
1155 (3, 30.0),
1156 (4, 40.0),
1157 (5, 50.0),
1158 ],
1159 60.0,
1160 &[
1161 DecorationRun {
1162 len: 2,
1163 color: red,
1164 background_color: None,
1165 underline: None,
1166 strikethrough: None,
1167 },
1168 DecorationRun {
1169 len: 3,
1170 color: green,
1171 background_color: None,
1172 underline: None,
1173 strikethrough: None,
1174 },
1175 DecorationRun {
1176 len: 1,
1177 color: blue,
1178 background_color: None,
1179 underline: None,
1180 strikethrough: None,
1181 },
1182 ],
1183 );
1184
1185 let (left, right) = line.split_at(3);
1186
1187 assert_eq!(left.decoration_runs.len(), 2);
1189 assert_eq!(left.decoration_runs[0].len, 2);
1190 assert_eq!(left.decoration_runs[0].color, red);
1191 assert_eq!(left.decoration_runs[1].len, 1);
1192 assert_eq!(left.decoration_runs[1].color, green);
1193
1194 assert_eq!(right.decoration_runs.len(), 2);
1196 assert_eq!(right.decoration_runs[0].len, 2);
1197 assert_eq!(right.decoration_runs[0].color, green);
1198 assert_eq!(right.decoration_runs[1].len, 1);
1199 assert_eq!(right.decoration_runs[1].color, blue);
1200 }
1201
1202 #[test]
1203 fn test_cursor_preserves_shaping_metadata_across_runs() {
1204 let line = ShapedLine {
1205 layout: Arc::new(LineLayout {
1206 font_size: px(16.0),
1207 width: px(50.0),
1208 ascent: px(12.0),
1209 descent: px(4.0),
1210 runs: vec![
1211 ShapedRun {
1212 font_id: FontId(3),
1213 glyphs: vec![
1214 ShapedGlyph {
1215 id: GlyphId(11),
1216 position: point(px(0.0), px(1.0)),
1217 index: 0,
1218 is_emoji: true,
1219 },
1220 ShapedGlyph {
1221 id: GlyphId(12),
1222 position: point(px(17.0), px(1.0)),
1223 index: 1,
1224 is_emoji: false,
1225 },
1226 ShapedGlyph {
1227 id: GlyphId(13),
1228 position: point(px(19.0), px(-1.0)),
1229 index: 1,
1230 is_emoji: false,
1231 },
1232 ],
1233 },
1234 ShapedRun {
1235 font_id: FontId(8),
1236 glyphs: vec![
1237 ShapedGlyph {
1238 id: GlyphId(21),
1239 position: point(px(25.0), px(1.0)),
1240 index: 5,
1241 is_emoji: true,
1242 },
1243 ShapedGlyph {
1244 id: GlyphId(22),
1245 position: point(px(41.0), px(1.0)),
1246 index: 7,
1247 is_emoji: false,
1248 },
1249 ],
1250 },
1251 ],
1252 len: 10,
1253 }),
1254 text: "a😀bcdef".into(),
1255 decoration_runs: SmallVec::new(),
1256 };
1257 let mut cursor = line.cursor();
1258 let first = cursor.take_until(5);
1259 assert_eq!(first.text.as_ref(), "a😀");
1260 assert_eq!(first.runs[0].font_id, FontId(3));
1261 assert_eq!(first.runs[0].glyphs[0].id, GlyphId(11));
1262 assert!(first.runs[0].glyphs[0].is_emoji);
1263 assert_eq!(first.runs[0].glyphs[1].index, 1);
1264 assert_eq!(first.runs[0].glyphs[1].position, point(px(17.0), px(1.0)));
1265 assert_eq!(first.runs[0].glyphs.len(), 3);
1266 assert_eq!(first.runs[0].glyphs[2].index, 1);
1267 assert_eq!(first.runs[0].glyphs[2].position, point(px(19.0), px(-1.0)));
1268 assert_eq!(cursor.x_offset(), px(25.0));
1269
1270 let second = cursor.take_until(7);
1271 assert_eq!(second.text.as_ref(), "bc");
1272 assert_eq!(second.runs[0].font_id, FontId(8));
1273 assert_eq!(second.runs[0].glyphs[0].id, GlyphId(21));
1274 assert_eq!(second.runs[0].glyphs[0].index, 0);
1275 assert_eq!(second.runs[0].glyphs[0].position, point(px(0.0), px(1.0)));
1276 assert_eq!(cursor.x_offset(), px(41.0));
1277
1278 let final_part = cursor.take_until(10);
1279 assert_eq!(final_part.text.as_ref(), "def");
1280 assert_eq!(final_part.runs[0].font_id, FontId(8));
1281 assert_eq!(final_part.runs[0].glyphs[0].id, GlyphId(22));
1282 assert_eq!(final_part.runs[0].glyphs[0].index, 0);
1283 assert_eq!(
1284 final_part.runs[0].glyphs[0].position,
1285 point(px(0.0), px(1.0))
1286 );
1287 }
1288
1289 #[test]
1290 fn test_cursor_preserves_existing_visual_order_splitting() {
1291 let line = make_shaped_line("abc", &[(0, 0.0), (2, 10.0), (1, 20.0)], 30.0, &[]);
1292 let mut cursor = line.cursor();
1293 let mut remainder = line.clone();
1294 let mut previous_boundary = 0;
1295 for boundary in [0, 1, 2, 3] {
1296 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1297 let actual = cursor.take_until(boundary);
1298 assert_eq!(actual.text, expected.text);
1299 assert_eq!(actual.width(), expected.width());
1300 assert_eq!(actual.runs.len(), expected.runs.len());
1301 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1302 assert_eq!(actual.font_id, expected.font_id);
1303 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1304 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1305 assert_eq!(actual.id, expected.id);
1306 assert_eq!(actual.index, expected.index);
1307 assert_eq!(actual.position, expected.position);
1308 }
1309 }
1310 assert_eq!(cursor.x_offset(), line.x_for_index(boundary));
1311 remainder = rest;
1312 previous_boundary = boundary;
1313 }
1314 }
1315
1316 #[test]
1317 fn test_cursor_partitions_one_decoration_across_three_chunks() {
1318 let line = make_shaped_line(
1319 "abcdef",
1320 &[
1321 (0, 0.0),
1322 (1, 10.0),
1323 (2, 20.0),
1324 (3, 30.0),
1325 (4, 40.0),
1326 (5, 50.0),
1327 ],
1328 60.0,
1329 &[DecorationRun {
1330 len: 6,
1331 color: Hsla {
1332 h: 0.2,
1333 s: 0.4,
1334 l: 0.6,
1335 a: 1.0,
1336 },
1337 background_color: None,
1338 underline: None,
1339 strikethrough: None,
1340 }],
1341 );
1342 let mut cursor = line.cursor();
1343 assert_eq!(cursor.take_until(2).decoration_runs[0].len, 2);
1344 assert_eq!(cursor.take_until(4).decoration_runs[0].len, 2);
1345 assert_eq!(cursor.take_until(6).decoration_runs[0].len, 2);
1346 }
1347
1348 #[test]
1349 fn test_cursor_matches_successive_splits_at_ordered_boundaries() {
1350 let decorations: Vec<_> = [2, 0, 3, 1]
1351 .into_iter()
1352 .map(|len| DecorationRun {
1353 len,
1354 color: Hsla {
1355 h: len as f32 / 10.0,
1356 s: 0.5,
1357 l: 0.5,
1358 a: 1.0,
1359 },
1360 background_color: Some(black()),
1361 underline: None,
1362 strikethrough: None,
1363 })
1364 .collect();
1365 let line = make_shaped_line(
1366 "abcdef",
1367 &[(0, 5.0), (0, 5.0), (2, 15.0), (4, 25.0), (5, 35.0)],
1368 45.0,
1369 &decorations,
1370 );
1371 for first in 0..=line.len() {
1372 for second in first..=line.len() {
1373 let mut cursor = line.cursor();
1374 let mut remainder = line.clone();
1375 let mut previous_boundary = 0;
1376 let mut total_width = px(0.0);
1377 let mut text = String::new();
1378 for boundary in [first, second, line.len(), line.len()] {
1379 let (expected, rest) = remainder.split_at(boundary - previous_boundary);
1380 let actual = cursor.take_until(boundary);
1381 assert_eq!(actual.text, expected.text);
1382 assert_eq!(actual.len(), expected.len());
1383 assert_eq!(actual.width(), expected.width());
1384 assert_eq!(actual.runs.len(), expected.runs.len());
1385 for (actual, expected) in actual.runs.iter().zip(&expected.runs) {
1386 assert_eq!(actual.font_id, expected.font_id);
1387 assert_eq!(actual.glyphs.len(), expected.glyphs.len());
1388 for (actual, expected) in actual.glyphs.iter().zip(&expected.glyphs) {
1389 assert_eq!(actual.id, expected.id);
1390 assert_eq!(actual.index, expected.index);
1391 assert_eq!(actual.position, expected.position);
1392 }
1393 }
1394 assert_eq!(actual.decoration_runs.len(), expected.decoration_runs.len());
1395 for (actual, expected) in
1396 actual.decoration_runs.iter().zip(&expected.decoration_runs)
1397 {
1398 assert_eq!(actual.len, expected.len);
1399 assert_eq!(actual.color, expected.color);
1400 assert_eq!(actual.background_color, expected.background_color);
1401 }
1402 total_width += actual.width();
1403 text.push_str(&actual.text);
1404 remainder = rest;
1405 previous_boundary = boundary;
1406 }
1407 assert_eq!(total_width, line.width());
1408 assert_eq!(text, line.text.as_ref());
1409 }
1410 }
1411 }
1412
1413 #[test]
1414 fn test_cursor_empty_chunks_and_repeated_boundaries() {
1415 let line = make_shaped_line("ab", &[(0, 5.0), (1, 15.0)], 20.0, &[]);
1416 let mut cursor = line.cursor();
1417 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1418 assert_eq!(cursor.take_until(0).text.as_ref(), "");
1419 assert_eq!(cursor.take_until(1).text.as_ref(), "a");
1420 assert_eq!(cursor.take_until(2).text.as_ref(), "b");
1421 assert_eq!(cursor.take_until(2).text.as_ref(), "");
1422 let empty = make_shaped_line("", &[], 0.0, &[]);
1423 let piece = empty.cursor().take_until(0);
1424 assert!(piece.text.is_empty());
1425 assert!(piece.runs.is_empty());
1426 assert_eq!(piece.width(), px(0.0));
1427 }
1428
1429 #[test]
1430 fn test_cursor_rejects_invalid_boundaries() {
1431 let line = make_shaped_line("é", &[(0, 0.0)], 10.0, &[]);
1432 let mut cursor = line.cursor();
1433 assert!(
1434 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1435 cursor.take_until(1);
1436 }))
1437 .is_err()
1438 );
1439 let mut cursor = line.cursor();
1440 cursor.take_until(2);
1441 assert!(
1442 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1443 cursor.take_until(0);
1444 }))
1445 .is_err()
1446 );
1447 let mut cursor = line.cursor();
1448 assert!(
1449 std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1450 cursor.take_until(3);
1451 }))
1452 .is_err()
1453 );
1454 }
1455}