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