line.rs

  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}