1use crate::{
2 black, point, px, Bounds, FontId, Hsla, Layout, Pixels, Point, RunStyle, ShapedBoundary,
3 ShapedLine, 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 // todo!
94 pub fn paint(
95 &self,
96 layout: &Layout,
97 visible_bounds: Bounds<Pixels>,
98 line_height: Pixels,
99 cx: &mut WindowContext,
100 ) -> Result<()> {
101 let origin = layout.bounds.origin;
102 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
103 let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
104
105 let mut style_runs = self.style_runs.iter();
106 let mut run_end = 0;
107 let mut color = black();
108 let mut underline = None;
109 let text_system = cx.text_system().clone();
110
111 for run in &self.layout.runs {
112 let max_glyph_width = text_system
113 .bounding_box(run.font_id, self.layout.font_size)?
114 .size
115 .width;
116
117 for glyph in &run.glyphs {
118 let glyph_origin = origin + baseline_offset + glyph.position;
119 if glyph_origin.x > visible_bounds.upper_right().x {
120 break;
121 }
122
123 let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
124 if glyph.index >= run_end {
125 if let Some(style_run) = style_runs.next() {
126 if let Some((_, underline_style)) = &mut underline {
127 if style_run.underline != *underline_style {
128 finished_underline = underline.take();
129 }
130 }
131 if style_run.underline.thickness > px(0.) {
132 underline.get_or_insert((
133 point(
134 glyph_origin.x,
135 origin.y + baseline_offset.y + (self.layout.descent * 0.618),
136 ),
137 UnderlineStyle {
138 color: style_run.underline.color,
139 thickness: style_run.underline.thickness,
140 squiggly: style_run.underline.squiggly,
141 },
142 ));
143 }
144
145 run_end += style_run.len as usize;
146 color = style_run.color;
147 } else {
148 run_end = self.layout.len;
149 finished_underline = underline.take();
150 }
151 }
152
153 if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
154 continue;
155 }
156
157 if let Some((_underline_origin, _underline_style)) = finished_underline {
158 todo!()
159 }
160
161 if glyph.is_emoji {
162 cx.paint_emoji(
163 glyph_origin,
164 layout.order,
165 run.font_id,
166 glyph.id,
167 self.layout.font_size,
168 )?;
169 } else {
170 cx.paint_glyph(
171 glyph_origin,
172 layout.order,
173 run.font_id,
174 glyph.id,
175 self.layout.font_size,
176 color,
177 )?;
178 }
179 }
180 }
181
182 if let Some((_underline_start, _underline_style)) = underline.take() {
183 let _line_end_x = origin.x + self.layout.width;
184 // cx.scene().push_underline(Underline {
185 // origin: underline_start,
186 // width: line_end_x - underline_start.x,
187 // color: underline_style.color,
188 // thickness: underline_style.thickness.into(),
189 // squiggly: underline_style.squiggly,
190 // });
191 }
192
193 Ok(())
194 }
195
196 pub fn paint_wrapped(
197 &self,
198 origin: Point<Pixels>,
199 _visible_bounds: Bounds<Pixels>,
200 line_height: Pixels,
201 boundaries: &[ShapedBoundary],
202 cx: &mut WindowContext,
203 ) -> Result<()> {
204 let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
205 let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
206
207 let mut boundaries = boundaries.into_iter().peekable();
208 let mut color_runs = self.style_runs.iter();
209 let mut style_run_end = 0;
210 let mut _color = black(); // todo!
211 let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
212
213 let mut glyph_origin = origin;
214 let mut prev_position = px(0.);
215 for (run_ix, run) in self.layout.runs.iter().enumerate() {
216 for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
217 glyph_origin.x += glyph.position.x - prev_position;
218
219 if boundaries
220 .peek()
221 .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
222 {
223 boundaries.next();
224 if let Some((_underline_origin, _underline_style)) = underline.take() {
225 // cx.scene().push_underline(Underline {
226 // origin: underline_origin,
227 // width: glyph_origin.x - underline_origin.x,
228 // thickness: underline_style.thickness.into(),
229 // color: underline_style.color.unwrap(),
230 // squiggly: underline_style.squiggly,
231 // });
232 }
233
234 glyph_origin = point(origin.x, glyph_origin.y + line_height);
235 }
236 prev_position = glyph.position.x;
237
238 let mut finished_underline = None;
239 if glyph.index >= style_run_end {
240 if let Some(style_run) = color_runs.next() {
241 style_run_end += style_run.len as usize;
242 _color = style_run.color;
243 if let Some((_, underline_style)) = &mut underline {
244 if style_run.underline != *underline_style {
245 finished_underline = underline.take();
246 }
247 }
248 if style_run.underline.thickness > px(0.) {
249 underline.get_or_insert((
250 glyph_origin
251 + point(
252 px(0.),
253 baseline_offset.y + (self.layout.descent * 0.618),
254 ),
255 UnderlineStyle {
256 color: Some(
257 style_run.underline.color.unwrap_or(style_run.color),
258 ),
259 thickness: style_run.underline.thickness,
260 squiggly: style_run.underline.squiggly,
261 },
262 ));
263 }
264 } else {
265 style_run_end = self.layout.len;
266 _color = black();
267 finished_underline = underline.take();
268 }
269 }
270
271 if let Some((_underline_origin, _underline_style)) = finished_underline {
272 // cx.scene().push_underline(Underline {
273 // origin: underline_origin,
274 // width: glyph_origin.x - underline_origin.x,
275 // thickness: underline_style.thickness.into(),
276 // color: underline_style.color.unwrap(),
277 // squiggly: underline_style.squiggly,
278 // });
279 }
280
281 let text_system = cx.text_system();
282 let _glyph_bounds = Bounds {
283 origin: glyph_origin,
284 size: text_system
285 .bounding_box(run.font_id, self.layout.font_size)?
286 .size,
287 };
288 // if glyph_bounds.intersects(visible_bounds) {
289 // if glyph.is_emoji {
290 // cx.scene().push_image_glyph(scene::ImageGlyph {
291 // font_id: run.font_id,
292 // font_size: self.layout.font_size,
293 // id: glyph.id,
294 // origin: glyph_bounds.origin() + baseline_offset,
295 // });
296 // } else {
297 // cx.scene().push_glyph(scene::Glyph {
298 // font_id: run.font_id,
299 // font_size: self.layout.font_size,
300 // id: glyph.id,
301 // origin: glyph_bounds.origin() + baseline_offset,
302 // color,
303 // });
304 // }
305 // }
306 }
307 }
308
309 if let Some((_underline_origin, _underline_style)) = underline.take() {
310 // let line_end_x = glyph_origin.x + self.layout.width - prev_position;
311 // cx.scene().push_underline(Underline {
312 // origin: underline_origin,
313 // width: line_end_x - underline_origin.x,
314 // thickness: underline_style.thickness.into(),
315 // color: underline_style.color,
316 // squiggly: underline_style.squiggly,
317 // });
318 }
319
320 Ok(())
321 }
322}