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    /// Paint the background of line of text to the window.
158    pub fn paint_background(
159        &self,
160        origin: Point<Pixels>,
161        line_height: Pixels,
162        align: TextAlign,
163        bounds: Option<Bounds<Pixels>>,
164        window: &mut Window,
165        cx: &mut App,
166    ) -> Result<()> {
167        let align_width = match bounds {
168            Some(bounds) => Some(bounds.size.width),
169            None => self.layout.wrap_width,
170        };
171
172        paint_line_background(
173            origin,
174            &self.layout.unwrapped_layout,
175            line_height,
176            align,
177            align_width,
178            &self.decoration_runs,
179            &self.wrap_boundaries,
180            window,
181            cx,
182        )?;
183
184        Ok(())
185    }
186}
187
188fn paint_line(
189    origin: Point<Pixels>,
190    layout: &LineLayout,
191    line_height: Pixels,
192    align: TextAlign,
193    align_width: Option<Pixels>,
194    decoration_runs: &[DecorationRun],
195    wrap_boundaries: &[WrapBoundary],
196    window: &mut Window,
197    cx: &mut App,
198) -> Result<()> {
199    let line_bounds = Bounds::new(
200        origin,
201        size(
202            layout.width,
203            line_height * (wrap_boundaries.len() as f32 + 1.),
204        ),
205    );
206    window.paint_layer(line_bounds, |window| {
207        let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
208        let baseline_offset = point(px(0.), padding_top + layout.ascent);
209        let mut decoration_runs = decoration_runs.iter();
210        let mut wraps = wrap_boundaries.iter().peekable();
211        let mut run_end = 0;
212        let mut color = black();
213        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
214        let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
215        let text_system = cx.text_system().clone();
216        let mut glyph_origin = point(
217            aligned_origin_x(
218                origin,
219                align_width.unwrap_or(layout.width),
220                px(0.0),
221                &align,
222                layout,
223                wraps.peek(),
224            ),
225            origin.y,
226        );
227        let mut prev_glyph_position = Point::default();
228        let mut max_glyph_size = size(px(0.), px(0.));
229        let mut first_glyph_x = origin.x;
230        for (run_ix, run) in layout.runs.iter().enumerate() {
231            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
232
233            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
234                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
235                if glyph_ix == 0 && run_ix == 0 {
236                    first_glyph_x = glyph_origin.x;
237                }
238
239                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
240                    wraps.next();
241                    if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
242                        if glyph_origin.x == underline_origin.x {
243                            underline_origin.x -= max_glyph_size.width.half();
244                        };
245                        window.paint_underline(
246                            *underline_origin,
247                            glyph_origin.x - underline_origin.x,
248                            underline_style,
249                        );
250                        underline_origin.x = origin.x;
251                        underline_origin.y += line_height;
252                    }
253                    if let Some((strikethrough_origin, strikethrough_style)) =
254                        current_strikethrough.as_mut()
255                    {
256                        if glyph_origin.x == strikethrough_origin.x {
257                            strikethrough_origin.x -= max_glyph_size.width.half();
258                        };
259                        window.paint_strikethrough(
260                            *strikethrough_origin,
261                            glyph_origin.x - strikethrough_origin.x,
262                            strikethrough_style,
263                        );
264                        strikethrough_origin.x = origin.x;
265                        strikethrough_origin.y += line_height;
266                    }
267
268                    glyph_origin.x = aligned_origin_x(
269                        origin,
270                        align_width.unwrap_or(layout.width),
271                        glyph.position.x,
272                        &align,
273                        layout,
274                        wraps.peek(),
275                    );
276                    glyph_origin.y += line_height;
277                }
278                prev_glyph_position = glyph.position;
279
280                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
281                let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
282                if glyph.index >= run_end {
283                    let mut style_run = decoration_runs.next();
284
285                    // ignore style runs that apply to a partial glyph
286                    while let Some(run) = style_run {
287                        if glyph.index < run_end + (run.len as usize) {
288                            break;
289                        }
290                        run_end += run.len as usize;
291                        style_run = decoration_runs.next();
292                    }
293
294                    if let Some(style_run) = style_run {
295                        if let Some((_, underline_style)) = &mut current_underline
296                            && style_run.underline.as_ref() != Some(underline_style)
297                        {
298                            finished_underline = current_underline.take();
299                        }
300                        if let Some(run_underline) = style_run.underline.as_ref() {
301                            current_underline.get_or_insert((
302                                point(
303                                    glyph_origin.x,
304                                    glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
305                                ),
306                                UnderlineStyle {
307                                    color: Some(run_underline.color.unwrap_or(style_run.color)),
308                                    thickness: run_underline.thickness,
309                                    wavy: run_underline.wavy,
310                                },
311                            ));
312                        }
313                        if let Some((_, strikethrough_style)) = &mut current_strikethrough
314                            && style_run.strikethrough.as_ref() != Some(strikethrough_style)
315                        {
316                            finished_strikethrough = current_strikethrough.take();
317                        }
318                        if let Some(run_strikethrough) = style_run.strikethrough.as_ref() {
319                            current_strikethrough.get_or_insert((
320                                point(
321                                    glyph_origin.x,
322                                    glyph_origin.y
323                                        + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
324                                ),
325                                StrikethroughStyle {
326                                    color: Some(run_strikethrough.color.unwrap_or(style_run.color)),
327                                    thickness: run_strikethrough.thickness,
328                                },
329                            ));
330                        }
331
332                        run_end += style_run.len as usize;
333                        color = style_run.color;
334                    } else {
335                        run_end = layout.len;
336                        finished_underline = current_underline.take();
337                        finished_strikethrough = current_strikethrough.take();
338                    }
339                }
340
341                if let Some((mut underline_origin, underline_style)) = finished_underline {
342                    if underline_origin.x == glyph_origin.x {
343                        underline_origin.x -= max_glyph_size.width.half();
344                    };
345                    window.paint_underline(
346                        underline_origin,
347                        glyph_origin.x - underline_origin.x,
348                        &underline_style,
349                    );
350                }
351
352                if let Some((mut strikethrough_origin, strikethrough_style)) =
353                    finished_strikethrough
354                {
355                    if strikethrough_origin.x == glyph_origin.x {
356                        strikethrough_origin.x -= max_glyph_size.width.half();
357                    };
358                    window.paint_strikethrough(
359                        strikethrough_origin,
360                        glyph_origin.x - strikethrough_origin.x,
361                        &strikethrough_style,
362                    );
363                }
364
365                let max_glyph_bounds = Bounds {
366                    origin: glyph_origin,
367                    size: max_glyph_size,
368                };
369
370                let content_mask = window.content_mask();
371                if max_glyph_bounds.intersects(&content_mask.bounds) {
372                    let vertical_offset = point(px(0.0), glyph.position.y);
373                    if glyph.is_emoji {
374                        window.paint_emoji(
375                            glyph_origin + baseline_offset + vertical_offset,
376                            run.font_id,
377                            glyph.id,
378                            layout.font_size,
379                        )?;
380                    } else {
381                        window.paint_glyph(
382                            glyph_origin + baseline_offset + vertical_offset,
383                            run.font_id,
384                            glyph.id,
385                            layout.font_size,
386                            color,
387                        )?;
388                    }
389                }
390            }
391        }
392
393        let mut last_line_end_x = first_glyph_x + layout.width;
394        if let Some(boundary) = wrap_boundaries.last() {
395            let run = &layout.runs[boundary.run_ix];
396            let glyph = &run.glyphs[boundary.glyph_ix];
397            last_line_end_x -= glyph.position.x;
398        }
399
400        if let Some((mut underline_start, underline_style)) = current_underline.take() {
401            if last_line_end_x == underline_start.x {
402                underline_start.x -= max_glyph_size.width.half()
403            };
404            window.paint_underline(
405                underline_start,
406                last_line_end_x - underline_start.x,
407                &underline_style,
408            );
409        }
410
411        if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
412            if last_line_end_x == strikethrough_start.x {
413                strikethrough_start.x -= max_glyph_size.width.half()
414            };
415            window.paint_strikethrough(
416                strikethrough_start,
417                last_line_end_x - strikethrough_start.x,
418                &strikethrough_style,
419            );
420        }
421
422        Ok(())
423    })
424}
425
426fn paint_line_background(
427    origin: Point<Pixels>,
428    layout: &LineLayout,
429    line_height: Pixels,
430    align: TextAlign,
431    align_width: Option<Pixels>,
432    decoration_runs: &[DecorationRun],
433    wrap_boundaries: &[WrapBoundary],
434    window: &mut Window,
435    cx: &mut App,
436) -> Result<()> {
437    let line_bounds = Bounds::new(
438        origin,
439        size(
440            layout.width,
441            line_height * (wrap_boundaries.len() as f32 + 1.),
442        ),
443    );
444    window.paint_layer(line_bounds, |window| {
445        let mut decoration_runs = decoration_runs.iter();
446        let mut wraps = wrap_boundaries.iter().peekable();
447        let mut run_end = 0;
448        let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
449        let text_system = cx.text_system().clone();
450        let mut glyph_origin = point(
451            aligned_origin_x(
452                origin,
453                align_width.unwrap_or(layout.width),
454                px(0.0),
455                &align,
456                layout,
457                wraps.peek(),
458            ),
459            origin.y,
460        );
461        let mut prev_glyph_position = Point::default();
462        let mut max_glyph_size = size(px(0.), px(0.));
463        for (run_ix, run) in layout.runs.iter().enumerate() {
464            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
465
466            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
467                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
468
469                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
470                    wraps.next();
471                    if let Some((background_origin, background_color)) = current_background.as_mut()
472                    {
473                        if glyph_origin.x == background_origin.x {
474                            background_origin.x -= max_glyph_size.width.half()
475                        }
476                        window.paint_quad(fill(
477                            Bounds {
478                                origin: *background_origin,
479                                size: size(glyph_origin.x - background_origin.x, line_height),
480                            },
481                            *background_color,
482                        ));
483                        background_origin.x = origin.x;
484                        background_origin.y += line_height;
485                    }
486
487                    glyph_origin.x = aligned_origin_x(
488                        origin,
489                        align_width.unwrap_or(layout.width),
490                        glyph.position.x,
491                        &align,
492                        layout,
493                        wraps.peek(),
494                    );
495                    glyph_origin.y += line_height;
496                }
497                prev_glyph_position = glyph.position;
498
499                let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
500                if glyph.index >= run_end {
501                    let mut style_run = decoration_runs.next();
502
503                    // ignore style runs that apply to a partial glyph
504                    while let Some(run) = style_run {
505                        if glyph.index < run_end + (run.len as usize) {
506                            break;
507                        }
508                        run_end += run.len as usize;
509                        style_run = decoration_runs.next();
510                    }
511
512                    if let Some(style_run) = style_run {
513                        if let Some((_, background_color)) = &mut current_background
514                            && style_run.background_color.as_ref() != Some(background_color)
515                        {
516                            finished_background = current_background.take();
517                        }
518                        if let Some(run_background) = style_run.background_color {
519                            current_background.get_or_insert((
520                                point(glyph_origin.x, glyph_origin.y),
521                                run_background,
522                            ));
523                        }
524                        run_end += style_run.len as usize;
525                    } else {
526                        run_end = layout.len;
527                        finished_background = current_background.take();
528                    }
529                }
530
531                if let Some((mut background_origin, background_color)) = finished_background {
532                    let mut width = glyph_origin.x - background_origin.x;
533                    if background_origin.x == glyph_origin.x {
534                        background_origin.x -= max_glyph_size.width.half();
535                    };
536                    window.paint_quad(fill(
537                        Bounds {
538                            origin: background_origin,
539                            size: size(width, line_height),
540                        },
541                        background_color,
542                    ));
543                }
544            }
545        }
546
547        let mut last_line_end_x = origin.x + layout.width;
548        if let Some(boundary) = wrap_boundaries.last() {
549            let run = &layout.runs[boundary.run_ix];
550            let glyph = &run.glyphs[boundary.glyph_ix];
551            last_line_end_x -= glyph.position.x;
552        }
553
554        if let Some((mut background_origin, background_color)) = current_background.take() {
555            if last_line_end_x == background_origin.x {
556                background_origin.x -= max_glyph_size.width.half()
557            };
558            window.paint_quad(fill(
559                Bounds {
560                    origin: background_origin,
561                    size: size(last_line_end_x - background_origin.x, line_height),
562                },
563                background_color,
564            ));
565        }
566
567        Ok(())
568    })
569}
570
571fn aligned_origin_x(
572    origin: Point<Pixels>,
573    align_width: Pixels,
574    last_glyph_x: Pixels,
575    align: &TextAlign,
576    layout: &LineLayout,
577    wrap_boundary: Option<&&WrapBoundary>,
578) -> Pixels {
579    let end_of_line = if let Some(WrapBoundary { run_ix, glyph_ix }) = wrap_boundary {
580        layout.runs[*run_ix].glyphs[*glyph_ix].position.x
581    } else {
582        layout.width
583    };
584
585    let line_width = end_of_line - last_glyph_x;
586
587    match align {
588        TextAlign::Left => origin.x,
589        TextAlign::Center => (origin.x * 2.0 + align_width - line_width) / 2.0,
590        TextAlign::Right => origin.x + align_width - line_width,
591    }
592}