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 #[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 window: &mut Window,
68 cx: &mut App,
69 ) -> Result<()> {
70 paint_line(
71 origin,
72 &self.layout,
73 line_height,
74 TextAlign::default(),
75 None,
76 &self.decoration_runs,
77 &[],
78 window,
79 cx,
80 )?;
81
82 Ok(())
83 }
84
85 pub fn paint_background(
87 &self,
88 origin: Point<Pixels>,
89 line_height: Pixels,
90 window: &mut Window,
91 cx: &mut App,
92 ) -> Result<()> {
93 paint_line_background(
94 origin,
95 &self.layout,
96 line_height,
97 TextAlign::default(),
98 None,
99 &self.decoration_runs,
100 &[],
101 window,
102 cx,
103 )?;
104
105 Ok(())
106 }
107
108 pub fn paint_shadow(
110 &self,
111 origin: Point<Pixels>,
112 line_height: Pixels,
113 shadow: &TextShadow,
114 window: &mut Window,
115 cx: &mut App,
116 ) -> Result<()> {
117 let shadow_runs: SmallVec<[DecorationRun; 32]> = self
118 .decoration_runs
119 .iter()
120 .map(|run| DecorationRun {
121 len: run.len,
122 color: shadow.color,
123 background_color: None,
124 underline: None,
125 strikethrough: None,
126 })
127 .collect();
128 let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
129 paint_line(
130 shadow_origin,
131 &self.layout,
132 line_height,
133 TextAlign::default(),
134 None,
135 &shadow_runs,
136 &[],
137 window,
138 cx,
139 )
140 }
141}
142
143impl LineLayout {
144 pub fn paint(
150 &self,
151 origin: Point<Pixels>,
152 line_height: Pixels,
153 align: TextAlign,
154 align_width: Option<Pixels>,
155 decoration_runs: &[DecorationRun],
156 window: &mut Window,
157 cx: &mut App,
158 ) -> Result<()> {
159 paint_line(
160 origin,
161 self,
162 line_height,
163 align,
164 align_width,
165 decoration_runs,
166 &[],
167 window,
168 cx,
169 )
170 }
171
172 pub fn paint_background(
178 &self,
179 origin: Point<Pixels>,
180 line_height: Pixels,
181 align: TextAlign,
182 align_width: Option<Pixels>,
183 decoration_runs: &[DecorationRun],
184 window: &mut Window,
185 cx: &mut App,
186 ) -> Result<()> {
187 paint_line_background(
188 origin,
189 self,
190 line_height,
191 align,
192 align_width,
193 decoration_runs,
194 &[],
195 window,
196 cx,
197 )
198 }
199}
200
201#[derive(Clone, Default, Debug, Deref, DerefMut)]
203pub struct WrappedLine {
204 #[deref]
205 #[deref_mut]
206 pub(crate) layout: Arc<WrappedLineLayout>,
207 pub text: SharedString,
209 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
210}
211
212impl WrappedLine {
213 #[allow(clippy::len_without_is_empty)]
215 pub fn len(&self) -> usize {
216 self.layout.len()
217 }
218
219 pub fn paint(
221 &self,
222 origin: Point<Pixels>,
223 line_height: Pixels,
224 align: TextAlign,
225 bounds: Option<Bounds<Pixels>>,
226 window: &mut Window,
227 cx: &mut App,
228 ) -> Result<()> {
229 let align_width = match bounds {
230 Some(bounds) => Some(bounds.size.width),
231 None => self.layout.wrap_width,
232 };
233
234 paint_line(
235 origin,
236 &self.layout.unwrapped_layout,
237 line_height,
238 align,
239 align_width,
240 &self.decoration_runs,
241 &self.wrap_boundaries,
242 window,
243 cx,
244 )?;
245
246 Ok(())
247 }
248
249 pub fn paint_background(
251 &self,
252 origin: Point<Pixels>,
253 line_height: Pixels,
254 align: TextAlign,
255 bounds: Option<Bounds<Pixels>>,
256 window: &mut Window,
257 cx: &mut App,
258 ) -> Result<()> {
259 let align_width = match bounds {
260 Some(bounds) => Some(bounds.size.width),
261 None => self.layout.wrap_width,
262 };
263
264 paint_line_background(
265 origin,
266 &self.layout.unwrapped_layout,
267 line_height,
268 align,
269 align_width,
270 &self.decoration_runs,
271 &self.wrap_boundaries,
272 window,
273 cx,
274 )?;
275
276 Ok(())
277 }
278
279 pub fn paint_shadow(
281 &self,
282 origin: Point<Pixels>,
283 line_height: Pixels,
284 align: TextAlign,
285 bounds: Option<Bounds<Pixels>>,
286 shadow: &TextShadow,
287 window: &mut Window,
288 cx: &mut App,
289 ) -> Result<()> {
290 let align_width = match bounds {
291 Some(bounds) => Some(bounds.size.width),
292 None => self.layout.wrap_width,
293 };
294 let shadow_runs: SmallVec<[DecorationRun; 32]> = self
295 .decoration_runs
296 .iter()
297 .map(|run| DecorationRun {
298 len: run.len,
299 color: shadow.color,
300 background_color: None,
301 underline: None,
302 strikethrough: None,
303 })
304 .collect();
305 let shadow_origin = point(origin.x + shadow.offset.x, origin.y + shadow.offset.y);
306 paint_line(
307 shadow_origin,
308 &self.layout.unwrapped_layout,
309 line_height,
310 align,
311 align_width,
312 &shadow_runs,
313 &self.wrap_boundaries,
314 window,
315 cx,
316 )
317 }
318}
319
320fn paint_line(
321 origin: Point<Pixels>,
322 layout: &LineLayout,
323 line_height: Pixels,
324 align: TextAlign,
325 align_width: Option<Pixels>,
326 decoration_runs: &[DecorationRun],
327 wrap_boundaries: &[WrapBoundary],
328 window: &mut Window,
329 cx: &mut App,
330) -> Result<()> {
331 let line_bounds = Bounds::new(
332 origin,
333 size(
334 layout.width,
335 line_height * (wrap_boundaries.len() as f32 + 1.),
336 ),
337 );
338 window.paint_layer(line_bounds, |window| {
339 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
340 let baseline_offset = point(px(0.), padding_top + layout.ascent);
341 let mut decoration_runs = decoration_runs.iter();
342 let mut wraps = wrap_boundaries.iter().peekable();
343 let mut run_end = 0;
344 let mut color = black();
345 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
346 let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
347 let text_system = cx.text_system().clone();
348 let mut glyph_origin = point(
349 aligned_origin_x(
350 origin,
351 align_width.unwrap_or(layout.width),
352 px(0.0),
353 &align,
354 layout,
355 wraps.peek(),
356 ),
357 origin.y,
358 );
359 let mut prev_glyph_position = Point::default();
360 let mut max_glyph_size = size(px(0.), px(0.));
361 let mut first_glyph_x = origin.x;
362 for (run_ix, run) in layout.runs.iter().enumerate() {
363 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
364
365 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
366 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
367 if glyph_ix == 0 && run_ix == 0 {
368 first_glyph_x = glyph_origin.x;
369 }
370
371 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
372 wraps.next();
373 if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
374 if glyph_origin.x == underline_origin.x {
375 underline_origin.x -= max_glyph_size.width.half();
376 };
377 window.paint_underline(
378 *underline_origin,
379 glyph_origin.x - underline_origin.x,
380 underline_style,
381 );
382 if glyph.index < run_end {
383 underline_origin.x = origin.x;
384 underline_origin.y += line_height;
385 } else {
386 current_underline = None;
387 }
388 }
389 if let Some((strikethrough_origin, strikethrough_style)) =
390 current_strikethrough.as_mut()
391 {
392 if glyph_origin.x == strikethrough_origin.x {
393 strikethrough_origin.x -= max_glyph_size.width.half();
394 };
395 window.paint_strikethrough(
396 *strikethrough_origin,
397 glyph_origin.x - strikethrough_origin.x,
398 strikethrough_style,
399 );
400 if glyph.index < run_end {
401 strikethrough_origin.x = origin.x;
402 strikethrough_origin.y += line_height;
403 } else {
404 current_strikethrough = None;
405 }
406 }
407
408 glyph_origin.x = aligned_origin_x(
409 origin,
410 align_width.unwrap_or(layout.width),
411 glyph.position.x,
412 &align,
413 layout,
414 wraps.peek(),
415 );
416 glyph_origin.y += line_height;
417 }
418 prev_glyph_position = glyph.position;
419
420 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
421 let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
422 if glyph.index >= run_end {
423 let mut style_run = decoration_runs.next();
424
425 while let Some(run) = style_run {
427 if glyph.index < run_end + (run.len as usize) {
428 break;
429 }
430 run_end += run.len as usize;
431 style_run = decoration_runs.next();
432 }
433
434 if let Some(style_run) = style_run {
435 if let Some((_, underline_style)) = &mut current_underline
436 && style_run.underline.as_ref() != Some(underline_style)
437 {
438 finished_underline = current_underline.take();
439 }
440 if let Some(run_underline) = style_run.underline.as_ref() {
441 current_underline.get_or_insert((
442 point(
443 glyph_origin.x,
444 glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
445 ),
446 UnderlineStyle {
447 color: Some(run_underline.color.unwrap_or(style_run.color)),
448 thickness: run_underline.thickness,
449 wavy: run_underline.wavy,
450 },
451 ));
452 }
453 if let Some((_, strikethrough_style)) = &mut current_strikethrough
454 && style_run.strikethrough.as_ref() != Some(strikethrough_style)
455 {
456 finished_strikethrough = current_strikethrough.take();
457 }
458 if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
459 current_strikethrough.get_or_insert((
460 point(
461 glyph_origin.x,
462 glyph_origin.y
463 + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
464 ),
465 StrikethroughStyle {
466 color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
467 thickness: run_strikethrough.thickness,
468 },
469 ));
470 }
471
472 run_end += style_run.len as usize;
473 color = style_run.color;
474 } else {
475 run_end = layout.len;
476 finished_underline = current_underline.take();
477 finished_strikethrough = current_strikethrough.take();
478 }
479 }
480
481 if let Some((mut underline_origin, underline_style)) = finished_underline {
482 if underline_origin.x == glyph_origin.x {
483 underline_origin.x -= max_glyph_size.width.half();
484 };
485 window.paint_underline(
486 underline_origin,
487 glyph_origin.x - underline_origin.x,
488 &underline_style,
489 );
490 }
491
492 if let Some((mut strikethrough_origin, strikethrough_style)) =
493 finished_strikethrough
494 {
495 if strikethrough_origin.x == glyph_origin.x {
496 strikethrough_origin.x -= max_glyph_size.width.half();
497 };
498 window.paint_strikethrough(
499 strikethrough_origin,
500 glyph_origin.x - strikethrough_origin.x,
501 &strikethrough_style,
502 );
503 }
504
505 let max_glyph_bounds = Bounds {
506 origin: glyph_origin,
507 size: max_glyph_size,
508 };
509
510 let content_mask = window.content_mask();
511 if max_glyph_bounds.intersects(&content_mask.bounds) {
512 if glyph.is_emoji {
513 window.paint_emoji(
514 glyph_origin + baseline_offset,
515 run.font_id,
516 glyph.id,
517 layout.font_size,
518 )?;
519 } else {
520 window.paint_glyph(
521 glyph_origin + baseline_offset,
522 run.font_id,
523 glyph.id,
524 layout.font_size,
525 color,
526 )?;
527 }
528 }
529 }
530 }
531
532 let mut last_line_end_x = first_glyph_x + layout.width;
533 if let Some(boundary) = wrap_boundaries.last() {
534 let run = &layout.runs[boundary.run_ix];
535 let glyph = &run.glyphs[boundary.glyph_ix];
536 last_line_end_x -= glyph.position.x;
537 }
538
539 if let Some((mut underline_start, underline_style)) = current_underline.take() {
540 if last_line_end_x == underline_start.x {
541 underline_start.x -= max_glyph_size.width.half()
542 };
543 window.paint_underline(
544 underline_start,
545 last_line_end_x - underline_start.x,
546 &underline_style,
547 );
548 }
549
550 if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
551 if last_line_end_x == strikethrough_start.x {
552 strikethrough_start.x -= max_glyph_size.width.half()
553 };
554 window.paint_strikethrough(
555 strikethrough_start,
556 last_line_end_x - strikethrough_start.x,
557 &strikethrough_style,
558 );
559 }
560
561 Ok(())
562 })
563}
564
565fn paint_line_background(
566 origin: Point<Pixels>,
567 layout: &LineLayout,
568 line_height: Pixels,
569 align: TextAlign,
570 align_width: Option<Pixels>,
571 decoration_runs: &[DecorationRun],
572 wrap_boundaries: &[WrapBoundary],
573 window: &mut Window,
574 cx: &mut App,
575) -> Result<()> {
576 let line_bounds = Bounds::new(
577 origin,
578 size(
579 layout.width,
580 line_height * (wrap_boundaries.len() as f32 + 1.),
581 ),
582 );
583 window.paint_layer(line_bounds, |window| {
584 let mut decoration_runs = decoration_runs.iter();
585 let mut wraps = wrap_boundaries.iter().peekable();
586 let mut run_end = 0;
587 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
588 let text_system = cx.text_system().clone();
589 let mut glyph_origin = point(
590 aligned_origin_x(
591 origin,
592 align_width.unwrap_or(layout.width),
593 px(0.0),
594 &align,
595 layout,
596 wraps.peek(),
597 ),
598 origin.y,
599 );
600 let mut prev_glyph_position = Point::default();
601 let mut max_glyph_size = size(px(0.), px(0.));
602 for (run_ix, run) in layout.runs.iter().enumerate() {
603 max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
604
605 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
606 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
607
608 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
609 wraps.next();
610 if let Some((background_origin, background_color)) = current_background.as_mut()
611 {
612 if glyph_origin.x == background_origin.x {
613 background_origin.x -= max_glyph_size.width.half()
614 }
615 window.paint_quad(fill(
616 Bounds {
617 origin: *background_origin,
618 size: size(glyph_origin.x - background_origin.x, line_height),
619 },
620 *background_color,
621 ));
622 if glyph.index < run_end {
623 background_origin.x = origin.x;
624 background_origin.y += line_height;
625 } else {
626 current_background = None;
627 }
628 }
629
630 glyph_origin.x = aligned_origin_x(
631 origin,
632 align_width.unwrap_or(layout.width),
633 glyph.position.x,
634 &align,
635 layout,
636 wraps.peek(),
637 );
638 glyph_origin.y += line_height;
639 }
640 prev_glyph_position = glyph.position;
641
642 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
643 if glyph.index >= run_end {
644 let mut style_run = decoration_runs.next();
645
646 while let Some(run) = style_run {
648 if glyph.index < run_end + (run.len as usize) {
649 break;
650 }
651 run_end += run.len as usize;
652 style_run = decoration_runs.next();
653 }
654
655 if let Some(style_run) = style_run {
656 if let Some((_, background_color)) = &mut current_background
657 && style_run.background_color.as_ref() != Some(background_color)
658 {
659 finished_background = current_background.take();
660 }
661 if let Some(run_background) = style_run.background_color {
662 current_background.get_or_insert((
663 point(glyph_origin.x, glyph_origin.y),
664 run_background,
665 ));
666 }
667 run_end += style_run.len as usize;
668 } else {
669 run_end = layout.len;
670 finished_background = current_background.take();
671 }
672 }
673
674 if let Some((mut background_origin, background_color)) = finished_background {
675 let mut width = glyph_origin.x - background_origin.x;
676 if background_origin.x == glyph_origin.x {
677 background_origin.x -= max_glyph_size.width.half();
678 };
679 window.paint_quad(fill(
680 Bounds {
681 origin: background_origin,
682 size: size(width, line_height),
683 },
684 background_color,
685 ));
686 }
687 }
688 }
689
690 let mut last_line_end_x = origin.x + layout.width;
691 if let Some(boundary) = wrap_boundaries.last() {
692 let run = &layout.runs[boundary.run_ix];
693 let glyph = &run.glyphs[boundary.glyph_ix];
694 last_line_end_x -= glyph.position.x;
695 }
696
697 if let Some((mut background_origin, background_color)) = current_background.take() {
698 if last_line_end_x == background_origin.x {
699 background_origin.x -= max_glyph_size.width.half()
700 };
701 window.paint_quad(fill(
702 Bounds {
703 origin: background_origin,
704 size: size(last_line_end_x - background_origin.x, line_height),
705 },
706 background_color,
707 ));
708 }
709
710 Ok(())
711 })
712}
713
714fn aligned_origin_x(
715 origin: Point<Pixels>,
716 align_width: Pixels,
717 last_glyph_x: Pixels,
718 align: &TextAlign,
719 layout: &LineLayout,
720 wrap_boundary: Option<&&WrapBoundary>,
721) -> Pixels {
722 let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
723 layout.runs[*run_ix].glyphs[*glyph_ix].position.x
724 } else {
725 layout.width
726 };
727
728 let line_width = end_of_line - last_glyph_x;
729
730 match align {
731 TextAlign::Left => origin.x,
732 TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
733 TextAlign::Right => origin.x + align_width - line_width,
734 }
735}