1
//! CSS properties for writing modes and clearing.
2
//!
3
//! Key types:
4
//! - [`LayoutWritingMode`] — `writing-mode` (`horizontal-tb`, `vertical-rl`, `vertical-lr`)
5
//! - [`LayoutClear`] — `clear` (`none`, `left`, `right`, `both`)
6
//!
7
//! Parse functions are gated behind the `parser` feature and are consumed
8
//! by the CSS property system in `property.rs`.
9

            
10
use alloc::string::{String, ToString};
11
use crate::corety::AzString;
12

            
13
use crate::props::formatter::PrintAsCssValue;
14

            
15
// --- writing-mode (LayoutWritingMode) ---
16

            
17
// +spec:writing-modes:ec496c - writing-mode property: horizontal-tb, vertical-rl, vertical-lr block flow directions
18
// +spec:writing-modes:fdc4cc - writing-mode property: horizontal-tb | vertical-rl | vertical-lr
19
// +spec:writing-modes:aeb9bb - writing-mode property determines block flow direction
20
/// Represents a `writing-mode` attribute
21
// +spec:writing-modes:a7f174 - line orientation: in vertical-lr the line-over (ascender) side is block-end, not block-start
22
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
23
#[repr(C)]
24
// +spec:block-formatting-context:387117 - writing-mode specifies horizontal/vertical line layout and block progression direction
25
// +spec:block-formatting-context:3815e7 - vertical-rl writing mode supported via VerticalRl variant
26
// +spec:block-formatting-context:9d7cd4 - vertical writing mode support (VerticalRl, VerticalLr)
27
#[derive(Default)]
28
pub enum LayoutWritingMode {
29
    /// Top-to-bottom block flow, left-to-right inline direction (Latin, etc.).
30
    #[default]
31
    HorizontalTb,
32
    /// Right-to-left block flow, top-to-bottom inline direction (CJK vertical).
33
    VerticalRl,
34
    // +spec:writing-modes:f35728 - vertical-lr writing mode for left-to-right block flow (Manchu, Mongolian)
35
    /// Left-to-right block flow, top-to-bottom inline direction (Mongolian).
36
    VerticalLr,
37
}
38

            
39

            
40
impl LayoutWritingMode {
41
    /// Returns true if the writing mode is vertical (VerticalRl or VerticalLr)
42
    pub const fn is_vertical(self) -> bool {
43
        matches!(self, Self::VerticalRl | Self::VerticalLr)
44
    }
45
}
46

            
47
impl core::fmt::Debug for LayoutWritingMode {
48
4956
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
49
4956
        write!(f, "{}", self.print_as_css_value())
50
4956
    }
51
}
52

            
53
impl core::fmt::Display for LayoutWritingMode {
54
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
55
        write!(f, "{}", self.print_as_css_value())
56
    }
57
}
58

            
59
impl PrintAsCssValue for LayoutWritingMode {
60
4959
    fn print_as_css_value(&self) -> String {
61
4959
        match self {
62
4957
            LayoutWritingMode::HorizontalTb => "horizontal-tb".to_string(),
63
1
            LayoutWritingMode::VerticalRl => "vertical-rl".to_string(),
64
1
            LayoutWritingMode::VerticalLr => "vertical-lr".to_string(),
65
        }
66
4959
    }
67
}
68

            
69
#[cfg(feature = "parser")]
70
#[derive(Clone, PartialEq)]
71
pub enum LayoutWritingModeParseError<'a> {
72
    InvalidValue(&'a str),
73
}
74

            
75
#[cfg(feature = "parser")]
76
impl_debug_as_display!(LayoutWritingModeParseError<'a>);
77
#[cfg(feature = "parser")]
78
impl_display! { LayoutWritingModeParseError<'a>, {
79
    InvalidValue(e) => format!("Invalid writing-mode value: \"{}\"", e),
80
}}
81

            
82
#[cfg(feature = "parser")]
83
#[derive(Debug, Clone, PartialEq)]
84
#[repr(C, u8)]
85
pub enum LayoutWritingModeParseErrorOwned {
86
    InvalidValue(AzString),
87
}
88

            
89
#[cfg(feature = "parser")]
90
impl<'a> LayoutWritingModeParseError<'a> {
91
    pub fn to_contained(&self) -> LayoutWritingModeParseErrorOwned {
92
        match self {
93
            LayoutWritingModeParseError::InvalidValue(s) => {
94
                LayoutWritingModeParseErrorOwned::InvalidValue(s.to_string().into())
95
            }
96
        }
97
    }
98
}
99

            
100
#[cfg(feature = "parser")]
101
impl LayoutWritingModeParseErrorOwned {
102
    pub fn to_shared<'a>(&'a self) -> LayoutWritingModeParseError<'a> {
103
        match self {
104
            LayoutWritingModeParseErrorOwned::InvalidValue(s) => {
105
                LayoutWritingModeParseError::InvalidValue(s.as_str())
106
            }
107
        }
108
    }
109
}
110

            
111
#[cfg(feature = "parser")]
112
6
pub fn parse_layout_writing_mode<'a>(
113
6
    input: &'a str,
114
6
) -> Result<LayoutWritingMode, LayoutWritingModeParseError<'a>> {
115
6
    let input = input.trim();
116
6
    match input {
117
6
        "horizontal-tb" => Ok(LayoutWritingMode::HorizontalTb),
118
5
        "vertical-rl" => Ok(LayoutWritingMode::VerticalRl),
119
3
        "vertical-lr" => Ok(LayoutWritingMode::VerticalLr),
120
2
        "tb-lr" => Ok(LayoutWritingMode::VerticalLr), // +spec:writing-modes:23147f - SVG1.1 tb-lr maps to vertical-lr
121
2
        _ => Err(LayoutWritingModeParseError::InvalidValue(input)),
122
    }
123
6
}
124

            
125
// --- clear (LayoutClear) ---
126

            
127
/// Represents a `clear` attribute
128
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
129
#[repr(C)]
130
#[derive(Default)]
131
pub enum LayoutClear {
132
    /// No clearing; element is not moved below preceding floats.
133
    #[default]
134
    None,
135
    /// Element is moved below preceding left floats.
136
    Left,
137
    /// Element is moved below preceding right floats.
138
    Right,
139
    /// Element is moved below all preceding floats.
140
    Both,
141
}
142

            
143

            
144
impl core::fmt::Debug for LayoutClear {
145
18144
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
146
18144
        write!(f, "{}", self.print_as_css_value())
147
18144
    }
148
}
149

            
150
impl core::fmt::Display for LayoutClear {
151
    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
152
        write!(f, "{}", self.print_as_css_value())
153
    }
154
}
155

            
156
impl PrintAsCssValue for LayoutClear {
157
18148
    fn print_as_css_value(&self) -> String {
158
18148
        match self {
159
17767
            LayoutClear::None => "none".to_string(),
160
211
            LayoutClear::Left => "left".to_string(),
161
85
            LayoutClear::Right => "right".to_string(),
162
85
            LayoutClear::Both => "both".to_string(),
163
        }
164
18148
    }
165
}
166

            
167
#[cfg(feature = "parser")]
168
#[derive(Clone, PartialEq)]
169
pub enum LayoutClearParseError<'a> {
170
    InvalidValue(&'a str),
171
}
172

            
173
#[cfg(feature = "parser")]
174
impl_debug_as_display!(LayoutClearParseError<'a>);
175
#[cfg(feature = "parser")]
176
impl_display! { LayoutClearParseError<'a>, {
177
    InvalidValue(e) => format!("Invalid clear value: \"{}\"", e),
178
}}
179

            
180
#[cfg(feature = "parser")]
181
#[derive(Debug, Clone, PartialEq)]
182
#[repr(C, u8)]
183
pub enum LayoutClearParseErrorOwned {
184
    InvalidValue(AzString),
185
}
186

            
187
#[cfg(feature = "parser")]
188
impl<'a> LayoutClearParseError<'a> {
189
    pub fn to_contained(&self) -> LayoutClearParseErrorOwned {
190
        match self {
191
            LayoutClearParseError::InvalidValue(s) => {
192
                LayoutClearParseErrorOwned::InvalidValue(s.to_string().into())
193
            }
194
        }
195
    }
196
}
197

            
198
#[cfg(feature = "parser")]
199
impl LayoutClearParseErrorOwned {
200
    pub fn to_shared<'a>(&'a self) -> LayoutClearParseError<'a> {
201
        match self {
202
            LayoutClearParseErrorOwned::InvalidValue(s) => {
203
                LayoutClearParseError::InvalidValue(s.as_str())
204
            }
205
        }
206
    }
207
}
208

            
209
#[cfg(feature = "parser")]
210
217
pub fn parse_layout_clear<'a>(input: &'a str) -> Result<LayoutClear, LayoutClearParseError<'a>> {
211
217
    let input = input.trim();
212
217
    match input {
213
217
        "none" => Ok(LayoutClear::None),
214
216
        "left" => Ok(LayoutClear::Left),
215
89
        "right" => Ok(LayoutClear::Right),
216
46
        "both" => Ok(LayoutClear::Both),
217
2
        _ => Err(LayoutClearParseError::InvalidValue(input)),
218
    }
219
217
}
220

            
221
#[cfg(all(test, feature = "parser"))]
222
mod tests {
223
    use super::*;
224

            
225
    // LayoutWritingMode tests
226
    #[test]
227
1
    fn test_parse_writing_mode_horizontal_tb() {
228
1
        assert_eq!(
229
1
            parse_layout_writing_mode("horizontal-tb").unwrap(),
230
            LayoutWritingMode::HorizontalTb
231
        );
232
1
    }
233

            
234
    #[test]
235
1
    fn test_parse_writing_mode_vertical_rl() {
236
1
        assert_eq!(
237
1
            parse_layout_writing_mode("vertical-rl").unwrap(),
238
            LayoutWritingMode::VerticalRl
239
        );
240
1
    }
241

            
242
    #[test]
243
1
    fn test_parse_writing_mode_vertical_lr() {
244
1
        assert_eq!(
245
1
            parse_layout_writing_mode("vertical-lr").unwrap(),
246
            LayoutWritingMode::VerticalLr
247
        );
248
1
    }
249

            
250
    #[test]
251
1
    fn test_parse_writing_mode_invalid() {
252
1
        assert!(parse_layout_writing_mode("invalid").is_err());
253
1
        assert!(parse_layout_writing_mode("horizontal").is_err());
254
1
    }
255

            
256
    #[test]
257
1
    fn test_parse_writing_mode_whitespace() {
258
1
        assert_eq!(
259
1
            parse_layout_writing_mode("  vertical-rl  ").unwrap(),
260
            LayoutWritingMode::VerticalRl
261
        );
262
1
    }
263

            
264
    // LayoutClear tests
265
    #[test]
266
1
    fn test_parse_layout_clear_none() {
267
1
        assert_eq!(parse_layout_clear("none").unwrap(), LayoutClear::None);
268
1
    }
269

            
270
    #[test]
271
1
    fn test_parse_layout_clear_left() {
272
1
        assert_eq!(parse_layout_clear("left").unwrap(), LayoutClear::Left);
273
1
    }
274

            
275
    #[test]
276
1
    fn test_parse_layout_clear_right() {
277
1
        assert_eq!(parse_layout_clear("right").unwrap(), LayoutClear::Right);
278
1
    }
279

            
280
    #[test]
281
1
    fn test_parse_layout_clear_both() {
282
1
        assert_eq!(parse_layout_clear("both").unwrap(), LayoutClear::Both);
283
1
    }
284

            
285
    #[test]
286
1
    fn test_parse_layout_clear_invalid() {
287
1
        assert!(parse_layout_clear("invalid").is_err());
288
1
        assert!(parse_layout_clear("all").is_err());
289
1
    }
290

            
291
    #[test]
292
1
    fn test_parse_layout_clear_whitespace() {
293
1
        assert_eq!(parse_layout_clear("  both  ").unwrap(), LayoutClear::Both);
294
1
    }
295

            
296
    // Print tests
297
    #[test]
298
1
    fn test_print_writing_mode() {
299
1
        assert_eq!(
300
1
            LayoutWritingMode::HorizontalTb.print_as_css_value(),
301
            "horizontal-tb"
302
        );
303
1
        assert_eq!(
304
1
            LayoutWritingMode::VerticalRl.print_as_css_value(),
305
            "vertical-rl"
306
        );
307
1
        assert_eq!(
308
1
            LayoutWritingMode::VerticalLr.print_as_css_value(),
309
            "vertical-lr"
310
        );
311
1
    }
312

            
313
    #[test]
314
1
    fn test_print_layout_clear() {
315
1
        assert_eq!(LayoutClear::None.print_as_css_value(), "none");
316
1
        assert_eq!(LayoutClear::Left.print_as_css_value(), "left");
317
1
        assert_eq!(LayoutClear::Right.print_as_css_value(), "right");
318
1
        assert_eq!(LayoutClear::Both.print_as_css_value(), "both");
319
1
    }
320
}