line.rs

  1use crate::{
  2    black, fill, point, px, size, BorrowWindow, Bounds, Hsla, LineLayout, Pixels, Point, Result,
  3    SharedString, UnderlineStyle, WindowContext, WrapBoundary, WrappedLineLayout,
  4};
  5use derive_more::{Deref, DerefMut};
  6use smallvec::SmallVec;
  7use std::sync::Arc;
  8
  9#[derive(Debug, Clone)]
 10pub struct DecorationRun {
 11    pub len: u32,
 12    pub color: Hsla,
 13    pub background_color: Option<Hsla>,
 14    pub underline: Option<UnderlineStyle>,
 15}
 16
 17#[derive(Clone, Default, Debug, Deref, DerefMut)]
 18pub struct ShapedLine {
 19    #[deref]
 20    #[deref_mut]
 21    pub(crate) layout: Arc<LineLayout>,
 22    pub text: SharedString,
 23    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
 24}
 25
 26impl ShapedLine {
 27    pub fn len(&self) -> usize {
 28        self.layout.len
 29    }
 30
 31    pub fn paint(
 32        &self,
 33        origin: Point<Pixels>,
 34        line_height: Pixels,
 35        cx: &mut WindowContext,
 36    ) -> Result<()> {
 37        paint_line(
 38            origin,
 39            &self.layout,
 40            line_height,
 41            &self.decoration_runs,
 42            &[],
 43            cx,
 44        )?;
 45
 46        Ok(())
 47    }
 48}
 49
 50#[derive(Clone, Default, Debug, Deref, DerefMut)]
 51pub struct WrappedLine {
 52    #[deref]
 53    #[deref_mut]
 54    pub(crate) layout: Arc<WrappedLineLayout>,
 55    pub text: SharedString,
 56    pub(crate) decoration_runs: SmallVec<[DecorationRun; 32]>,
 57}
 58
 59impl WrappedLine {
 60    pub fn len(&self) -> usize {
 61        self.layout.len()
 62    }
 63
 64    pub fn paint(
 65        &self,
 66        origin: Point<Pixels>,
 67        line_height: Pixels,
 68        cx: &mut WindowContext,
 69    ) -> Result<()> {
 70        paint_line(
 71            origin,
 72            &self.layout.unwrapped_layout,
 73            line_height,
 74            &self.decoration_runs,
 75            &self.wrap_boundaries,
 76            cx,
 77        )?;
 78
 79        Ok(())
 80    }
 81}
 82
 83fn paint_line(
 84    origin: Point<Pixels>,
 85    layout: &LineLayout,
 86    line_height: Pixels,
 87    decoration_runs: &[DecorationRun],
 88    wrap_boundaries: &[WrapBoundary],
 89    cx: &mut WindowContext<'_>,
 90) -> Result<()> {
 91    let padding_top = (line_height - layout.ascent - layout.descent) / 2.;
 92    let baseline_offset = point(px(0.), padding_top + layout.ascent);
 93    let mut decoration_runs = decoration_runs.iter();
 94    let mut wraps = wrap_boundaries.iter().peekable();
 95    let mut run_end = 0;
 96    let mut color = black();
 97    let mut current_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
 98    let mut current_background: Option<(Point<Pixels>, Hsla)> = None;
 99    let text_system = cx.text_system().clone();
100    let mut glyph_origin = origin;
101    let mut prev_glyph_position = Point::default();
102    for (run_ix, run) in layout.runs.iter().enumerate() {
103        let max_glyph_size = text_system.bounding_box(run.font_id, layout.font_size).size;
104
105        for (glyph_ix, glyph) in run.glyphs.iter().enumerate() {
106            glyph_origin.x += glyph.position.x - prev_glyph_position.x;
107
108            if wraps.peek() == Some(&&WrapBoundary { run_ix, glyph_ix }) {
109                wraps.next();
110                if let Some((background_origin, background_color)) = current_background.as_mut() {
111                    cx.paint_quad(fill(
112                        Bounds {
113                            origin: *background_origin,
114                            size: size(glyph_origin.x - background_origin.x, line_height),
115                        },
116                        *background_color,
117                    ));
118                    background_origin.x = origin.x;
119                    background_origin.y += line_height;
120                }
121                if let Some((underline_origin, underline_style)) = current_underline.as_mut() {
122                    cx.paint_underline(
123                        *underline_origin,
124                        glyph_origin.x - underline_origin.x,
125                        underline_style,
126                    );
127                    underline_origin.x = origin.x;
128                    underline_origin.y += line_height;
129                }
130
131                glyph_origin.x = origin.x;
132                glyph_origin.y += line_height;
133            }
134            prev_glyph_position = glyph.position;
135
136            let mut finished_background: Option<(Point<Pixels>, Hsla)> = None;
137            let mut finished_underline: Option<(Point<Pixels>, UnderlineStyle)> = None;
138            if glyph.index >= run_end {
139                if let Some(style_run) = decoration_runs.next() {
140                    if let Some((_, background_color)) = &mut current_background {
141                        if style_run.background_color.as_ref() != Some(background_color) {
142                            finished_background = current_background.take();
143                        }
144                    }
145                    if let Some(run_background) = style_run.background_color {
146                        current_background
147                            .get_or_insert((point(glyph_origin.x, glyph_origin.y), run_background));
148                    }
149
150                    if let Some((_, underline_style)) = &mut current_underline {
151                        if style_run.underline.as_ref() != Some(underline_style) {
152                            finished_underline = current_underline.take();
153                        }
154                    }
155                    if let Some(run_underline) = style_run.underline.as_ref() {
156                        current_underline.get_or_insert((
157                            point(
158                                glyph_origin.x,
159                                glyph_origin.y + baseline_offset.y + (layout.descent * 0.618),
160                            ),
161                            UnderlineStyle {
162                                color: Some(run_underline.color.unwrap_or(style_run.color)),
163                                thickness: run_underline.thickness,
164                                wavy: run_underline.wavy,
165                            },
166                        ));
167                    }
168
169                    run_end += style_run.len as usize;
170                    color = style_run.color;
171                } else {
172                    run_end = layout.len;
173                    finished_background = current_background.take();
174                    finished_underline = current_underline.take();
175                }
176            }
177
178            if let Some((background_origin, background_color)) = finished_background {
179                cx.paint_quad(fill(
180                    Bounds {
181                        origin: background_origin,
182                        size: size(glyph_origin.x - background_origin.x, line_height),
183                    },
184                    background_color,
185                ));
186            }
187
188            if let Some((underline_origin, underline_style)) = finished_underline {
189                cx.paint_underline(
190                    underline_origin,
191                    glyph_origin.x - underline_origin.x,
192                    &underline_style,
193                );
194            }
195
196            let max_glyph_bounds = Bounds {
197                origin: glyph_origin,
198                size: max_glyph_size,
199            };
200
201            let content_mask = cx.content_mask();
202            if max_glyph_bounds.intersects(&content_mask.bounds) {
203                if glyph.is_emoji {
204                    cx.paint_emoji(
205                        glyph_origin + baseline_offset,
206                        run.font_id,
207                        glyph.id,
208                        layout.font_size,
209                    )?;
210                } else {
211                    cx.paint_glyph(
212                        glyph_origin + baseline_offset,
213                        run.font_id,
214                        glyph.id,
215                        layout.font_size,
216                        color,
217                    )?;
218                }
219            }
220        }
221    }
222
223    let mut last_line_end_x = origin.x + layout.width;
224    if let Some(boundary) = wrap_boundaries.last() {
225        let run = &layout.runs[boundary.run_ix];
226        let glyph = &run.glyphs[boundary.glyph_ix];
227        last_line_end_x -= glyph.position.x;
228    }
229
230    if let Some((background_origin, background_color)) = current_background.take() {
231        cx.paint_quad(fill(
232            Bounds {
233                origin: background_origin,
234                size: size(last_line_end_x - background_origin.x, line_height),
235            },
236            background_color,
237        ));
238    }
239
240    if let Some((underline_start, underline_style)) = current_underline.take() {
241        cx.paint_underline(
242            underline_start,
243            last_line_end_x - underline_start.x,
244            &underline_style,
245        );
246    }
247
248    Ok(())
249}