line.rs

  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: style_run.underline.color,
138                                    thickness: style_run.underline.thickness,
139                                    squiggly: style_run.underline.squiggly,
140                                },
141                            ));
142                        }
143
144                        run_end += style_run.len as usize;
145                        color = style_run.color;
146                    } else {
147                        run_end = self.layout.len;
148                        finished_underline = underline.take();
149                    }
150                }
151
152                if glyph_origin.x + max_glyph_width < visible_bounds.origin.x {
153                    continue;
154                }
155
156                if let Some((_underline_origin, _underline_style)) = finished_underline {
157                    todo!()
158                }
159
160                if glyph.is_emoji {
161                    cx.paint_emoji(glyph_origin, run.font_id, glyph.id, self.layout.font_size)?;
162                } else {
163                    cx.paint_glyph(
164                        glyph_origin,
165                        run.font_id,
166                        glyph.id,
167                        self.layout.font_size,
168                        color,
169                    )?;
170                }
171            }
172        }
173
174        if let Some((_underline_start, _underline_style)) = underline.take() {
175            let _line_end_x = origin.x + self.layout.width;
176            // cx.scene().push_underline(Underline {
177            //     origin: underline_start,
178            //     width: line_end_x - underline_start.x,
179            //     color: underline_style.color,
180            //     thickness: underline_style.thickness.into(),
181            //     squiggly: underline_style.squiggly,
182            // });
183        }
184
185        Ok(())
186    }
187
188    pub fn paint_wrapped(
189        &self,
190        origin: Point<Pixels>,
191        _visible_bounds: Bounds<Pixels>,
192        line_height: Pixels,
193        boundaries: &[ShapedBoundary],
194        cx: &mut WindowContext,
195    ) -> Result<()> {
196        let padding_top = (line_height - self.layout.ascent - self.layout.descent) / 2.;
197        let baseline_offset = point(px(0.), padding_top + self.layout.ascent);
198
199        let mut boundaries = boundaries.into_iter().peekable();
200        let mut color_runs = self.style_runs.iter();
201        let mut style_run_end = 0;
202        let mut _color = black(); // todo!
203        let mut underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
204
205        let mut glyph_origin = origin;
206        let mut prev_position = px(0.);
207        for (run_ix, run) in self.layout.runs.iter().enumerate() {
208            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
209                glyph_origin.x += glyph.position.x - prev_position;
210
211                if boundaries
212                    .peek()
213                    .map_or(false, |b| b.run_ix == run_ix && b.glyph_ix == glyph_ix)
214                {
215                    boundaries.next();
216                    if let Some((_underline_origin, _underline_style)) = underline.take() {
217                        // cx.scene().push_underline(Underline {
218                        //     origin: underline_origin,
219                        //     width: glyph_origin.x - underline_origin.x,
220                        //     thickness: underline_style.thickness.into(),
221                        //     color: underline_style.color.unwrap(),
222                        //     squiggly: underline_style.squiggly,
223                        // });
224                    }
225
226                    glyph_origin = point(origin.x, glyph_origin.y + line_height);
227                }
228                prev_position = glyph.position.x;
229
230                let mut finished_underline = None;
231                if glyph.index >= style_run_end {
232                    if let Some(style_run) = color_runs.next() {
233                        style_run_end += style_run.len as usize;
234                        _color = style_run.color;
235                        if let Some((_, underline_style)) = &mut underline {
236                            if style_run.underline != *underline_style {
237                                finished_underline = underline.take();
238                            }
239                        }
240                        if style_run.underline.thickness > px(0.) {
241                            underline.get_or_insert((
242                                glyph_origin
243                                    + point(
244                                        px(0.),
245                                        baseline_offset.y + (self.layout.descent * 0.618),
246                                    ),
247                                UnderlineStyle {
248                                    color: Some(
249                                        style_run.underline.color.unwrap_or(style_run.color),
250                                    ),
251                                    thickness: style_run.underline.thickness,
252                                    squiggly: style_run.underline.squiggly,
253                                },
254                            ));
255                        }
256                    } else {
257                        style_run_end = self.layout.len;
258                        _color = black();
259                        finished_underline = underline.take();
260                    }
261                }
262
263                if let Some((_underline_origin, _underline_style)) = finished_underline {
264                    // cx.scene().push_underline(Underline {
265                    //     origin: underline_origin,
266                    //     width: glyph_origin.x - underline_origin.x,
267                    //     thickness: underline_style.thickness.into(),
268                    //     color: underline_style.color.unwrap(),
269                    //     squiggly: underline_style.squiggly,
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.scene().push_underline(Underline {
304            //     origin: underline_origin,
305            //     width: line_end_x - underline_origin.x,
306            //     thickness: underline_style.thickness.into(),
307            //     color: underline_style.color,
308            //     squiggly: underline_style.squiggly,
309            // });
310        }
311
312        Ok(())
313    }
314}