line.rs

  1use crate::{
  2    App, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result, SharedString, StrikethroughStyle,
  3    TextAlign, UnderlineStyle, Window, WrapBoundary, WrappedLineLayout, black, fill, point, px,
  4    size,
  5};
  6use derive_more::{Deref, DerefMut};
  7use smallvec::SmallVec;
  8use std::sync::Arc;
  9
 10/// Set the text decoration for a run of text.
 11#[derive(Debug, Clone)]
 12pub struct DecorationRun {
 13    /// The length of the run in utf-8 bytes.
 14    pub len: u32,
 15
 16    /// The color for this run
 17    pub color: Hsla,
 18
 19    /// The background color for this run
 20    pub background_color: Option<Hsla>,
 21
 22    /// The underline style for this run
 23    pub underline: Option<UnderlineStyle>,
 24
 25    /// The strikethrough style for this run
 26    pub strikethrough: Option<StrikethroughStyle>,
 27}
 28
 29/// A line of text that has been shaped and decorated.
 30#[derive(Clone, Default, Debug, Deref, DerefMut)]
 31pub struct ShapedLine {
 32    #[deref]
 33    #[deref_mut]
 34    pub(crate) layout: Arc<LineLayout>,
 35    /// The text that was shaped for this line.
 36    pub text: SharedString,
 37    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
 38}
 39
 40impl ShapedLine {
 41    /// The length of the line in utf-8 bytes.
 42    #[allow(clippy::len_without_is_empty)]
 43    pub fn len(&self) -> usize {
 44        self.layout.len
 45    }
 46
 47    /// Override the len, useful if you're rendering text a
 48    /// as text b (e.g. rendering invisibles).
 49    pub fn with_len(mut self, len: usize) -> Self {
 50        let layout = self.layout.as_ref();
 51        self.layout = Arc::new(LineLayout {
 52            font_size: layout.font_size,
 53            width: layout.width,
 54            ascent: layout.ascent,
 55            descent: layout.descent,
 56            runs: layout.runs.clone(),
 57            len,
 58        });
 59        self
 60    }
 61
 62    /// Paint the line of text to the window.
 63    pub fn paint(
 64        &self,
 65        origin: Point<Pixels>,
 66        line_height: Pixels,
 67        window: &mut Window,
 68        cx: &mut App,
 69    ) -> Result<()> {
 70        paint_line(
 71            origin,
 72            &self.layout,
 73            line_height,
 74            TextAlign::default(),
 75            None,
 76            &self.decoration_runs,
 77            &[],
 78            window,
 79            cx,
 80        )?;
 81
 82        Ok(())
 83    }
 84
 85    /// Paint the background of the line to the window.
 86    pub fn paint_background(
 87        &self,
 88        origin: Point<Pixels>,
 89        line_height: Pixels,
 90        window: &mut Window,
 91        cx: &mut App,
 92    ) -> Result<()> {
 93        paint_line_background(
 94            origin,
 95            &self.layout,
 96            line_height,
 97            TextAlign::default(),
 98            None,
 99            &self.decoration_runs,
100            &[],
101            window,
102            cx,
103        )?;
104
105        Ok(())
106    }
107}
108
109/// A line of text that has been shaped, decorated, and wrapped by the text layout system.
110#[derive(Clone, Default, Debug, Deref, DerefMut)]
111pub struct WrappedLine {
112    #[deref]
113    #[deref_mut]
114    pub(crate) layout: Arc<WrappedLineLayout>,
115    /// The text that was shaped for this line.
116    pub text: SharedString,
117    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
118}
119
120impl WrappedLine {
121    /// The length of the underlying, unwrapped layout, in utf-8 bytes.
122    #[allow(clippy::len_without_is_empty)]
123    pub fn len(&self) -> usize {
124        self.layout.len()
125    }
126
127    /// Paint this line of text to the window.
128    pub fn paint(
129        &self,
130        origin: Point<Pixels>,
131        line_height: Pixels,
132        align: TextAlign,
133        bounds: Option<Bounds<Pixels>>,
134        window: &mut Window,
135        cx: &mut App,
136    ) -> Result<()> {
137        let align_width = match bounds {
138            Some(bounds) => Some(bounds.size.width),
139            None => self.layout.wrap_width,
140        };
141
142        paint_line(
143            origin,
144            &self.layout.unwrapped_layout,
145            line_height,
146            align,
147            align_width,
148            &self.decoration_runs,
149            &self.wrap_boundaries,
150            window,
151            cx,
152        )?;
153
154        Ok(())
155    }
156}
157
158fn paint_line(
159    origin: Point<Pixels>,
160    layout: &LineLayout,
161    line_height: Pixels,
162    align: TextAlign,
163    align_width: Option<Pixels>,
164    decoration_runs: &[DecorationRun],
165    wrap_boundaries: &[WrapBoundary],
166    window: &mut Window,
167    cx: &mut App,
168) -> Result<()> {
169    let line_bounds = Bounds::new(
170        origin,
171        size(
172            layout.width,
173            line_height * (wrap_boundaries.len() as f32 + 1.),
174        ),
175    );
176    window.paint_layer(line_bounds, |window| {
177        let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
178        let baseline_offset = point(px(0.), padding_top + layout.ascent);
179        let mut decoration_runs = decoration_runs.iter();
180        let mut wraps = wrap_boundaries.iter().peekable();
181        let mut run_end = 0;
182        let mut color = black();
183        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
184        let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
185        let text_system = cx.text_system().clone();
186        let mut glyph_origin = point(
187            aligned_origin_x(
188                origin,
189                align_width.unwrap_or(layout.width),
190                px(0.0),
191                &align,
192                layout,
193                wraps.peek(),
194            ),
195            origin.y,
196        );
197        let mut prev_glyph_position = Point::default();
198        let mut max_glyph_size = size(px(0.), px(0.));
199        let mut first_glyph_x = origin.x;
200        for (run_ix, run) in layout.runs.iter().enumerate() {
201            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
202
203            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
204                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
205                if glyph_ix == 0 && run_ix == 0 {
206                    first_glyph_x = glyph_origin.x;
207                }
208
209                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
210                    wraps.next();
211                    if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
212                        if glyph_origin.x == underline_origin.x {
213                            underline_origin.x -= max_glyph_size.width.half();
214                        };
215                        window.paint_underline(
216                            *underline_origin,
217                            glyph_origin.x - underline_origin.x,
218                            underline_style,
219                        );
220                        underline_origin.x = origin.x;
221                        underline_origin.y += line_height;
222                    }
223                    if let Some((strikethrough_origin, strikethrough_style)) =
224                        current_strikethrough.as_mut()
225                    {
226                        if glyph_origin.x == strikethrough_origin.x {
227                            strikethrough_origin.x -= max_glyph_size.width.half();
228                        };
229                        window.paint_strikethrough(
230                            *strikethrough_origin,
231                            glyph_origin.x - strikethrough_origin.x,
232                            strikethrough_style,
233                        );
234                        strikethrough_origin.x = origin.x;
235                        strikethrough_origin.y += line_height;
236                    }
237
238                    glyph_origin.x = aligned_origin_x(
239                        origin,
240                        align_width.unwrap_or(layout.width),
241                        glyph.position.x,
242                        &align,
243                        layout,
244                        wraps.peek(),
245                    );
246                    glyph_origin.y += line_height;
247                }
248                prev_glyph_position = glyph.position;
249
250                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
251                let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
252                if glyph.index >= run_end {
253                    let mut style_run = decoration_runs.next();
254
255                    // ignore style runs that apply to a partial glyph
256                    while let Some(run) = style_run {
257                        if glyph.index < run_end + (run.len as usize) {
258                            break;
259                        }
260                        run_end += run.len as usize;
261                        style_run = decoration_runs.next();
262                    }
263
264                    if let Some(style_run) = style_run {
265                        if let Some((_, underline_style)) = &mut current_underline {
266                            if style_run.underline.as_ref() != Some(underline_style) {
267                                finished_underline = current_underline.take();
268                            }
269                        }
270                        if let Some(run_underline) = style_run.underline.as_ref() {
271                            current_underline.get_or_insert((
272                                point(
273                                    glyph_origin.x,
274                                    glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
275                                ),
276                                UnderlineStyle {
277                                    color: Some(run_underline.color.unwrap_or(style_run.color)),
278                                    thickness: run_underline.thickness,
279                                    wavy: run_underline.wavy,
280                                },
281                            ));
282                        }
283                        if let Some((_, strikethrough_style)) = &mut current_strikethrough {
284                            if style_run.strikethrough.as_ref() != Some(strikethrough_style) {
285                                finished_strikethrough = current_strikethrough.take();
286                            }
287                        }
288                        if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
289                            current_strikethrough.get_or_insert((
290                                point(
291                                    glyph_origin.x,
292                                    glyph_origin.y
293                                        + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
294                                ),
295                                StrikethroughStyle {
296                                    color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
297                                    thickness: run_strikethrough.thickness,
298                                },
299                            ));
300                        }
301
302                        run_end += style_run.len as usize;
303                        color = style_run.color;
304                    } else {
305                        run_end = layout.len;
306                        finished_underline = current_underline.take();
307                        finished_strikethrough = current_strikethrough.take();
308                    }
309                }
310
311                if let Some((mut underline_origin, underline_style)) = finished_underline {
312                    if underline_origin.x == glyph_origin.x {
313                        underline_origin.x -= max_glyph_size.width.half();
314                    };
315                    window.paint_underline(
316                        underline_origin,
317                        glyph_origin.x - underline_origin.x,
318                        &underline_style,
319                    );
320                }
321
322                if let Some((mut strikethrough_origin, strikethrough_style)) =
323                    finished_strikethrough
324                {
325                    if strikethrough_origin.x == glyph_origin.x {
326                        strikethrough_origin.x -= max_glyph_size.width.half();
327                    };
328                    window.paint_strikethrough(
329                        strikethrough_origin,
330                        glyph_origin.x - strikethrough_origin.x,
331                        &strikethrough_style,
332                    );
333                }
334
335                let max_glyph_bounds = Bounds {
336                    origin: glyph_origin,
337                    size: max_glyph_size,
338                };
339
340                let content_mask = window.content_mask();
341                if max_glyph_bounds.intersects(&content_mask.bounds) {
342                    if glyph.is_emoji {
343                        window.paint_emoji(
344                            glyph_origin + baseline_offset,
345                            run.font_id,
346                            glyph.id,
347                            layout.font_size,
348                        )?;
349                    } else {
350                        window.paint_glyph(
351                            glyph_origin + baseline_offset,
352                            run.font_id,
353                            glyph.id,
354                            layout.font_size,
355                            color,
356                        )?;
357                    }
358                }
359            }
360        }
361
362        let mut last_line_end_x = first_glyph_x + layout.width;
363        if let Some(boundary) = wrap_boundaries.last() {
364            let run = &layout.runs[boundary.run_ix];
365            let glyph = &run.glyphs[boundary.glyph_ix];
366            last_line_end_x -= glyph.position.x;
367        }
368
369        if let Some((mut underline_start, underline_style)) = current_underline.take() {
370            if last_line_end_x == underline_start.x {
371                underline_start.x -= max_glyph_size.width.half()
372            };
373            window.paint_underline(
374                underline_start,
375                last_line_end_x - underline_start.x,
376                &underline_style,
377            );
378        }
379
380        if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
381            if last_line_end_x == strikethrough_start.x {
382                strikethrough_start.x -= max_glyph_size.width.half()
383            };
384            window.paint_strikethrough(
385                strikethrough_start,
386                last_line_end_x - strikethrough_start.x,
387                &strikethrough_style,
388            );
389        }
390
391        Ok(())
392    })
393}
394
395fn paint_line_background(
396    origin: Point<Pixels>,
397    layout: &LineLayout,
398    line_height: Pixels,
399    align: TextAlign,
400    align_width: Option<Pixels>,
401    decoration_runs: &[DecorationRun],
402    wrap_boundaries: &[WrapBoundary],
403    window: &mut Window,
404    cx: &mut App,
405) -> Result<()> {
406    let line_bounds = Bounds::new(
407        origin,
408        size(
409            layout.width,
410            line_height * (wrap_boundaries.len() as f32 + 1.),
411        ),
412    );
413    window.paint_layer(line_bounds, |window| {
414        let mut decoration_runs = decoration_runs.iter();
415        let mut wraps = wrap_boundaries.iter().peekable();
416        let mut run_end = 0;
417        let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
418        let text_system = cx.text_system().clone();
419        let mut glyph_origin = point(
420            aligned_origin_x(
421                origin,
422                align_width.unwrap_or(layout.width),
423                px(0.0),
424                &align,
425                layout,
426                wraps.peek(),
427            ),
428            origin.y,
429        );
430        let mut prev_glyph_position = Point::default();
431        let mut max_glyph_size = size(px(0.), px(0.));
432        for (run_ix, run) in layout.runs.iter().enumerate() {
433            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
434
435            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
436                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
437
438                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
439                    wraps.next();
440                    if let Some((background_origin, background_color)) = current_background.as_mut()
441                    {
442                        if glyph_origin.x == background_origin.x {
443                            background_origin.x -= max_glyph_size.width.half()
444                        }
445                        window.paint_quad(fill(
446                            Bounds {
447                                origin: *background_origin,
448                                size: size(glyph_origin.x - background_origin.x, line_height),
449                            },
450                            *background_color,
451                        ));
452                        background_origin.x = origin.x;
453                        background_origin.y += line_height;
454                    }
455                }
456                prev_glyph_position = glyph.position;
457
458                let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
459                if glyph.index >= run_end {
460                    let mut style_run = decoration_runs.next();
461
462                    // ignore style runs that apply to a partial glyph
463                    while let Some(run) = style_run {
464                        if glyph.index < run_end + (run.len as usize) {
465                            break;
466                        }
467                        run_end += run.len as usize;
468                        style_run = decoration_runs.next();
469                    }
470
471                    if let Some(style_run) = style_run {
472                        if let Some((_, background_color)) = &mut current_background {
473                            if style_run.background_color.as_ref() != Some(background_color) {
474                                finished_background = current_background.take();
475                            }
476                        }
477                        if let Some(run_background) = style_run.background_color {
478                            current_background.get_or_insert((
479                                point(glyph_origin.x, glyph_origin.y),
480                                run_background,
481                            ));
482                        }
483                        run_end += style_run.len as usize;
484                    } else {
485                        run_end = layout.len;
486                        finished_background = current_background.take();
487                    }
488                }
489
490                if let Some((mut background_origin, background_color)) = finished_background {
491                    let mut width = glyph_origin.x - background_origin.x;
492                    if background_origin.x == glyph_origin.x {
493                        background_origin.x -= max_glyph_size.width.half();
494                    };
495                    window.paint_quad(fill(
496                        Bounds {
497                            origin: background_origin,
498                            size: size(width, line_height),
499                        },
500                        background_color,
501                    ));
502                }
503            }
504        }
505
506        let mut last_line_end_x = origin.x + layout.width;
507        if let Some(boundary) = wrap_boundaries.last() {
508            let run = &layout.runs[boundary.run_ix];
509            let glyph = &run.glyphs[boundary.glyph_ix];
510            last_line_end_x -= glyph.position.x;
511        }
512
513        if let Some((mut background_origin, background_color)) = current_background.take() {
514            if last_line_end_x == background_origin.x {
515                background_origin.x -= max_glyph_size.width.half()
516            };
517            window.paint_quad(fill(
518                Bounds {
519                    origin: background_origin,
520                    size: size(last_line_end_x - background_origin.x, line_height),
521                },
522                background_color,
523            ));
524        }
525
526        Ok(())
527    })
528}
529
530fn aligned_origin_x(
531    origin: Point<Pixels>,
532    align_width: Pixels,
533    last_glyph_x: Pixels,
534    align: &TextAlign,
535    layout: &LineLayout,
536    wrap_boundary: Option<&&WrapBoundary>,
537) -> Pixels {
538    let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
539        layout.runs[*run_ix].glyphs[*glyph_ix].position.x
540    } else {
541        layout.width
542    };
543
544    let line_width = end_of_line - last_glyph_x;
545
546    match align {
547        TextAlign::Left => origin.x,
548        TextAlign::Center => (2.0 * origin.x + align_width - line_width) / 2.0,
549        TextAlign::Right => origin.x + align_width - line_width,
550    }
551}