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