ferroui_controls/
text_layout_rounding_placement.rs1use ferroui_base::ferro_markup_enum;
14use ferroui_base::layout::LayoutHelper;
15
16#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
19#[repr(i32)]
20pub enum TextLayoutRoundingPlacement {
21 #[default]
24 Below,
25 Above,
28}
29
30ferro_markup_enum!(TextLayoutRoundingPlacement { Below, Above }, { namespace: "FerroUI.Controls" });
31
32impl TextLayoutRoundingPlacement {
33 pub fn offset(self, height: f64, scale: f64) -> f64 {
38 match self {
39 Self::Below => 0.0,
40 Self::Above => {
41 if !height.is_finite() || height <= 0.0 || !scale.is_finite() || scale <= 0.0 {
42 return 0.0;
43 }
44 (LayoutHelper::round_layout_value_up(height, scale) - height).max(0.0)
45 }
46 }
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 fn segoe(size: f64) -> (f64, f64) {
56 (size * 2210.0 / 2048.0, size * 2724.0 / 2048.0)
57 }
58
59 fn baseline(placement: TextLayoutRoundingPlacement, size: f64, scale: f64) -> f64 {
62 let (ascent, height) = segoe(size);
63 ((ascent + placement.offset(height, scale)) * scale + 0.5).floor()
64 }
65
66 #[test]
67 fn above_puts_the_baselines_where_winui_draws_them() {
68 let below = TextLayoutRoundingPlacement::Below;
72 let above = TextLayoutRoundingPlacement::Above;
73 assert_eq!((15.0, 15.0), (baseline(below, 14.0, 1.0), baseline(above, 14.0, 1.0)));
74 assert_eq!((30.0, 31.0), (baseline(below, 14.0, 2.0), baseline(above, 14.0, 2.0)));
75 assert_eq!((30.0, 31.0), (baseline(below, 28.0, 1.0), baseline(above, 28.0, 1.0)));
76 assert_eq!((60.0, 61.0), (baseline(below, 28.0, 2.0), baseline(above, 28.0, 2.0)));
77 }
78
79 #[test]
80 fn the_offset_is_what_layout_rounding_adds() {
81 let above = TextLayoutRoundingPlacement::Above;
82 for scale in [1.0, 1.25, 1.5, 2.0] {
83 for height in [18.62, 15.96, 37.24, 42.56] {
84 let offset = above.offset(height, scale);
85 let rounded = ((height * scale).ceil()) / scale;
86 assert!((offset - (rounded - height)).abs() < 1e-9, "{height} at {scale}: {offset}");
87 assert!((0.0..1.0 / scale).contains(&offset), "{height} at {scale}: {offset}");
88 assert_eq!(0.0, TextLayoutRoundingPlacement::Below.offset(height, scale));
89 }
90 }
91 assert!((above.offset(18.62, 1.0) - 0.38).abs() < 1e-9);
92 assert!((above.offset(18.62, 2.0) - 0.38).abs() < 1e-9);
93 assert!((above.offset(18.62, 1.5) - (28.0 / 1.5 - 18.62)).abs() < 1e-9);
95 assert!((above.offset(18.62, 1.25) - (24.0 / 1.25 - 18.62)).abs() < 1e-9);
97 assert_eq!(0.0, above.offset(19.0, 1.0));
98 assert_eq!(0.0, above.offset(0.0, 2.0));
99 assert_eq!(0.0, above.offset(f64::INFINITY, 2.0));
100 assert_eq!(0.0, above.offset(18.62, 0.0));
101 }
102
103 #[test]
104 fn below_is_the_default() {
105 assert_eq!(TextLayoutRoundingPlacement::Below, TextLayoutRoundingPlacement::default());
106 }
107}