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