Skip to main content

ferroui_base/media/
text_decoration.rs

1use std::rc::Rc;
2
3use crate::media::text_formatting::{ITextDrawingSink, TextMetrics};
4use crate::media::{
5    DashStyle, GlyphRun, IBrush, IPen, MediaCollection, Pen, PenLineCap, PenLineJoin, TextDecorationLocation,
6    TextDecorationUnit,
7};
8use crate::{
9    ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObject, FerroObjectImpl, FerroProperty, Point,
10    Ref, StyledProperty,
11};
12
13/// Represents a text decoration, which is a visual ornamentation that is
14/// added to text (such as an underline).
15#[repr(C)]
16pub struct TextDecoration {
17    base: FerroObject,
18}
19
20ferro_class!(TextDecoration: FerroObject);
21crate::ferro_class_info!(TextDecoration { new: TextDecoration::new });
22ferro_impl_classes!(TextDecoration: FerroObjectImpl);
23
24crate::ferro_properties! { impl TextDecoration {
25    ferro_property!(
26        /// Defines the `Location` property.
27        pub fn location_property() -> StyledProperty<TextDecorationLocation> {
28            FerroProperty::register::<TextDecoration, _>("Location", TextDecorationLocation::Underline)
29        }
30    );
31
32    ferro_property!(
33        /// Defines the `Stroke` property.
34        pub fn stroke_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
35            FerroProperty::register::<TextDecoration, _>("Stroke", None)
36        }
37    );
38
39    ferro_property!(
40        /// Defines the `StrokeThicknessUnit` property.
41        pub fn stroke_thickness_unit_property() -> StyledProperty<TextDecorationUnit> {
42            FerroProperty::register::<TextDecoration, _>("StrokeThicknessUnit", TextDecorationUnit::FontRecommended)
43        }
44    );
45
46    ferro_property!(
47        /// Defines the `StrokeDashArray` property.
48        pub fn stroke_dash_array_property() -> StyledProperty<Option<MediaCollection<f64>>> {
49            FerroProperty::register::<TextDecoration, _>("StrokeDashArray", None)
50        }
51    );
52
53    ferro_property!(
54        /// Defines the `StrokeDashOffset` property.
55        pub fn stroke_dash_offset_property() -> StyledProperty<f64> {
56            FerroProperty::register::<TextDecoration, _>("StrokeDashOffset", 0.0)
57        }
58    );
59
60    ferro_property!(
61        /// Defines the `StrokeThickness` property.
62        pub fn stroke_thickness_property() -> StyledProperty<f64> {
63            FerroProperty::register::<TextDecoration, _>("StrokeThickness", 1.0)
64        }
65    );
66
67    ferro_property!(
68        /// Defines the `StrokeLineCap` property.
69        pub fn stroke_line_cap_property() -> StyledProperty<PenLineCap> {
70            FerroProperty::register::<TextDecoration, _>("StrokeLineCap", PenLineCap::Flat)
71        }
72    );
73
74    ferro_property!(
75        /// Defines the `StrokeOffset` property.
76        pub fn stroke_offset_property() -> StyledProperty<f64> {
77            FerroProperty::register::<TextDecoration, _>("StrokeOffset", 0.0)
78        }
79    );
80
81    ferro_property!(
82        /// Defines the `StrokeOffsetUnit` property.
83        pub fn stroke_offset_unit_property() -> StyledProperty<TextDecorationUnit> {
84            FerroProperty::register::<TextDecoration, _>("StrokeOffsetUnit", TextDecorationUnit::FontRecommended)
85        }
86    );
87} }
88
89impl TextDecoration {
90    /// Creates the class data.
91    pub fn construct() -> Self {
92        Self { base: FerroObject::construct() }
93    }
94
95    /// Creates a text decoration with default values (an underline).
96    pub fn new() -> Ref<Self> {
97        instantiate(Self::construct())
98    }
99
100    /// Gets the location.
101    pub fn location(&self) -> TextDecorationLocation {
102        self.get_value(Self::location_property())
103    }
104
105    /// Sets the location.
106    pub fn set_location(&self, value: TextDecorationLocation) {
107        self.set_value(Self::location_property(), value)
108    }
109
110    /// Gets the brush that specifies how the text decoration is painted.
111    pub fn stroke(&self) -> Option<Rc<dyn IBrush>> {
112        self.get_value(Self::stroke_property())
113    }
114
115    /// Sets the brush that specifies how the text decoration is painted.
116    pub fn set_stroke(&self, value: Option<Rc<dyn IBrush>>) {
117        self.set_value(Self::stroke_property(), value)
118    }
119
120    /// Gets the units in which the thickness of the text decoration is expressed.
121    pub fn stroke_thickness_unit(&self) -> TextDecorationUnit {
122        self.get_value(Self::stroke_thickness_unit_property())
123    }
124
125    /// Sets the units in which the thickness of the text decoration is expressed.
126    pub fn set_stroke_thickness_unit(&self, value: TextDecorationUnit) {
127        self.set_value(Self::stroke_thickness_unit_property(), value)
128    }
129
130    /// Gets a collection of values that indicate the pattern of dashes and
131    /// gaps that is used to draw the text decoration.
132    pub fn stroke_dash_array(&self) -> Option<MediaCollection<f64>> {
133        self.get_value(Self::stroke_dash_array_property())
134    }
135
136    /// Sets a collection of values that indicate the pattern of dashes and
137    /// gaps that is used to draw the text decoration.
138    pub fn set_stroke_dash_array(&self, value: Option<MediaCollection<f64>>) {
139        self.set_value(Self::stroke_dash_array_property(), value)
140    }
141
142    /// Gets a value that specifies the distance within the dash pattern where a dash begins.
143    pub fn stroke_dash_offset(&self) -> f64 {
144        self.get_value(Self::stroke_dash_offset_property())
145    }
146
147    /// Sets a value that specifies the distance within the dash pattern where a dash begins.
148    pub fn set_stroke_dash_offset(&self, value: f64) {
149        self.set_value(Self::stroke_dash_offset_property(), value)
150    }
151
152    /// Gets the thickness of the text decoration.
153    pub fn stroke_thickness(&self) -> f64 {
154        self.get_value(Self::stroke_thickness_property())
155    }
156
157    /// Sets the thickness of the text decoration.
158    pub fn set_stroke_thickness(&self, value: f64) {
159        self.set_value(Self::stroke_thickness_property(), value)
160    }
161
162    /// Gets a value that describes the shape at the ends of a line.
163    pub fn stroke_line_cap(&self) -> PenLineCap {
164        self.get_value(Self::stroke_line_cap_property())
165    }
166
167    /// Sets a value that describes the shape at the ends of a line.
168    pub fn set_stroke_line_cap(&self, value: PenLineCap) {
169        self.set_value(Self::stroke_line_cap_property(), value)
170    }
171
172    /// The stroke's offset.
173    pub fn stroke_offset(&self) -> f64 {
174        self.get_value(Self::stroke_offset_property())
175    }
176
177    /// Sets the stroke's offset.
178    pub fn set_stroke_offset(&self, value: f64) {
179        self.set_value(Self::stroke_offset_property(), value)
180    }
181
182    /// Gets the units in which the offset value is expressed.
183    pub fn stroke_offset_unit(&self) -> TextDecorationUnit {
184        self.get_value(Self::stroke_offset_unit_property())
185    }
186
187    /// Sets the units in which the offset value is expressed.
188    pub fn set_stroke_offset_unit(&self, value: TextDecorationUnit) {
189        self.set_value(Self::stroke_offset_unit_property(), value)
190    }
191
192    /// Draws the text decoration at given origin.
193    ///
194    /// * `drawing_context` — the drawing context.
195    /// * `glyph_run` — the decorated run.
196    /// * `text_metrics` — the font metrics of the decorated run.
197    /// * `default_brush` — the default brush that is used to draw the decoration.
198    pub(crate) fn draw(
199        &self,
200        drawing_context: &mut dyn ITextDrawingSink,
201        glyph_run: &GlyphRun,
202        text_metrics: &TextMetrics,
203        default_brush: &Rc<dyn IBrush>,
204    ) {
205        let baseline_origin = glyph_run.baseline_origin();
206        let mut thickness = self.stroke_thickness();
207        let location = self.location();
208
209        match self.stroke_thickness_unit() {
210            TextDecorationUnit::FontRecommended => match location {
211                TextDecorationLocation::Underline => thickness = text_metrics.underline_thickness,
212                TextDecorationLocation::Strikethrough => thickness = text_metrics.strikethrough_thickness,
213                _ => {}
214            },
215            TextDecorationUnit::FontRenderingEmSize => {
216                thickness *= text_metrics.font_rendering_em_size;
217            }
218            TextDecorationUnit::Pixel => {}
219        }
220
221        let mut origin = baseline_origin;
222
223        match location {
224            TextDecorationLocation::Overline => origin.y += text_metrics.ascent,
225            TextDecorationLocation::Strikethrough => origin.y += text_metrics.strikethrough_position,
226            TextDecorationLocation::Underline => origin.y += text_metrics.underline_position,
227            TextDecorationLocation::Baseline => {}
228        }
229
230        match self.stroke_offset_unit() {
231            TextDecorationUnit::FontRenderingEmSize => {
232                origin.y += self.stroke_offset() * text_metrics.font_rendering_em_size;
233            }
234            TextDecorationUnit::Pixel => origin.y += self.stroke_offset(),
235            TextDecorationUnit::FontRecommended => {}
236        }
237
238        let dashes = self.stroke_dash_array().map(|dashes| dashes.to_vec());
239
240        let pen: Rc<dyn IPen> = Pen::with_all(
241            Some(self.stroke().unwrap_or_else(|| default_brush.clone())),
242            thickness,
243            Some(DashStyle::with_dashes(dashes.as_deref(), self.stroke_dash_offset()).into()),
244            self.stroke_line_cap(),
245            PenLineJoin::Miter,
246            10.0,
247        )
248        .into();
249
250        if location != TextDecorationLocation::Strikethrough {
251            let offset_y = glyph_run.baseline_origin().y - origin.y;
252
253            let intersections = glyph_run
254                .get_intersections((thickness * 0.5 - offset_y) as f32, (thickness * 1.5 - offset_y) as f32);
255
256            if !intersections.is_empty() {
257                let mut last = baseline_origin.x;
258                let final_pos = last + glyph_run.bounds().width;
259                let mut end = last;
260
261                let mut points = Vec::new();
262
263                // Math is taken from chrome's source code.
264                for pair in intersections.chunks_exact(2) {
265                    let start = pair[0] as f64 - thickness;
266                    end = pair[1] as f64 + thickness;
267                    if start > last && last + text_metrics.font_rendering_em_size / 12.0 < start {
268                        points.push(last);
269                        points.push(start);
270                    }
271                    last = end;
272                }
273
274                if end < final_pos {
275                    points.push(end);
276                    points.push(final_pos);
277                }
278
279                for pair in points.chunks_exact(2) {
280                    let a = Point::new(pair[0], origin.y);
281                    let b = Point::new(pair[1], origin.y);
282                    drawing_context.draw_line(&pen, a, b);
283                }
284
285                return;
286            }
287        }
288
289        let p1 = origin;
290        let p2 = Point::new(p1.x + glyph_run.metrics().width, p1.y);
291        drawing_context.draw_line(&pen, p1, p2);
292    }
293}