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: 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}