1use crate::{
2 black, point, px, Bounds, FontId, Hsla, Pixels, Point, RunStyle, ShapedBoundary, ShapedLine,
3 ShapedRun, UnderlineStyle, WindowContext,
4};
5use anyhow::Result;
6use smallvec::SmallVec;
7use std::sync::Arc;
8
9#[derive(Default, Debug, Clone)]
10pub struct Line {
11 layout: Arc<ShapedLine>,
12 style_runs: SmallVec<[StyleRun; 32]>,
13}
14
15#[derive(Debug, Clone)]
16struct StyleRun {
17 len: u32,
18 color: Hsla,
19 underline: UnderlineStyle,
20}
21
22impl Line {
23 pub fn new(layout: Arc<ShapedLine>, runs: &[(usize, RunStyle)]) -> Self {
24 let mut style_runs = SmallVec::new();
25 for (len, style) in runs {
26 style_runs.push(StyleRun {
27 len: *len as u32,
28 color: style.color,
29 underline: style.underline.clone().unwrap_or_default(),
30 });
31 }
32 Self { layout, style_runs }
33 }
34
35 pub fn runs(&self) -> &[ShapedRun] {
36 &self.layout.runs
37 }
38
39 pub fn width(&self) -> Pixels {
40 self.layout.width
41 }
42
43 pub fn font_size(&self) -> Pixels {
44 self.layout.font_size
45 }
46
47 pub fn x_for_index(&self, index: usize) -> Pixels {
48 for run in &self.layout.runs {
49 for glyph in &run.glyphs {
50 if glyph.index >= index {
51 return glyph.position.x;
52 }
53 }
54 }
55 self.layout.width
56 }
57
58 pub fn font_for_index(&self, index: usize) -> Option<FontId> {
59 for run in &self.layout.runs {
60 for glyph in &run.glyphs {
61 if glyph.index >= index {
62 return Some(run.font_id);
63 }
64 }
65 }
66
67 None
68 }
69
70 pub fn len(&self) -> usize {
71 self.layout.len
72 }
73
74 pub fn is_empty(&self) -> bool {
75 self.layout.len == 0
76 }
77
78 pub fn index_for_x(&self, x: Pixels) -> Option<usize> {
79 if x >= self.layout.width {
80 None
81 } else {
82 for run in self.layout.runs.iter().rev() {
83 for glyph in run.glyphs.iter().rev() {
84 if glyph.position.x <= x {
85 return Some(glyph.index);
86 }
87 }
88 }
89 Some(0)
90 }
91 }
92
93 pub fn paint(
94 &self,
95 bounds: Bounds<Pixels>,
96 visible_bounds: Bounds<Pixels>, // todo!("use clipping")
97 line_height: Pixels,
98 cx: &mut WindowContext,
99 ) -> Result<()> {
100 let origin = bounds.origin;
101 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
102 let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
103
104 let mut style_runs = self.style_runs.iter();
105 let mut run_end = 0;
106 let mut color = black();
107 let mut underline = None;
108 let text_system = cx.text_system().clone();
109
110 for run in &self.layout.runs {
111 let max_glyph_width = text_system
112 .bounding_box(run.font_id, self.layout.font_size)?
113 .size
114 .width;
115
116 for glyph in &run.glyphs {
117 let glyph_origin = origin + baseline_offset + glyph.position;
118 if glyph_origin.x > visible_bounds.upper_right().x {
119 break;
120 }
121
122 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
123 if glyph.index >= run_end {
124 if let Some(style_run) = style_runs.next() {
125 if let Some((_, underline_style)) = &mut underline {
126 if style_run.underline != *underline_style {
127 finished_underline = underline.take();
128 }
129 }
130 if style_run.underline.thickness > px(0.) {
131 underline.get_or_insert((
132 point(
133 glyph_origin.x,
134 origin.y + baseline_offset.y + (self.layout.descent * 0.618),
135 ),
136 UnderlineStyle {
137 color: Some(
138 style_run.underline.color.unwrap_or(style_run.color),
139 ),
140 thickness: style_run.underline.thickness,
141 wavy: style_run.underline.wavy,
142 },
143 ));
144 }
145
146 run_end += style_run.len as usize;
147 color = style_run.color;
148 } else {
149 run_end = self.layout.len;
150 finished_underline = underline.take();
151 }
152 }
153
154 if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
155 continue;
156 }
157
158 if let Some((underline_origin, underline_style)) = finished_underline {
159 cx.paint_underline(
160 underline_origin,
161 glyph_origin.x - underline_origin.x,
162 &underline_style,
163 )?;
164 }
165
166 if glyph.is_emoji {
167 cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
168 } else {
169 cx.paint_glyph(
170 glyph_origin,
171 run.font_id,
172 glyph.id,
173 self.layout.font_size,
174 color,
175 )?;
176 }
177 }
178 }
179
180 if let Some((underline_start, underline_style)) = underline.take() {
181 let line_end_x = origin.x + self.layout.width;
182 cx.paint_underline(
183 underline_start,
184 line_end_x - underline_start.x,
185 &underline_style,
186 )?;
187 }
188
189 Ok(())
190 }
191
192 pub fn paint_wrapped(
193 &self,
194 origin: Point<Pixels>,
195 _visible_bounds: Bounds<Pixels>, // todo!("use clipping")
196 line_height: Pixels,
197 boundaries: &[ShapedBoundary],
198 cx: &mut WindowContext,
199 ) -> Result<()> {
200 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
201 let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
202
203 let mut boundaries = boundaries.into_iter().peekable();
204 let mut color_runs = self.style_runs.iter();
205 let mut style_run_end = 0;
206 let mut _color = black(); // todo!
207 let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
208
209 let mut glyph_origin = origin;
210 let mut prev_position = px(0.);
211 for (run_ix, run) in self.layout.runs.iter().enumerate() {
212 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
213 glyph_origin.x += glyph.position.x - prev_position;
214
215 if boundaries
216 .peek()
217 .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
218 {
219 boundaries.next();
220 if let Some((underline_origin, underline_style)) = underline.take() {
221 cx.paint_underline(
222 underline_origin,
223 glyph_origin.x - underline_origin.x,
224 &underline_style,
225 )?;
226 }
227
228 glyph_origin = point(origin.x, glyph_origin.y + line_height);
229 }
230 prev_position = glyph.position.x;
231
232 let mut finished_underline = None;
233 if glyph.index >= style_run_end {
234 if let Some(style_run) = color_runs.next() {
235 style_run_end += style_run.len as usize;
236 _color = style_run.color;
237 if let Some((_, underline_style)) = &mut underline {
238 if style_run.underline != *underline_style {
239 finished_underline = underline.take();
240 }
241 }
242 if style_run.underline.thickness > px(0.) {
243 underline.get_or_insert((
244 glyph_origin
245 + point(
246 px(0.),
247 baseline_offset.y + (self.layout.descent * 0.618),
248 ),
249 UnderlineStyle {
250 color: Some(
251 style_run.underline.color.unwrap_or(style_run.color),
252 ),
253 thickness: style_run.underline.thickness,
254 wavy: style_run.underline.wavy,
255 },
256 ));
257 }
258 } else {
259 style_run_end = self.layout.len;
260 _color = black();
261 finished_underline = underline.take();
262 }
263 }
264
265 if let Some((underline_origin, underline_style)) = finished_underline {
266 cx.paint_underline(
267 underline_origin,
268 glyph_origin.x - underline_origin.x,
269 &underline_style,
270 )?;
271 }
272
273 let text_system = cx.text_system();
274 let _glyph_bounds = Bounds {
275 origin: glyph_origin,
276 size: text_system
277 .bounding_box(run.font_id, self.layout.font_size)?
278 .size,
279 };
280 // if glyph_bounds.intersects(visible_bounds) {
281 // if glyph.is_emoji {
282 // cx.scene().push_image_glyph(scene::ImageGlyph {
283 // font_id: run.font_id,
284 // font_size: self.layout.font_size,
285 // id: glyph.id,
286 // origin: glyph_bounds.origin() + baseline_offset,
287 // });
288 // } else {
289 // cx.scene().push_glyph(scene::Glyph {
290 // font_id: run.font_id,
291 // font_size: self.layout.font_size,
292 // id: glyph.id,
293 // origin: glyph_bounds.origin() + baseline_offset,
294 // color,
295 // });
296 // }
297 // }
298 }
299 }
300
301 if let Some((underline_origin, underline_style)) = underline.take() {
302 let line_end_x = glyph_origin.x + self.layout.width - prev_position;
303 cx.paint_underline(
304 underline_origin,
305 line_end_x - underline_origin.x,
306 &underline_style,
307 )?;
308 }
309
310 Ok(())
311 }
312}