1use crate::{
2 black, point, px, size, transparent_black, BorrowWindow, Bounds, Corners, Edges, Hsla,
3 LineLayout, Pixels, Point, Result, SharedString, UnderlineStyle, WindowContext, WrapBoundary,
4 WrappedLineLayout,
5};
6use derive_more::{Deref, DerefMut};
7use smallvec::SmallVec;
8use std::sync::Arc;
9
10#[derive(Debug, Clone)]
11pub struct DecorationRun {
12 pub len: u32,
13 pub color: Hsla,
14 pub background_color: Option<Hsla>,
15 pub underline: Option<UnderlineStyle>,
16}
17
18#[derive(Clone, Default, Debug, Deref, DerefMut)]
19pub struct ShapedLine {
20 #[deref]
21 #[deref_mut]
22 pub(crate) layout: Arc<LineLayout>,
23 pub text: SharedString,
24 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
25}
26
27impl ShapedLine {
28 pub fn len(&self) -> usize {
29 self.layout.len
30 }
31
32 pub fn paint(
33 &self,
34 origin: Point<Pixels>,
35 line_height: Pixels,
36 cx: &mut WindowContext,
37 ) -> Result<()> {
38 paint_line(
39 origin,
40 &self.layout,
41 line_height,
42 &self.decoration_runs,
43 None,
44 &[],
45 cx,
46 )?;
47
48 Ok(())
49 }
50}
51
52#[derive(Clone, Default, Debug, Deref, DerefMut)]
53pub struct WrappedLine {
54 #[deref]
55 #[deref_mut]
56 pub(crate) layout: Arc<WrappedLineLayout>,
57 pub text: SharedString,
58 pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
59}
60
61impl WrappedLine {
62 pub fn len(&self) -> usize {
63 self.layout.len()
64 }
65
66 pub fn paint(
67 &self,
68 origin: Point<Pixels>,
69 line_height: Pixels,
70 cx: &mut WindowContext,
71 ) -> Result<()> {
72 paint_line(
73 origin,
74 &self.layout.unwrapped_layout,
75 line_height,
76 &self.decoration_runs,
77 self.wrap_width,
78 &self.wrap_boundaries,
79 cx,
80 )?;
81
82 Ok(())
83 }
84}
85
86fn paint_line(
87 origin: Point<Pixels>,
88 layout: &LineLayout,
89 line_height: Pixels,
90 decoration_runs: &[DecorationRun],
91 wrap_width: Option<Pixels>,
92 wrap_boundaries: &[WrapBoundary],
93 cx: &mut WindowContext<'_>,
94) -> Result<()> {
95 let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
96 let baseline_offset = point(px(0.), padding_top + layout.ascent);
97 let mut decoration_runs = decoration_runs.iter();
98 let mut wraps = wrap_boundaries.iter().peekable();
99 let mut run_end = 0;
100 let mut color = black();
101 let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
102 let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
103 let text_system = cx.text_system().clone();
104 let mut glyph_origin = origin;
105 let mut prev_glyph_position = Point::default();
106 for (run_ix, run) in layout.runs.iter().enumerate() {
107 let max_glyph_size = text_system
108 .bounding_box(run.font_id, layout.font_size)?
109 .size;
110
111 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
112 glyph_origin.x += glyph.position.x - prev_glyph_position.x;
113
114 if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
115 wraps.next();
116 if let Some((background_origin, background_color)) = current_background.take() {
117 cx.paint_quad(
118 Bounds {
119 origin: background_origin,
120 size: size(glyph_origin.x - background_origin.x, line_height),
121 },
122 Corners::default(),
123 background_color,
124 Edges::default(),
125 transparent_black(),
126 );
127 }
128 if let Some((underline_origin, underline_style)) = current_underline.take() {
129 cx.paint_underline(
130 underline_origin,
131 glyph_origin.x - underline_origin.x,
132 &underline_style,
133 );
134 }
135
136 glyph_origin.x = origin.x;
137 glyph_origin.y += line_height;
138 }
139 prev_glyph_position = glyph.position;
140
141 let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
142 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
143 if glyph.index >= run_end {
144 if let Some(style_run) = decoration_runs.next() {
145 if let Some((_, background_color)) = &mut current_background {
146 if style_run.background_color.as_ref() != Some(background_color) {
147 finished_background = current_background.take();
148 }
149 }
150 if let Some(run_background) = style_run.background_color {
151 current_background
152 .get_or_insert((point(glyph_origin.x, origin.y), run_background));
153 }
154
155 if let Some((_, underline_style)) = &mut current_underline {
156 if style_run.underline.as_ref() != Some(underline_style) {
157 finished_underline = current_underline.take();
158 }
159 }
160 if let Some(run_underline) = style_run.underline.as_ref() {
161 current_underline.get_or_insert((
162 point(
163 glyph_origin.x,
164 origin.y + baseline_offset.y + (layout.descent * 0.618),
165 ),
166 UnderlineStyle {
167 color: Some(run_underline.color.unwrap_or(style_run.color)),
168 thickness: run_underline.thickness,
169 wavy: run_underline.wavy,
170 },
171 ));
172 }
173
174 run_end += style_run.len as usize;
175 color = style_run.color;
176 } else {
177 run_end = layout.len;
178 finished_background = current_background.take();
179 finished_underline = current_underline.take();
180 }
181 }
182
183 if let Some((background_origin, background_color)) = finished_background {
184 cx.paint_quad(
185 Bounds {
186 origin: background_origin,
187 size: size(glyph_origin.x - background_origin.x, line_height),
188 },
189 Corners::default(),
190 background_color,
191 Edges::default(),
192 transparent_black(),
193 );
194 }
195
196 if let Some((underline_origin, underline_style)) = finished_underline {
197 cx.paint_underline(
198 underline_origin,
199 glyph_origin.x - underline_origin.x,
200 &underline_style,
201 );
202 }
203
204 let max_glyph_bounds = Bounds {
205 origin: glyph_origin,
206 size: max_glyph_size,
207 };
208
209 let content_mask = cx.content_mask();
210 if max_glyph_bounds.intersects(&content_mask.bounds) {
211 if glyph.is_emoji {
212 cx.paint_emoji(
213 glyph_origin + baseline_offset,
214 run.font_id,
215 glyph.id,
216 layout.font_size,
217 )?;
218 } else {
219 cx.paint_glyph(
220 glyph_origin + baseline_offset,
221 run.font_id,
222 glyph.id,
223 layout.font_size,
224 color,
225 )?;
226 }
227 }
228 }
229 }
230
231 if let Some((background_origin, background_color)) = current_background.take() {
232 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
233 cx.paint_quad(
234 Bounds {
235 origin: background_origin,
236 size: size(line_end_x - background_origin.x, line_height),
237 },
238 Corners::default(),
239 background_color,
240 Edges::default(),
241 transparent_black(),
242 );
243 }
244
245 if let Some((underline_start, underline_style)) = current_underline.take() {
246 let line_end_x = origin.x + wrap_width.unwrap_or(Pixels::MAX).min(layout.width);
247 cx.paint_underline(
248 underline_start,
249 line_end_x - underline_start.x,
250 &underline_style,
251 );
252 }
253
254 Ok(())
255}