line.rs

  1use crate::{
  2    App, Bounds, Half, Hsla, LineLayout, Pixels, Point, Result, SharedString, StrikethroughStyle,
  3    TextAlign, TextStyle, UnderlineStyle, Window, WrapBoundary, WrappedLineLayout, black, fill,
  4    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<&TextStyle>,
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<&TextStyle>,
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.map(|s| s.text_align).unwrap_or(TextAlign::Left);
210
211    window.paint_layer(line_bounds, |window| {
212        let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
213        let baseline_offset = point(px(0.), padding_top + layout.ascent);
214        let mut decoration_runs = decoration_runs.iter();
215        let mut wraps = wrap_boundaries.iter().peekable();
216        let mut run_end = 0;
217        let mut color = black();
218        let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
219        let mut current_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
220        let text_system = cx.text_system().clone();
221        let mut glyph_origin = point(
222            aligned_origin_x(
223                origin,
224                align_width.unwrap_or(layout.width),
225                px(0.0),
226                &text_align,
227                layout,
228                wraps.peek(),
229            ),
230            origin.y,
231        );
232        let mut prev_glyph_position = Point::default();
233        let mut max_glyph_size = size(px(0.), px(0.));
234        let mut first_glyph_x = origin.x;
235        for (run_ix, run) in layout.runs.iter().enumerate() {
236            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
237
238            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
239                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
240                if glyph_ix == 0 && run_ix == 0 {
241                    first_glyph_x = glyph_origin.x;
242                }
243
244                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
245                    wraps.next();
246                    if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
247                        if glyph_origin.x == underline_origin.x {
248                            underline_origin.x -= max_glyph_size.width.half();
249                        };
250                        window.paint_underline(
251                            *underline_origin,
252                            glyph_origin.x - underline_origin.x,
253                            underline_style,
254                        );
255                        underline_origin.x = origin.x;
256                        underline_origin.y += line_height;
257                    }
258                    if let Some((strikethrough_origin, strikethrough_style)) =
259                        current_strikethrough.as_mut()
260                    {
261                        if glyph_origin.x == strikethrough_origin.x {
262                            strikethrough_origin.x -= max_glyph_size.width.half();
263                        };
264                        window.paint_strikethrough(
265                            *strikethrough_origin,
266                            glyph_origin.x - strikethrough_origin.x,
267                            strikethrough_style,
268                        );
269                        strikethrough_origin.x = origin.x;
270                        strikethrough_origin.y += line_height;
271                    }
272
273                    glyph_origin.x = aligned_origin_x(
274                        origin,
275                        align_width.unwrap_or(layout.width),
276                        glyph.position.x,
277                        &text_align,
278                        layout,
279                        wraps.peek(),
280                    );
281                    glyph_origin.y += line_height;
282                }
283                prev_glyph_position = glyph.position;
284
285                let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
286                let mut finished_strikethrough: Option<(Point<Pixels>, StrikethroughStyle)> = None;
287                if glyph.index >= run_end {
288                    let mut style_run = decoration_runs.next();
289
290                    // ignore style runs that apply to a partial glyph
291                    while let Some(run) = style_run {
292                        if glyph.index < run_end + (run.len as usize) {
293                            break;
294                        }
295                        run_end += run.len as usize;
296                        style_run = decoration_runs.next();
297                    }
298
299                    if let Some(style_run) = style_run {
300                        let mut run_color = style_run.color;
301                        let mut run_underline = style_run.underline.as_ref();
302                        let mut run_strikethrough = style_run.strikethrough;
303                        // Override by text run by current style when hovered or activated.
304                        if let Some(val) = text_style.map(|s| s.color) {
305                            run_color = val;
306                        }
307                        if let Some(val) = text_style.and_then(|s| s.underline.as_ref()) {
308                            run_underline = Some(val);
309                        }
310                        if let Some(val) = text_style.and_then(|s| s.strikethrough) {
311                            run_strikethrough = Some(val);
312                        }
313
314                        if let Some((_, underline_style)) = &mut current_underline {
315                            if style_run.underline.as_ref() != Some(underline_style) {
316                                finished_underline = current_underline.take();
317                            }
318                        }
319                        if let Some(run_underline) = run_underline.as_ref() {
320                            current_underline.get_or_insert((
321                                point(
322                                    glyph_origin.x,
323                                    glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
324                                ),
325                                UnderlineStyle {
326                                    color: Some(run_underline.color.unwrap_or(run_color)),
327                                    thickness: run_underline.thickness,
328                                    wavy: run_underline.wavy,
329                                },
330                            ));
331                        }
332                        if let Some((_, strikethrough_style)) = &mut current_strikethrough {
333                            if run_strikethrough.as_ref() != Some(strikethrough_style) {
334                                finished_strikethrough = current_strikethrough.take();
335                            }
336                        }
337                        if let Some(mut run_strikethrough) = run_strikethrough.as_ref() {
338                            current_strikethrough.get_or_insert((
339                                point(
340                                    glyph_origin.x,
341                                    glyph_origin.y
342                                        + (((layout.ascent * 0.5) + baseline_offset.y) * 0.5),
343                                ),
344                                StrikethroughStyle {
345                                    color: Some(run_strikethrough.color.unwrap_or(run_color)),
346                                    thickness: run_strikethrough.thickness,
347                                },
348                            ));
349                        }
350
351                        run_end += style_run.len as usize;
352                        color = run_color;
353                    } else {
354                        run_end = layout.len;
355                        finished_underline = current_underline.take();
356                        finished_strikethrough = current_strikethrough.take();
357                    }
358                }
359
360                if let Some((mut underline_origin, underline_style)) = finished_underline {
361                    if underline_origin.x == glyph_origin.x {
362                        underline_origin.x -= max_glyph_size.width.half();
363                    };
364                    window.paint_underline(
365                        underline_origin,
366                        glyph_origin.x - underline_origin.x,
367                        &underline_style,
368                    );
369                }
370
371                if let Some((mut strikethrough_origin, strikethrough_style)) =
372                    finished_strikethrough
373                {
374                    if strikethrough_origin.x == glyph_origin.x {
375                        strikethrough_origin.x -= max_glyph_size.width.half();
376                    };
377                    window.paint_strikethrough(
378                        strikethrough_origin,
379                        glyph_origin.x - strikethrough_origin.x,
380                        &strikethrough_style,
381                    );
382                }
383
384                let max_glyph_bounds = Bounds {
385                    origin: glyph_origin,
386                    size: max_glyph_size,
387                };
388
389                let content_mask = window.content_mask();
390                if max_glyph_bounds.intersects(&content_mask.bounds) {
391                    if glyph.is_emoji {
392                        window.paint_emoji(
393                            glyph_origin + baseline_offset,
394                            run.font_id,
395                            glyph.id,
396                            layout.font_size,
397                        )?;
398                    } else {
399                        window.paint_glyph(
400                            glyph_origin + baseline_offset,
401                            run.font_id,
402                            glyph.id,
403                            layout.font_size,
404                            color,
405                        )?;
406                    }
407                }
408            }
409        }
410
411        let mut last_line_end_x = first_glyph_x + layout.width;
412        if let Some(boundary) = wrap_boundaries.last() {
413            let run = &layout.runs[boundary.run_ix];
414            let glyph = &run.glyphs[boundary.glyph_ix];
415            last_line_end_x -= glyph.position.x;
416        }
417
418        if let Some((mut underline_start, underline_style)) = current_underline.take() {
419            if last_line_end_x == underline_start.x {
420                underline_start.x -= max_glyph_size.width.half()
421            };
422            window.paint_underline(
423                underline_start,
424                last_line_end_x - underline_start.x,
425                &underline_style,
426            );
427        }
428
429        if let Some((mut strikethrough_start, strikethrough_style)) = current_strikethrough.take() {
430            if last_line_end_x == strikethrough_start.x {
431                strikethrough_start.x -= max_glyph_size.width.half()
432            };
433            window.paint_strikethrough(
434                strikethrough_start,
435                last_line_end_x - strikethrough_start.x,
436                &strikethrough_style,
437            );
438        }
439
440        Ok(())
441    })
442}
443
444fn paint_line_background(
445    origin: Point<Pixels>,
446    layout: &LineLayout,
447    line_height: Pixels,
448    align: TextAlign,
449    align_width: Option<Pixels>,
450    decoration_runs: &[DecorationRun],
451    wrap_boundaries: &[WrapBoundary],
452    window: &mut Window,
453    cx: &mut App,
454) -> Result<()> {
455    let line_bounds = Bounds::new(
456        origin,
457        size(
458            layout.width,
459            line_height * (wrap_boundaries.len() as f32 + 1.),
460        ),
461    );
462    window.paint_layer(line_bounds, |window| {
463        let mut decoration_runs = decoration_runs.iter();
464        let mut wraps = wrap_boundaries.iter().peekable();
465        let mut run_end = 0;
466        let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
467        let text_system = cx.text_system().clone();
468        let mut glyph_origin = point(
469            aligned_origin_x(
470                origin,
471                align_width.unwrap_or(layout.width),
472                px(0.0),
473                &align,
474                layout,
475                wraps.peek(),
476            ),
477            origin.y,
478        );
479        let mut prev_glyph_position = Point::default();
480        let mut max_glyph_size = size(px(0.), px(0.));
481        for (run_ix, run) in layout.runs.iter().enumerate() {
482            max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
483
484            for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
485                glyph_origin.x += glyph.position.x - prev_glyph_position.x;
486
487                if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
488                    wraps.next();
489                    if let Some((background_origin, background_color)) = current_background.as_mut()
490                    {
491                        if glyph_origin.x == background_origin.x {
492                            background_origin.x -= max_glyph_size.width.half()
493                        }
494                        window.paint_quad(fill(
495                            Bounds {
496                                origin: *background_origin,
497                                size: size(glyph_origin.x - background_origin.x, line_height),
498                            },
499                            *background_color,
500                        ));
501                        background_origin.x = origin.x;
502                        background_origin.y += line_height;
503                    }
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                            if style_run.background_color.as_ref() != Some(background_color) {
523                                finished_background = current_background.take();
524                            }
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 => (2.0 * origin.x + align_width - line_width) / 2.0,
598        TextAlign::Right => origin.x + align_width - line_width,
599    }
600}