1use crate::{
2 black, point, px, size, BorrowWindow, Bounds, Hsla, Pixels, Point, Result, Size,
3 UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
4};
5use smallvec::SmallVec;
6use std::sync::Arc;
7
8#[derive(Debug, Clone)]
9pub struct DecorationRun {
10 pub len: u32,
11 pub color: Hsla,
12 pub underline: Option<UnderlineStyle>,
13}
14
15#[derive(Clone, Default, Debug)]
16pub struct Line {
17 pub(crate) layout: Arc<WrappedLineLayout>,
18 pub(crate) decorations: SmallVec<[DecorationRun; 32]>,
19}
20
21impl Line {
22 pub fn size(&self, line_height: Pixels) -> Size<Pixels> {
23 size(
24 self.layout.width,
25 line_height * (self.layout.wrap_boundaries.len() + 1),
26 )
27 }
28
29 pub fn paint(
30 &self,
31 origin: Point<Pixels>,
32 line_height: Pixels,
33 cx: &mut WindowContext,
34 ) -> Result<()> {
35 let padding_top =
36 (line_height - self.layout.layout.ascent - self.layout.layout.descent) / 2.;
37 let baseline_offset = point(px(0.), padding_top + self.layout.layout.ascent);
38
39 let mut style_runs = self.decorations.iter();
40 let mut wraps = self.layout.wrap_boundaries.iter().peekable();
41 let mut run_end = 0;
42 let mut color = black();
43 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
44 let text_system = cx.text_system().clone();
45
46 let mut glyph_origin = origin;
47 let mut prev_glyph_position = Point::default();
48 for (run_ix, run) in self.layout.layout.runs.iter().enumerate() {
49 let max_glyph_size = text_system
50 .bounding_box(run.font_id, self.layout.layout.font_size)?
51 .size;
52
53 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
54 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
55
56 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
57 wraps.next();
58 if let Some((underline_origin, underline_style)) = current_underline.take() {
59 cx.paint_underline(
60 underline_origin,
61 glyph_origin.x - underline_origin.x,
62 &underline_style,
63 )?;
64 }
65
66 glyph_origin.x = origin.x;
67 glyph_origin.y += line_height;
68 }
69 prev_glyph_position = glyph.position;
70 let glyph_origin = glyph_origin + baseline_offset;
71
72 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
73 if glyph.index >= run_end {
74 if let Some(style_run) = style_runs.next() {
75 if let Some((_, underline_style)) = &mut current_underline {
76 if style_run.underline.as_ref() != Some(underline_style) {
77 finished_underline = current_underline.take();
78 }
79 }
80 if let Some(run_underline) = style_run.underline.as_ref() {
81 current_underline.get_or_insert((
82 point(
83 glyph_origin.x,
84 origin.y
85 + baseline_offset.y
86 + (self.layout.layout.descent * 0.618),
87 ),
88 UnderlineStyle {
89 color: Some(run_underline.color.unwrap_or(style_run.color)),
90 thickness: run_underline.thickness,
91 wavy: run_underline.wavy,
92 },
93 ));
94 }
95
96 run_end += style_run.len as usize;
97 color = style_run.color;
98 } else {
99 run_end = self.layout.text.len();
100 finished_underline = current_underline.take();
101 }
102 }
103
104 if let Some((underline_origin, underline_style)) = finished_underline {
105 cx.paint_underline(
106 underline_origin,
107 glyph_origin.x - underline_origin.x,
108 &underline_style,
109 )?;
110 }
111
112 let max_glyph_bounds = Bounds {
113 origin: glyph_origin,
114 size: max_glyph_size,
115 };
116
117 let content_mask = cx.content_mask();
118 if max_glyph_bounds.intersects(&content_mask.bounds) {
119 if glyph.is_emoji {
120 cx.paint_emoji(
121 glyph_origin,
122 run.font_id,
123 glyph.id,
124 self.layout.layout.font_size,
125 )?;
126 } else {
127 cx.paint_glyph(
128 glyph_origin,
129 run.font_id,
130 glyph.id,
131 self.layout.layout.font_size,
132 color,
133 )?;
134 }
135 }
136 }
137 }
138
139 if let Some((underline_start, underline_style)) = current_underline.take() {
140 let line_end_x = origin.x + self.layout.layout.width;
141 cx.paint_underline(
142 underline_start,
143 line_end_x - underline_start.x,
144 &underline_style,
145 )?;
146 }
147
148 Ok(())
149 }
150}