1use crate::{
2 App, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result, SharedString, StrikethroughStyle,
3 TextAlign, TransformationMatrix, UnderlineStyle, Window, WrapBoundary, WrappedLineLayout,
4 black, fill, 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 #[allow(clippy::len_without_is_empty)]
43 pub fn len(&self) -> usize {
44 self.layout.len
45 }
46
47 pub fn with_len(mut self, len: usize) -> Self {
50 let layout = self.layout.as_ref();
51 self.layout = Arc::new(LineLayout {
52 font_size: layout.font_size,
53 width: layout.width,
54 ascent: layout.ascent,
55 descent: layout.descent,
56 runs: layout.runs.clone(),
57 len,
58 });
59 self
60 }
61
62 pub fn paint(
64 &self,
65 origin: Point<Pixels>,
66 line_height: Pixels,
67 align: TextAlign,
68 align_width: Option<Pixels>,
69 window: &mut Window,
70 cx: &mut App,
71 ) -> Result<()> {
72 self.paint_with_transform(
73 origin,
74 line_height,
75 align,
76 align_width,
77 window,
78 cx,
79 TransformationMatrix::unit(),
80 )?;
81
82 Ok(())
83 }
84
85 pub fn paint_with_transform(
87 &self,
88 origin: Point<Pixels>,
89 line_height: Pixels,
90 align: TextAlign,
91 align_width: Option<Pixels>,
92 window: &mut Window,
93 cx: &mut App,
94 transform: TransformationMatrix,
95 ) -> Result<()> {
96 paint_line(
97 origin,
98 &self.layout,
99 line_height,
100 align,
101 align_width,
102 &self.decoration_runs,
103 &[],
104 window,
105 cx,
106 transform,
107 )
108 }
109
110 pub fn paint_background(
112 &self,
113 origin: Point<Pixels>,
114 line_height: Pixels,
115 align: TextAlign,
116 align_width: Option<Pixels>,
117 window: &mut Window,
118 cx: &mut App,
119 ) -> Result<()> {
120 self.paint_background_with_transform(
121 origin,
122 line_height,
123 align,
124 align_width,
125 window,
126 cx,
127 TransformationMatrix::unit(),
128 )?;
129
130 Ok(())
131 }
132
133 pub fn paint_background_with_transform(
135 &self,
136 origin: Point<Pixels>,
137 line_height: Pixels,
138 align: TextAlign,
139 align_width: Option<Pixels>,
140 window: &mut Window,
141 cx: &mut App,
142 transform: TransformationMatrix,
143 ) -> Result<()> {
144 paint_line_background(
145 origin,
146 &self.layout,
147 line_height,
148 align,
149 align_width,
150 &self.decoration_runs,
151 &[],
152 window,
153 cx,
154 transform,
155 )
156 }
157}
158
159#[derive(Clone, Default, Debug, Deref, DerefMut)]
161pub struct WrappedLine {
162 #[deref]
163 #[deref_mut]
164 pub(crate) layout: Arc<WrappedLineLayout>,
165 pub text: SharedString,
167 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
168}
169
170impl WrappedLine {
171 #[allow(clippy::len_without_is_empty)]
173 pub fn len(&self) -> usize {
174 self.layout.len()
175 }
176
177 pub fn paint(
179 &self,
180 origin: Point<Pixels>,
181 line_height: Pixels,
182 align: TextAlign,
183 bounds: Option<Bounds<Pixels>>,
184 window: &mut Window,
185 cx: &mut App,
186 ) -> Result<()> {
187 self.paint_with_transform(
188 origin,
189 line_height,
190 align,
191 bounds,
192 window,
193 cx,
194 TransformationMatrix::unit(),
195 )
196 }
197
198 pub fn paint_with_transform(
200 &self,
201 origin: Point<Pixels>,
202 line_height: Pixels,
203 align: TextAlign,
204 bounds: Option<Bounds<Pixels>>,
205 window: &mut Window,
206 cx: &mut App,
207 transform: TransformationMatrix,
208 ) -> Result<()> {
209 let align_width = match bounds {
210 Some(bounds) => Some(bounds.size.width),
211 None => self.layout.wrap_width,
212 };
213
214 paint_line(
215 origin,
216 &self.layout.unwrapped_layout,
217 line_height,
218 align,
219 align_width,
220 &self.decoration_runs,
221 &self.wrap_boundaries,
222 window,
223 cx,
224 transform,
225 )?;
226
227 Ok(())
228 }
229
230 pub fn paint_background(
232 &self,
233 origin: Point<Pixels>,
234 line_height: Pixels,
235 align: TextAlign,
236 bounds: Option<Bounds<Pixels>>,
237 window: &mut Window,
238 cx: &mut App,
239 ) -> Result<()> {
240 self.paint_background_with_transform(
241 origin,
242 line_height,
243 align,
244 bounds,
245 window,
246 cx,
247 TransformationMatrix::unit(),
248 )
249 }
250
251 pub fn paint_background_with_transform(
253 &self,
254 origin: Point<Pixels>,
255 line_height: Pixels,
256 align: TextAlign,
257 bounds: Option<Bounds<Pixels>>,
258 window: &mut Window,
259 cx: &mut App,
260 transform: TransformationMatrix,
261 ) -> Result<()> {
262 let align_width = match bounds {
263 Some(bounds) => Some(bounds.size.width),
264 None => self.layout.wrap_width,
265 };
266
267 paint_line_background(
268 origin,
269 &self.layout.unwrapped_layout,
270 line_height,
271 align,
272 align_width,
273 &self.decoration_runs,
274 &self.wrap_boundaries,
275 window,
276 cx,
277 transform,
278 )?;
279
280 Ok(())
281 }
282}
283
284fn paint_line(
285 origin: Point<Pixels>,
286 layout: &LineLayout,
287 line_height: Pixels,
288 align: TextAlign,
289 align_width: Option<Pixels>,
290 decoration_runs: &[DecorationRun],
291 wrap_boundaries: &[WrapBoundary],
292 window: &mut Window,
293 cx: &mut App,
294 transform: TransformationMatrix,
295) -> Result<()> {
296 let local_bounds = Bounds::new(
297 origin,
298 size(
299 layout.width,
300 line_height * (wrap_boundaries.len() as f32 + 1.),
301 ),
302 );
303 let layer_bounds = if transform.is_unit() {
307 local_bounds
308 } else {
309 let corners = [
311 local_bounds.origin,
312 point(
313 local_bounds.origin.x + local_bounds.size.width,
314 local_bounds.origin.y,
315 ),
316 point(
317 local_bounds.origin.x,
318 local_bounds.origin.y + local_bounds.size.height,
319 ),
320 point(
321 local_bounds.origin.x + local_bounds.size.width,
322 local_bounds.origin.y + local_bounds.size.height,
323 ),
324 ];
325
326 let mut min_x = f32::INFINITY;
327 let mut max_x = f32::NEG_INFINITY;
328 let mut min_y = f32::INFINITY;
329 let mut max_y = f32::NEG_INFINITY;
330
331 for corner in corners {
332 let p = transform.apply(corner);
333 let x = f32::from(p.x);
334 let y = f32::from(p.y);
335 if x < min_x {
336 min_x = x;
337 }
338 if x > max_x {
339 max_x = x;
340 }
341 if y < min_y {
342 min_y = y;
343 }
344 if y > max_y {
345 max_y = y;
346 }
347 }
348
349 Bounds {
350 origin: point(px(min_x), px(min_y)),
351 size: size(px((max_x - min_x).max(0.0)), px((max_y - min_y).max(0.0))),
352 }
353 };
354
355 window.paint_layer(layer_bounds, |window| {
356 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
357 let baseline_offset = point(px(0.), padding_top + layout.ascent);
358 let mut decoration_runs = decoration_runs.iter();
359 let mut wraps = wrap_boundaries.iter().peekable();
360 let mut run_end = 0;
361 let mut color = black();
362 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
363 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
364 let text_system = cx.text_system().clone();
365 let mut glyph_origin = point(
366 aligned_origin_x(
367 origin,
368 align_width.unwrap_or(layout.width),
369 px(0.0),
370 &align,
371 layout,
372 wraps.peek(),
373 ),
374 origin.y,
375 );
376 let mut prev_glyph_position = Point::default();
377 let mut max_glyph_size = size(px(0.), px(0.));
378 let mut first_glyph_x = origin.x;
379 for (run_ix, run) in layout.runs.iter().enumerate() {
380 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
381
382 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
383 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
384 if glyph_ix == 0 && run_ix == 0 {
385 first_glyph_x = glyph_origin.x;
386 }
387
388 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
389 wraps.next();
390 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
391 if glyph_origin.x == underline_origin.x {
392 underline_origin.x -= max_glyph_size.width.half();
393 };
394 window.paint_underline_with_transform(
395 *underline_origin,
396 glyph_origin.x - underline_origin.x,
397 underline_style,
398 transform,
399 );
400 underline_origin.x = origin.x;
401 underline_origin.y += line_height;
402 }
403 if let Some((strikethrough_origin, strikethrough_style)) =
404 current_strikethrough.as_mut()
405 {
406 if glyph_origin.x == strikethrough_origin.x {
407 strikethrough_origin.x -= max_glyph_size.width.half();
408 };
409 window.paint_strikethrough_with_transform(
410 *strikethrough_origin,
411 glyph_origin.x - strikethrough_origin.x,
412 strikethrough_style,
413 transform,
414 );
415 strikethrough_origin.x = origin.x;
416 strikethrough_origin.y += line_height;
417 }
418
419 glyph_origin.x = aligned_origin_x(
420 origin,
421 align_width.unwrap_or(layout.width),
422 glyph.position.x,
423 &align,
424 layout,
425 wraps.peek(),
426 );
427 glyph_origin.y += line_height;
428 }
429 prev_glyph_position = glyph.position;
430
431 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
432 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
433 if glyph.index >= run_end {
434 let mut style_run = decoration_runs.next();
435
436 while let Some(run) = style_run {
438 if glyph.index < run_end + (run.len as usize) {
439 break;
440 }
441 run_end += run.len as usize;
442 style_run = decoration_runs.next();
443 }
444
445 if let Some(style_run) = style_run {
446 if let Some((_, underline_style)) = &mut current_underline
447 && style_run.underline.as_ref() != Some(underline_style)
448 {
449 finished_underline = current_underline.take();
450 }
451 if let Some(run_underline) = style_run.underline.as_ref() {
452 current_underline.get_or_insert((
453 point(
454 glyph_origin.x,
455 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
456 ),
457 UnderlineStyle {
458 color: Some(run_underline.color.unwrap_or(style_run.color)),
459 thickness: run_underline.thickness,
460 wavy: run_underline.wavy,
461 },
462 ));
463 }
464 if let Some((_, strikethrough_style)) = &mut current_strikethrough
465 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
466 {
467 finished_strikethrough = current_strikethrough.take();
468 }
469 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
470 current_strikethrough.get_or_insert((
471 point(
472 glyph_origin.x,
473 glyph_origin.y
474 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
475 ),
476 StrikethroughStyle {
477 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
478 thickness: run_strikethrough.thickness,
479 },
480 ));
481 }
482
483 run_end += style_run.len as usize;
484 color = style_run.color;
485 } else {
486 run_end = layout.len;
487 finished_underline = current_underline.take();
488 finished_strikethrough = current_strikethrough.take();
489 }
490 }
491
492 if let Some((mut underline_origin, underline_style)) = finished_underline {
493 if underline_origin.x == glyph_origin.x {
494 underline_origin.x -= max_glyph_size.width.half();
495 };
496 window.paint_underline_with_transform(
497 underline_origin,
498 glyph_origin.x - underline_origin.x,
499 &underline_style,
500 transform,
501 );
502 }
503
504 if let Some((mut strikethrough_origin, strikethrough_style)) =
505 finished_strikethrough
506 {
507 if strikethrough_origin.x == glyph_origin.x {
508 strikethrough_origin.x -= max_glyph_size.width.half();
509 };
510 window.paint_strikethrough_with_transform(
511 strikethrough_origin,
512 glyph_origin.x - strikethrough_origin.x,
513 &strikethrough_style,
514 transform,
515 );
516 }
517
518 let max_glyph_bounds = Bounds {
519 origin: glyph_origin,
520 size: max_glyph_size,
521 };
522
523 let content_mask = window.content_mask();
524 let glyph_intersects_mask = if transform.is_unit() {
525 max_glyph_bounds.intersects(&content_mask.bounds)
526 } else {
527 let corners = [
530 max_glyph_bounds.origin,
531 point(
532 max_glyph_bounds.origin.x + max_glyph_bounds.size.width,
533 max_glyph_bounds.origin.y,
534 ),
535 point(
536 max_glyph_bounds.origin.x,
537 max_glyph_bounds.origin.y + max_glyph_bounds.size.height,
538 ),
539 point(
540 max_glyph_bounds.origin.x + max_glyph_bounds.size.width,
541 max_glyph_bounds.origin.y + max_glyph_bounds.size.height,
542 ),
543 ];
544
545 let mut min_x = f32::INFINITY;
546 let mut max_x = f32::NEG_INFINITY;
547 let mut min_y = f32::INFINITY;
548 let mut max_y = f32::NEG_INFINITY;
549
550 for corner in corners {
551 let p = transform.apply(corner);
552 let x = f32::from(p.x);
553 let y = f32::from(p.y);
554 if x < min_x {
555 min_x = x;
556 }
557 if x > max_x {
558 max_x = x;
559 }
560 if y < min_y {
561 min_y = y;
562 }
563 if y > max_y {
564 max_y = y;
565 }
566 }
567
568 let world_bounds = Bounds {
569 origin: point(px(min_x), px(min_y)),
570 size: size(px((max_x - min_x).max(0.0)), px((max_y - min_y).max(0.0))),
571 };
572
573 world_bounds.intersects(&content_mask.bounds)
574 };
575
576 if glyph_intersects_mask {
577 let vertical_offset = point(px(0.0), glyph.position.y);
578 if glyph.is_emoji {
579 window.paint_emoji_with_transform(
580 glyph_origin + baseline_offset + vertical_offset,
581 run.font_id,
582 glyph.id,
583 layout.font_size,
584 transform,
585 )?;
586 } else {
587 window.paint_glyph_with_transform(
588 glyph_origin + baseline_offset + vertical_offset,
589 run.font_id,
590 glyph.id,
591 layout.font_size,
592 color,
593 transform,
594 )?;
595 }
596 }
597 }
598 }
599
600 let mut last_line_end_x = first_glyph_x + layout.width;
601 if let Some(boundary) = wrap_boundaries.last() {
602 let run = &layout.runs[boundary.run_ix];
603 let glyph = &run.glyphs[boundary.glyph_ix];
604 last_line_end_x -= glyph.position.x;
605 }
606
607 if let Some((mut underline_start, underline_style)) = current_underline.take() {
608 if last_line_end_x == underline_start.x {
609 underline_start.x -= max_glyph_size.width.half()
610 };
611 window.paint_underline_with_transform(
612 underline_start,
613 last_line_end_x - underline_start.x,
614 &underline_style,
615 transform,
616 );
617 }
618
619 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
620 if last_line_end_x == strikethrough_start.x {
621 strikethrough_start.x -= max_glyph_size.width.half()
622 };
623 window.paint_strikethrough_with_transform(
624 strikethrough_start,
625 last_line_end_x - strikethrough_start.x,
626 &strikethrough_style,
627 transform,
628 );
629 }
630
631 Ok(())
632 })
633}
634
635fn paint_line_background(
636 origin: Point<Pixels>,
637 layout: &LineLayout,
638 line_height: Pixels,
639 align: TextAlign,
640 align_width: Option<Pixels>,
641 decoration_runs: &[DecorationRun],
642 wrap_boundaries: &[WrapBoundary],
643 window: &mut Window,
644 cx: &mut App,
645 transform: TransformationMatrix,
646) -> Result<()> {
647 let local_bounds = Bounds::new(
648 origin,
649 size(
650 layout.width,
651 line_height * (wrap_boundaries.len() as f32 + 1.),
652 ),
653 );
654 let layer_bounds = if transform.is_unit() {
655 local_bounds
656 } else {
657 let corners = [
658 local_bounds.origin,
659 point(
660 local_bounds.origin.x + local_bounds.size.width,
661 local_bounds.origin.y,
662 ),
663 point(
664 local_bounds.origin.x,
665 local_bounds.origin.y + local_bounds.size.height,
666 ),
667 point(
668 local_bounds.origin.x + local_bounds.size.width,
669 local_bounds.origin.y + local_bounds.size.height,
670 ),
671 ];
672
673 let mut min_x = f32::INFINITY;
674 let mut max_x = f32::NEG_INFINITY;
675 let mut min_y = f32::INFINITY;
676 let mut max_y = f32::NEG_INFINITY;
677
678 for corner in corners {
679 let p = transform.apply(corner);
680 let x = f32::from(p.x);
681 let y = f32::from(p.y);
682 if x < min_x {
683 min_x = x;
684 }
685 if x > max_x {
686 max_x = x;
687 }
688 if y < min_y {
689 min_y = y;
690 }
691 if y > max_y {
692 max_y = y;
693 }
694 }
695
696 Bounds {
697 origin: point(px(min_x), px(min_y)),
698 size: size(px((max_x - min_x).max(0.0)), px((max_y - min_y).max(0.0))),
699 }
700 };
701
702 window.paint_layer(layer_bounds, |window| {
703 let mut decoration_runs = decoration_runs.iter();
704 let mut wraps = wrap_boundaries.iter().peekable();
705 let mut run_end = 0;
706 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
707 let text_system = cx.text_system().clone();
708 let mut glyph_origin = point(
709 aligned_origin_x(
710 origin,
711 align_width.unwrap_or(layout.width),
712 px(0.0),
713 &align,
714 layout,
715 wraps.peek(),
716 ),
717 origin.y,
718 );
719 let mut prev_glyph_position = Point::default();
720 let mut max_glyph_size = size(px(0.), px(0.));
721 for (run_ix, run) in layout.runs.iter().enumerate() {
722 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
723
724 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
725 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
726
727 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
728 wraps.next();
729 if let Some((background_origin, background_color)) = current_background.as_mut()
730 {
731 if glyph_origin.x == background_origin.x {
732 background_origin.x -= max_glyph_size.width.half()
733 }
734 window.paint_quad_with_transform(
735 fill(
736 Bounds {
737 origin: *background_origin,
738 size: size(glyph_origin.x - background_origin.x, line_height),
739 },
740 *background_color,
741 ),
742 transform,
743 );
744 background_origin.x = origin.x;
745 background_origin.y += line_height;
746 }
747
748 glyph_origin.x = aligned_origin_x(
749 origin,
750 align_width.unwrap_or(layout.width),
751 glyph.position.x,
752 &align,
753 layout,
754 wraps.peek(),
755 );
756 glyph_origin.y += line_height;
757 }
758 prev_glyph_position = glyph.position;
759
760 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
761 if glyph.index >= run_end {
762 let mut style_run = decoration_runs.next();
763
764 while let Some(run) = style_run {
766 if glyph.index < run_end + (run.len as usize) {
767 break;
768 }
769 run_end += run.len as usize;
770 style_run = decoration_runs.next();
771 }
772
773 if let Some(style_run) = style_run {
774 if let Some((_, background_color)) = &mut current_background
775 && style_run.background_color.as_ref() != Some(background_color)
776 {
777 finished_background = current_background.take();
778 }
779 if let Some(run_background) = style_run.background_color {
780 current_background.get_or_insert((
781 point(glyph_origin.x, glyph_origin.y),
782 run_background,
783 ));
784 }
785 run_end += style_run.len as usize;
786 } else {
787 run_end = layout.len;
788 finished_background = current_background.take();
789 }
790 }
791
792 if let Some((mut background_origin, background_color)) = finished_background {
793 let mut width = glyph_origin.x - background_origin.x;
794 if background_origin.x == glyph_origin.x {
795 background_origin.x -= max_glyph_size.width.half();
796 };
797 window.paint_quad_with_transform(
798 fill(
799 Bounds {
800 origin: background_origin,
801 size: size(width, line_height),
802 },
803 background_color,
804 ),
805 transform,
806 );
807 }
808 }
809 }
810
811 let mut last_line_end_x = origin.x + layout.width;
812 if let Some(boundary) = wrap_boundaries.last() {
813 let run = &layout.runs[boundary.run_ix];
814 let glyph = &run.glyphs[boundary.glyph_ix];
815 last_line_end_x -= glyph.position.x;
816 }
817
818 if let Some((mut background_origin, background_color)) = current_background.take() {
819 if last_line_end_x == background_origin.x {
820 background_origin.x -= max_glyph_size.width.half()
821 };
822 window.paint_quad_with_transform(
823 fill(
824 Bounds {
825 origin: background_origin,
826 size: size(last_line_end_x - background_origin.x, line_height),
827 },
828 background_color,
829 ),
830 transform,
831 );
832 }
833
834 Ok(())
835 })
836}
837
838fn aligned_origin_x(
839 origin: Point<Pixels>,
840 align_width: Pixels,
841 last_glyph_x: Pixels,
842 align: &TextAlign,
843 layout: &LineLayout,
844 wrap_boundary: Option<&&WrapBoundary>,
845) -> Pixels {
846 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
847 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
848 } else {
849 layout.width
850 };
851
852 let line_width = end_of_line - last_glyph_x;
853
854 match align {
855 TextAlign::Left => origin.x,
856 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
857 TextAlign::Right => origin.x + align_width - line_width,
858 }
859}