line.rs

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