ferroui_base/media/
text_decoration.rs1use 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#[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 pub fn location_property() -> StyledProperty<TextDecorationLocation> {
28 FerroProperty::register::<TextDecoration, _>("Location", TextDecorationLocation::Underline)
29 }
30 );
31
32 ferro_property!(
33 pub fn stroke_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
35 FerroProperty::register::<TextDecoration, _>("Stroke", None)
36 }
37 );
38
39 ferro_property!(
40 pub fn stroke_thickness_unit_property() -> StyledProperty<TextDecorationUnit> {
42 FerroProperty::register::<TextDecoration, _>("StrokeThicknessUnit", TextDecorationUnit::FontRecommended)
43 }
44 );
45
46 ferro_property!(
47 pub fn stroke_dash_array_property() -> StyledProperty<Option<MediaCollection<f64>>> {
49 FerroProperty::register::<TextDecoration, _>("StrokeDashArray", None)
50 }
51 );
52
53 ferro_property!(
54 pub fn stroke_dash_offset_property() -> StyledProperty<f64> {
56 FerroProperty::register::<TextDecoration, _>("StrokeDashOffset", 0.0)
57 }
58 );
59
60 ferro_property!(
61 pub fn stroke_thickness_property() -> StyledProperty<f64> {
63 FerroProperty::register::<TextDecoration, _>("StrokeThickness", 1.0)
64 }
65 );
66
67 ferro_property!(
68 pub fn stroke_line_cap_property() -> StyledProperty<PenLineCap> {
70 FerroProperty::register::<TextDecoration, _>("StrokeLineCap", PenLineCap::Flat)
71 }
72 );
73
74 ferro_property!(
75 pub fn stroke_offset_property() -> StyledProperty<f64> {
77 FerroProperty::register::<TextDecoration, _>("StrokeOffset", 0.0)
78 }
79 );
80
81 ferro_property!(
82 pub fn stroke_offset_unit_property() -> StyledProperty<TextDecorationUnit> {
84 FerroProperty::register::<TextDecoration, _>("StrokeOffsetUnit", TextDecorationUnit::FontRecommended)
85 }
86 );
87} }
88
89impl TextDecoration {
90 pub fn construct() -> Self {
92 Self { base: FerroObject::construct() }
93 }
94
95 pub fn new() -> Ref<Self> {
97 instantiate(Self::construct())
98 }
99
100 pub fn location(&self) -> TextDecorationLocation {
102 self.get_value(Self::location_property())
103 }
104
105 pub fn set_location(&self, value: TextDecorationLocation) {
107 self.set_value(Self::location_property(), value)
108 }
109
110 pub fn stroke(&self) -> Option<Rc<dyn IBrush>> {
112 self.get_value(Self::stroke_property())
113 }
114
115 pub fn set_stroke(&self, value: Option<Rc<dyn IBrush>>) {
117 self.set_value(Self::stroke_property(), value)
118 }
119
120 pub fn stroke_thickness_unit(&self) -> TextDecorationUnit {
122 self.get_value(Self::stroke_thickness_unit_property())
123 }
124
125 pub fn set_stroke_thickness_unit(&self, value: TextDecorationUnit) {
127 self.set_value(Self::stroke_thickness_unit_property(), value)
128 }
129
130 pub fn stroke_dash_array(&self) -> Option<MediaCollection<f64>> {
133 self.get_value(Self::stroke_dash_array_property())
134 }
135
136 pub fn set_stroke_dash_array(&self, value: Option<MediaCollection<f64>>) {
139 self.set_value(Self::stroke_dash_array_property(), value)
140 }
141
142 pub fn stroke_dash_offset(&self) -> f64 {
144 self.get_value(Self::stroke_dash_offset_property())
145 }
146
147 pub fn set_stroke_dash_offset(&self, value: f64) {
149 self.set_value(Self::stroke_dash_offset_property(), value)
150 }
151
152 pub fn stroke_thickness(&self) -> f64 {
154 self.get_value(Self::stroke_thickness_property())
155 }
156
157 pub fn set_stroke_thickness(&self, value: f64) {
159 self.set_value(Self::stroke_thickness_property(), value)
160 }
161
162 pub fn stroke_line_cap(&self) -> PenLineCap {
164 self.get_value(Self::stroke_line_cap_property())
165 }
166
167 pub fn set_stroke_line_cap(&self, value: PenLineCap) {
169 self.set_value(Self::stroke_line_cap_property(), value)
170 }
171
172 pub fn stroke_offset(&self) -> f64 {
174 self.get_value(Self::stroke_offset_property())
175 }
176
177 pub fn set_stroke_offset(&self, value: f64) {
179 self.set_value(Self::stroke_offset_property(), value)
180 }
181
182 pub fn stroke_offset_unit(&self) -> TextDecorationUnit {
184 self.get_value(Self::stroke_offset_unit_property())
185 }
186
187 pub fn set_stroke_offset_unit(&self, value: TextDecorationUnit) {
189 self.set_value(Self::stroke_offset_unit_property(), value)
190 }
191
192 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 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}