piet_cosmic_text/
lines.rs

1// SPDX-License-Identifier: LGPL-3.0-or-later OR MPL-2.0
2// This file is a part of `piet-cosmic-text`.
3//
4// `piet-cosmic-text` is free software: you can redistribute it and/or modify it under the
5// terms of either:
6//
7// * GNU Lesser General Public License as published by the Free Software Foundation, either
8//   version 3 of the License, or (at your option) any later version.
9// * Mozilla Public License as published by the Mozilla Foundation, version 2.
10//
11// `piet-cosmic-text` is distributed in the hope that it will be useful, but WITHOUT ANY
12// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13// PURPOSE. See the GNU Lesser General Public License or the Mozilla Public License for more
14// details.
15//
16// You should have received a copy of the GNU Lesser General Public License and the Mozilla
17// Public License along with `piet-cosmic-text`. If not, see <https://www.gnu.org/licenses/>.
18
19//! Integration with the [`line-straddler`] crate.
20//!
21//! [`line-straddler`]: https://crates.io/crates/line-straddler
22
23use crate::metadata::Metadata;
24
25use core::mem;
26use cosmic_text::LayoutGlyph;
27use line_straddler::{Glyph, GlyphStyle, Line as LsLine, LineGenerator, LineType};
28
29use piet::kurbo::{Line, Point, Rect};
30use piet::{Color, FontWeight};
31
32/// A bundle between a line and a glyph styling.
33#[derive(Debug, Clone, Copy, PartialEq)]
34pub struct StyledLine {
35    /// The line.
36    pub line: Line,
37
38    /// The color of the line.
39    pub color: Color,
40
41    /// Whether or not the line is bolded.
42    pub bold: FontWeight,
43
44    /// The size of the font, in pixels.
45    pub font_size: f32,
46}
47
48impl StyledLine {
49    /// Represent this styled line as a rectangle.
50    pub fn into_rect(self) -> Rect {
51        const FONT_WEIGHT_MULTIPLIER: f32 = 0.05;
52        const OFFSET_MULTIPLIER: f32 = -0.83;
53
54        let offset = self.font_size * OFFSET_MULTIPLIER;
55        let width = self.font_size
56            * (self.bold.to_raw() as f32 / FontWeight::NORMAL.to_raw() as f32)
57            * FONT_WEIGHT_MULTIPLIER;
58
59        let mut p0 = self.line.p0;
60        let mut p1 = self.line.p1;
61        p0.y += f64::from(offset);
62        p1.y = p0.y - f64::from(width);
63        Rect::from_points(p0, p1)
64    }
65}
66
67/// State for text processing underlines and strikethroughs using [`line-straddler`].
68///
69/// [`line-straddler`]: https://crates.io/crates/line-straddler
70#[derive(Debug)]
71pub struct LineProcessor {
72    /// State for the underline.
73    underline: LineGenerator,
74
75    /// State for the strikethrough.
76    strikethrough: LineGenerator,
77
78    /// The lines to draw.
79    lines: Vec<StyledLine>,
80
81    /// The last glyph size processed.
82    last_glyph_size: f32,
83}
84
85impl Default for LineProcessor {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl LineProcessor {
92    /// Create a new, empty state.
93    pub fn new() -> Self {
94        Self {
95            underline: LineGenerator::new(LineType::Underline),
96            strikethrough: LineGenerator::new(LineType::StrikeThrough),
97            lines: Vec::new(),
98            last_glyph_size: 0.0,
99        }
100    }
101
102    /// Handle a glyph.
103    pub fn handle_glyph(&mut self, glyph: &LayoutGlyph, line_y: f32, color: cosmic_text::Color) {
104        // Get the metadata.
105        let metadata = Metadata::from_raw(glyph.metadata);
106        let font_size = glyph.font_size;
107        let glyph = Glyph {
108            line_y,
109            font_size,
110            width: glyph.w,
111            x: glyph.x,
112            style: GlyphStyle {
113                boldness: metadata.boldness().to_raw(),
114                color: match glyph.color_opt {
115                    Some(color) => {
116                        let [r, g, b, a] = [color.r(), color.g(), color.b(), color.a()];
117                        line_straddler::Color::rgba(r, g, b, a)
118                    }
119
120                    None => {
121                        let [r, g, b, a] = [color.r(), color.g(), color.b(), color.a()];
122                        line_straddler::Color::rgba(r, g, b, a)
123                    }
124                },
125            },
126        };
127
128        let Self {
129            underline,
130            strikethrough,
131            lines,
132            last_glyph_size,
133        } = self;
134
135        let handle_meta = |generator: &mut LineGenerator, has_it| {
136            let line = if has_it {
137                generator.add_glyph(glyph)
138            } else {
139                generator.pop_line()
140            };
141
142            line.map(|line| cvt_line(line, font_size))
143        };
144
145        let underline = handle_meta(underline, metadata.underline());
146        let strikethrough = handle_meta(strikethrough, metadata.strikethrough());
147
148        lines.extend(underline);
149        lines.extend(strikethrough);
150        *last_glyph_size = font_size;
151    }
152
153    /// Take the associated lines.
154    pub fn lines(&mut self) -> Vec<StyledLine> {
155        // Pop the last lines.
156        let underline = self.underline.pop_line();
157        let strikethrough = self.strikethrough.pop_line();
158        let font_size = self.last_glyph_size;
159        self.lines
160            .extend(underline.map(|line| cvt_line(line, font_size)));
161        self.lines
162            .extend(strikethrough.map(|line| cvt_line(line, font_size)));
163
164        mem::take(&mut self.lines)
165    }
166}
167
168fn cvt_line(ls_line: LsLine, font_size: f32) -> StyledLine {
169    let line = Line {
170        p0: Point::new(ls_line.start_x.into(), ls_line.y.into()),
171        p1: Point::new(ls_line.end_x.into(), ls_line.y.into()),
172    };
173
174    StyledLine {
175        line,
176        color: cvt_color(ls_line.style.color),
177        bold: FontWeight::new(ls_line.style.boldness),
178        font_size,
179    }
180}
181
182fn cvt_color(color: line_straddler::Color) -> Color {
183    let [r, g, b, a] = color.components();
184    Color::rgba8(r, g, b, a)
185}