1
//! CSS properties for `display` and `float`.
2

            
3
use alloc::string::{String, ToString};
4
use crate::corety::AzString;
5

            
6
use crate::props::formatter::PrintAsCssValue;
7

            
8
/// Represents a `display` CSS property value
9
// +spec:display-property:472a62 - display property controls box generation types per CSS 2.2 §9.2
10
// +spec:display-property:cf1820 - display type enum defining box generation qualities
11
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12
#[repr(C)]
13
pub enum LayoutDisplay {
14
    // Basic display types
15
    None,
16
    // +spec:display-property:7d945d - outer display defaults to block, inner defaults to flow
17
    #[default]
18
    Block,
19
    Inline,
20
    InlineBlock,
21

            
22
    // Flex layout
23
    Flex,
24
    InlineFlex,
25

            
26
    // +spec:display-property:03b26a - Table display types mapping document elements to CSS table model
27
    // +spec:display-property:d40388 - layout-internal display types set both inner and outer display
28
    // +spec:display-property:dcf7f5 - table display values (table, inline-table, table-row, etc.) per CSS 2.2 §17
29
    // +spec:table-layout:7fdc60 - display property maps elements to table roles (CSS 2.2 §17.1)
30
    // Table layout
31
    // +spec:display-property:1554ad - Layout-internal display types for table layout
32
    // +spec:table-layout:6cc828 - <display-internal> and <display-legacy> table display types
33
    Table,
34
    InlineTable,
35
    TableRowGroup,
36
    TableHeaderGroup,
37
    TableFooterGroup,
38
    TableRow,
39
    TableColumnGroup,
40
    TableColumn,
41
    TableCell,
42
    TableCaption,
43

            
44
    FlowRoot,
45

            
46
    // List layout
47
    ListItem,
48

            
49
    // Special displays
50
    RunIn,
51
    Marker,
52

            
53
    // CSS3 additions
54
    Grid,
55
    InlineGrid,
56

            
57
    // display:contents - element generates no box, children promoted to parent
58
    Contents,
59
}
60

            
61
impl LayoutDisplay {
62
    /// Returns true if this display type establishes a block formatting context.
63
3010
    pub fn creates_block_context(&self) -> bool {
64
        matches!(
65
3010
            self,
66
            LayoutDisplay::Block
67
                | LayoutDisplay::FlowRoot
68
                | LayoutDisplay::Flex
69
                | LayoutDisplay::Grid
70
                | LayoutDisplay::Table
71
                | LayoutDisplay::ListItem
72
        )
73
3010
    }
74

            
75
    /// Returns true if this display type establishes a flex formatting context.
76
    pub fn creates_flex_context(&self) -> bool {
77
        matches!(self, LayoutDisplay::Flex | LayoutDisplay::InlineFlex)
78
    }
79

            
80
    // +spec:display-property:798b4f - table box establishes table formatting context (CSS 2.2 §17.4)
81
    /// Returns true if this display type establishes a table formatting context.
82
140
    pub fn creates_table_context(&self) -> bool {
83
140
        matches!(self, LayoutDisplay::Table | LayoutDisplay::InlineTable)
84
140
    }
85

            
86
    /// Returns true for layout-internal display types (CSS Display 3 §2.4):
87
    /// table-row-group, table-header-group, table-footer-group, table-row,
88
    /// table-column-group, table-column, table-cell, table-caption.
89
9450
    pub fn is_layout_internal(&self) -> bool {
90
9450
        matches!(
91
9450
            self,
92
            LayoutDisplay::TableRowGroup
93
                | LayoutDisplay::TableHeaderGroup
94
                | LayoutDisplay::TableFooterGroup
95
                | LayoutDisplay::TableRow
96
                | LayoutDisplay::TableColumnGroup
97
                | LayoutDisplay::TableColumn
98
                | LayoutDisplay::TableCell
99
                | LayoutDisplay::TableCaption
100
        )
101
9450
    }
102

            
103
    // +spec:display-property:101f27 - inline-level boxes (InlineBlock, InlineFlex, etc.) vs inline boxes (Inline)
104
    // +spec:display-property:18e77e - inner-only display keywords (flex, grid, table, flow-root) are not inline-level, defaulting outer display to block
105
    // +spec:display-property:a43e48 - inline-table is inline-level per CSS 2.2 §17.4
106
    /// Returns true if this display type generates an inline-level box.
107
    pub fn is_inline_level(&self) -> bool {
108
        matches!(
109
            self,
110
            LayoutDisplay::Inline
111
                | LayoutDisplay::InlineBlock
112
                | LayoutDisplay::InlineFlex
113
                | LayoutDisplay::InlineTable
114
                | LayoutDisplay::InlineGrid
115
        )
116
    }
117
}
118

            
119
// +spec:display-property:cabaec - serialization uses short display keywords per CSSOM precedence rules
120
impl PrintAsCssValue for LayoutDisplay {
121
    fn print_as_css_value(&self) -> String {
122
        String::from(match self {
123
            LayoutDisplay::None => "none",
124
            LayoutDisplay::Block => "block",
125
            LayoutDisplay::Inline => "inline",
126
            LayoutDisplay::InlineBlock => "inline-block",
127
            LayoutDisplay::Flex => "flex",
128
            LayoutDisplay::InlineFlex => "inline-flex",
129
            LayoutDisplay::Table => "table",
130
            LayoutDisplay::InlineTable => "inline-table",
131
            LayoutDisplay::TableRowGroup => "table-row-group",
132
            LayoutDisplay::TableHeaderGroup => "table-header-group",
133
            LayoutDisplay::TableFooterGroup => "table-footer-group",
134
            LayoutDisplay::TableRow => "table-row",
135
            LayoutDisplay::TableColumnGroup => "table-column-group",
136
            LayoutDisplay::TableColumn => "table-column",
137
            LayoutDisplay::TableCell => "table-cell",
138
            LayoutDisplay::TableCaption => "table-caption",
139
            LayoutDisplay::ListItem => "list-item",
140
            LayoutDisplay::RunIn => "run-in",
141
            LayoutDisplay::Marker => "marker",
142
            LayoutDisplay::FlowRoot => "flow-root",
143
            LayoutDisplay::Grid => "grid",
144
            LayoutDisplay::InlineGrid => "inline-grid",
145
            LayoutDisplay::Contents => "contents",
146
        })
147
    }
148
}
149

            
150
/// Represents a `float` attribute
151
#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
152
#[repr(C)]
153
pub enum LayoutFloat {
154
    Left,
155
    Right,
156
    #[default]
157
    None,
158
}
159

            
160
impl PrintAsCssValue for LayoutFloat {
161
    fn print_as_css_value(&self) -> String {
162
        String::from(match self {
163
            LayoutFloat::Left => "left",
164
            LayoutFloat::Right => "right",
165
            LayoutFloat::None => "none",
166
        })
167
    }
168
}
169

            
170
// --- PARSERS ---
171

            
172
#[cfg(feature = "parser")]
173
#[derive(Clone, PartialEq)]
174
pub enum LayoutDisplayParseError<'a> {
175
    InvalidValue(&'a str),
176
}
177

            
178
#[cfg(feature = "parser")]
179
impl_debug_as_display!(LayoutDisplayParseError<'a>);
180

            
181
#[cfg(feature = "parser")]
182
impl_display! { LayoutDisplayParseError<'a>, {
183
    InvalidValue(val) => format!("Invalid display value: \"{}\"", val),
184
}}
185

            
186
#[cfg(feature = "parser")]
187
#[derive(Debug, Clone, PartialEq)]
188
#[repr(C, u8)]
189
pub enum LayoutDisplayParseErrorOwned {
190
    InvalidValue(AzString),
191
}
192

            
193
#[cfg(feature = "parser")]
194
impl<'a> LayoutDisplayParseError<'a> {
195
    pub fn to_contained(&self) -> LayoutDisplayParseErrorOwned {
196
        match self {
197
            Self::InvalidValue(s) => LayoutDisplayParseErrorOwned::InvalidValue(s.to_string().into()),
198
        }
199
    }
200
}
201

            
202
#[cfg(feature = "parser")]
203
impl LayoutDisplayParseErrorOwned {
204
    pub fn to_shared<'a>(&'a self) -> LayoutDisplayParseError<'a> {
205
        match self {
206
            Self::InvalidValue(s) => LayoutDisplayParseError::InvalidValue(s.as_str()),
207
        }
208
    }
209
}
210

            
211
#[cfg(feature = "parser")]
212
809
pub fn parse_layout_display<'a>(
213
809
    input: &'a str,
214
809
) -> Result<LayoutDisplay, LayoutDisplayParseError<'a>> {
215
809
    let input = input.trim();
216
809
    match input {
217
809
        "none" => Ok(LayoutDisplay::None),
218
766
        "block" => Ok(LayoutDisplay::Block),
219
739
        "inline" => Ok(LayoutDisplay::Inline),
220
        // +spec:display-property:f704ef - legacy single-keyword inline-level display values (inline-block, inline-table, inline-flex, inline-grid)
221
738
        "inline-block" => Ok(LayoutDisplay::InlineBlock),
222
653
        "flex" => Ok(LayoutDisplay::Flex),
223
210
        "inline-flex" => Ok(LayoutDisplay::InlineFlex),
224
208
        "table" => Ok(LayoutDisplay::Table),
225
165
        "inline-table" => Ok(LayoutDisplay::InlineTable),
226
164
        "table-row-group" => Ok(LayoutDisplay::TableRowGroup),
227
163
        "table-header-group" => Ok(LayoutDisplay::TableHeaderGroup),
228
162
        "table-footer-group" => Ok(LayoutDisplay::TableFooterGroup),
229
161
        "table-row" => Ok(LayoutDisplay::TableRow),
230
118
        "table-column-group" => Ok(LayoutDisplay::TableColumnGroup),
231
117
        "table-column" => Ok(LayoutDisplay::TableColumn),
232
117
        "table-cell" => Ok(LayoutDisplay::TableCell),
233
32
        "table-caption" => Ok(LayoutDisplay::TableCaption),
234
31
        "list-item" => Ok(LayoutDisplay::ListItem),
235
30
        "run-in" => Ok(LayoutDisplay::RunIn),
236
30
        "marker" => Ok(LayoutDisplay::Marker),
237
30
        "grid" => Ok(LayoutDisplay::Grid),
238
7
        "inline-grid" => Ok(LayoutDisplay::InlineGrid),
239
6
        "flow-root" => Ok(LayoutDisplay::FlowRoot),
240
5
        "contents" => Ok(LayoutDisplay::Contents),
241
5
        _ => Err(LayoutDisplayParseError::InvalidValue(input)),
242
    }
243
809
}
244

            
245
#[cfg(feature = "parser")]
246
#[derive(Clone, PartialEq)]
247
pub enum LayoutFloatParseError<'a> {
248
    InvalidValue(&'a str),
249
}
250

            
251
#[cfg(feature = "parser")]
252
impl_debug_as_display!(LayoutFloatParseError<'a>);
253

            
254
#[cfg(feature = "parser")]
255
impl_display! { LayoutFloatParseError<'a>, {
256
    InvalidValue(val) => format!("Invalid float value: \"{}\"", val),
257
}}
258

            
259
#[cfg(feature = "parser")]
260
#[derive(Debug, Clone, PartialEq)]
261
#[repr(C, u8)]
262
pub enum LayoutFloatParseErrorOwned {
263
    InvalidValue(AzString),
264
}
265

            
266
#[cfg(feature = "parser")]
267
impl<'a> LayoutFloatParseError<'a> {
268
    pub fn to_contained(&self) -> LayoutFloatParseErrorOwned {
269
        match self {
270
            Self::InvalidValue(s) => LayoutFloatParseErrorOwned::InvalidValue(s.to_string().into()),
271
        }
272
    }
273
}
274

            
275
#[cfg(feature = "parser")]
276
impl LayoutFloatParseErrorOwned {
277
    pub fn to_shared<'a>(&'a self) -> LayoutFloatParseError<'a> {
278
        match self {
279
            Self::InvalidValue(s) => LayoutFloatParseError::InvalidValue(s.as_str()),
280
        }
281
    }
282
}
283

            
284
#[cfg(feature = "parser")]
285
1141
pub fn parse_layout_float<'a>(input: &'a str) -> Result<LayoutFloat, LayoutFloatParseError<'a>> {
286
1141
    let input = input.trim();
287
1141
    match input {
288
1141
        "left" => Ok(LayoutFloat::Left),
289
384
        "right" => Ok(LayoutFloat::Right),
290
46
        "none" => Ok(LayoutFloat::None),
291
3
        _ => Err(LayoutFloatParseError::InvalidValue(input)),
292
    }
293
1141
}
294

            
295
#[cfg(all(test, feature = "parser"))]
296
mod tests {
297
    use super::*;
298

            
299
    #[test]
300
1
    fn test_parse_layout_display() {
301
1
        assert_eq!(parse_layout_display("block").unwrap(), LayoutDisplay::Block);
302
1
        assert_eq!(
303
1
            parse_layout_display("inline").unwrap(),
304
            LayoutDisplay::Inline
305
        );
306
1
        assert_eq!(
307
1
            parse_layout_display("inline-block").unwrap(),
308
            LayoutDisplay::InlineBlock
309
        );
310
1
        assert_eq!(parse_layout_display("flex").unwrap(), LayoutDisplay::Flex);
311
1
        assert_eq!(
312
1
            parse_layout_display("inline-flex").unwrap(),
313
            LayoutDisplay::InlineFlex
314
        );
315
1
        assert_eq!(parse_layout_display("grid").unwrap(), LayoutDisplay::Grid);
316
1
        assert_eq!(
317
1
            parse_layout_display("inline-grid").unwrap(),
318
            LayoutDisplay::InlineGrid
319
        );
320
1
        assert_eq!(parse_layout_display("none").unwrap(), LayoutDisplay::None);
321
1
        assert_eq!(
322
1
            parse_layout_display("flow-root").unwrap(),
323
            LayoutDisplay::FlowRoot
324
        );
325
1
        assert_eq!(
326
1
            parse_layout_display("list-item").unwrap(),
327
            LayoutDisplay::ListItem
328
        );
329
        // Note: 'inherit' and 'initial' are handled by the CSS cascade system,
330
        // not as enum variants
331
1
        assert!(parse_layout_display("inherit").is_err());
332
1
        assert!(parse_layout_display("initial").is_err());
333

            
334
        // Table values
335
1
        assert_eq!(parse_layout_display("table").unwrap(), LayoutDisplay::Table);
336
1
        assert_eq!(
337
1
            parse_layout_display("inline-table").unwrap(),
338
            LayoutDisplay::InlineTable
339
        );
340
1
        assert_eq!(
341
1
            parse_layout_display("table-row").unwrap(),
342
            LayoutDisplay::TableRow
343
        );
344
1
        assert_eq!(
345
1
            parse_layout_display("table-cell").unwrap(),
346
            LayoutDisplay::TableCell
347
        );
348
1
        assert_eq!(
349
1
            parse_layout_display("table-caption").unwrap(),
350
            LayoutDisplay::TableCaption
351
        );
352
1
        assert_eq!(
353
1
            parse_layout_display("table-column-group").unwrap(),
354
            LayoutDisplay::TableColumnGroup
355
        );
356
1
        assert_eq!(
357
1
            parse_layout_display("table-header-group").unwrap(),
358
            LayoutDisplay::TableHeaderGroup
359
        );
360
1
        assert_eq!(
361
1
            parse_layout_display("table-footer-group").unwrap(),
362
            LayoutDisplay::TableFooterGroup
363
        );
364
1
        assert_eq!(
365
1
            parse_layout_display("table-row-group").unwrap(),
366
            LayoutDisplay::TableRowGroup
367
        );
368

            
369
        // Whitespace
370
1
        assert_eq!(
371
1
            parse_layout_display("  inline-flex  ").unwrap(),
372
            LayoutDisplay::InlineFlex
373
        );
374

            
375
        // Invalid values
376
1
        assert!(parse_layout_display("invalid-value").is_err());
377
1
        assert!(parse_layout_display("").is_err());
378
1
        assert!(parse_layout_display("display").is_err());
379
1
    }
380

            
381
    #[test]
382
1
    fn test_parse_layout_float() {
383
1
        assert_eq!(parse_layout_float("left").unwrap(), LayoutFloat::Left);
384
1
        assert_eq!(parse_layout_float("right").unwrap(), LayoutFloat::Right);
385
1
        assert_eq!(parse_layout_float("none").unwrap(), LayoutFloat::None);
386

            
387
        // Whitespace
388
1
        assert_eq!(parse_layout_float("  right  ").unwrap(), LayoutFloat::Right);
389

            
390
        // Invalid values
391
1
        assert!(parse_layout_float("center").is_err());
392
1
        assert!(parse_layout_float("").is_err());
393
1
        assert!(parse_layout_float("float-left").is_err());
394
1
    }
395
}