1
//! CSS properties for controlling fragmentation (page/column breaks).
2
//!
3
//! Defines [`PageBreak`], [`BreakInside`], [`Widows`], [`Orphans`], and
4
//! [`BoxDecorationBreak`]. The `parser` sub-module (behind the `parser`
5
//! feature) provides CSS-value parsing for each type.
6

            
7
use alloc::string::{String, ToString};
8

            
9
use crate::props::formatter::PrintAsCssValue;
10

            
11
// --- break-before / break-after ---
12

            
13
/// Represents a `break-before` or `break-after` CSS property value.
14
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15
#[repr(C)]
16
#[derive(Default)]
17
pub enum PageBreak {
18
    #[default]
19
    Auto,
20
    Avoid,
21
    Always,
22
    All,
23
    Page,
24
    AvoidPage,
25
    Left,
26
    Right,
27
    Recto,
28
    Verso,
29
    Column,
30
    AvoidColumn,
31
}
32

            
33

            
34
impl PrintAsCssValue for PageBreak {
35
    fn print_as_css_value(&self) -> String {
36
        String::from(match self {
37
            Self::Auto => "auto",
38
            Self::Avoid => "avoid",
39
            Self::Always => "always",
40
            Self::All => "all",
41
            Self::Page => "page",
42
            Self::AvoidPage => "avoid-page",
43
            Self::Left => "left",
44
            Self::Right => "right",
45
            Self::Recto => "recto",
46
            Self::Verso => "verso",
47
            Self::Column => "column",
48
            Self::AvoidColumn => "avoid-column",
49
        })
50
    }
51
}
52

            
53
// --- break-inside ---
54

            
55
/// Represents a `break-inside` CSS property value.
56
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
57
#[repr(C)]
58
#[derive(Default)]
59
pub enum BreakInside {
60
    #[default]
61
    Auto,
62
    Avoid,
63
    AvoidPage,
64
    AvoidColumn,
65
}
66

            
67

            
68
impl PrintAsCssValue for BreakInside {
69
    fn print_as_css_value(&self) -> String {
70
        String::from(match self {
71
            Self::Auto => "auto",
72
            Self::Avoid => "avoid",
73
            Self::AvoidPage => "avoid-page",
74
            Self::AvoidColumn => "avoid-column",
75
        })
76
    }
77
}
78

            
79
// --- widows / orphans ---
80

            
81
/// CSS `widows` property - minimum number of lines in a block container
82
/// that must be shown at the top of a page, region, or column.
83
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
84
#[repr(C)]
85
pub struct Widows {
86
    pub inner: u32,
87
}
88

            
89
impl Default for Widows {
90
    fn default() -> Self {
91
        Self { inner: 2 }
92
    }
93
}
94

            
95
impl PrintAsCssValue for Widows {
96
    fn print_as_css_value(&self) -> String {
97
        self.inner.to_string()
98
    }
99
}
100

            
101
/// CSS `orphans` property - minimum number of lines in a block container
102
/// that must be shown at the bottom of a page, region, or column.
103
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
104
#[repr(C)]
105
pub struct Orphans {
106
    pub inner: u32,
107
}
108

            
109
impl Default for Orphans {
110
    fn default() -> Self {
111
        Self { inner: 2 }
112
    }
113
}
114

            
115
impl PrintAsCssValue for Orphans {
116
    fn print_as_css_value(&self) -> String {
117
        self.inner.to_string()
118
    }
119
}
120

            
121
// --- box-decoration-break ---
122

            
123
/// Represents a `box-decoration-break` CSS property value.
124
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
125
#[repr(C)]
126
#[derive(Default)]
127
pub enum BoxDecorationBreak {
128
    #[default]
129
    Slice,
130
    Clone,
131
}
132

            
133

            
134
impl PrintAsCssValue for BoxDecorationBreak {
135
    fn print_as_css_value(&self) -> String {
136
        String::from(match self {
137
            Self::Slice => "slice",
138
            Self::Clone => "clone",
139
        })
140
    }
141
}
142

            
143
// Formatting to Rust code
144
impl crate::format_rust_code::FormatAsRustCode for PageBreak {
145
    fn format_as_rust_code(&self, _tabs: usize) -> String {
146
        match self {
147
            PageBreak::Auto => String::from("PageBreak::Auto"),
148
            PageBreak::Avoid => String::from("PageBreak::Avoid"),
149
            PageBreak::Always => String::from("PageBreak::Always"),
150
            PageBreak::All => String::from("PageBreak::All"),
151
            PageBreak::Page => String::from("PageBreak::Page"),
152
            PageBreak::AvoidPage => String::from("PageBreak::AvoidPage"),
153
            PageBreak::Left => String::from("PageBreak::Left"),
154
            PageBreak::Right => String::from("PageBreak::Right"),
155
            PageBreak::Recto => String::from("PageBreak::Recto"),
156
            PageBreak::Verso => String::from("PageBreak::Verso"),
157
            PageBreak::Column => String::from("PageBreak::Column"),
158
            PageBreak::AvoidColumn => String::from("PageBreak::AvoidColumn"),
159
        }
160
    }
161
}
162

            
163
impl crate::format_rust_code::FormatAsRustCode for BreakInside {
164
    fn format_as_rust_code(&self, _tabs: usize) -> String {
165
        match self {
166
            BreakInside::Auto => String::from("BreakInside::Auto"),
167
            BreakInside::Avoid => String::from("BreakInside::Avoid"),
168
            BreakInside::AvoidPage => String::from("BreakInside::AvoidPage"),
169
            BreakInside::AvoidColumn => String::from("BreakInside::AvoidColumn"),
170
        }
171
    }
172
}
173

            
174
impl crate::format_rust_code::FormatAsRustCode for Widows {
175
    fn format_as_rust_code(&self, _tabs: usize) -> String {
176
        format!("Widows {{ inner: {} }}", self.inner)
177
    }
178
}
179

            
180
impl crate::format_rust_code::FormatAsRustCode for Orphans {
181
    fn format_as_rust_code(&self, _tabs: usize) -> String {
182
        format!("Orphans {{ inner: {} }}", self.inner)
183
    }
184
}
185

            
186
impl crate::format_rust_code::FormatAsRustCode for BoxDecorationBreak {
187
    fn format_as_rust_code(&self, _tabs: usize) -> String {
188
        match self {
189
            BoxDecorationBreak::Slice => String::from("BoxDecorationBreak::Slice"),
190
            BoxDecorationBreak::Clone => String::from("BoxDecorationBreak::Clone"),
191
        }
192
    }
193
}
194

            
195
// --- PARSERS ---
196

            
197
#[cfg(feature = "parser")]
198
pub mod parser {
199
    use super::*;
200
    use core::num::ParseIntError;
201
    use crate::corety::AzString;
202
    use crate::props::layout::position::ParseIntErrorWithInput;
203

            
204
    // -- PageBreak parser (`break-before`, `break-after`)
205

            
206
    /// Error returned when parsing a `break-before` or `break-after` value.
207
    #[derive(Clone, PartialEq)]
208
    pub enum PageBreakParseError<'a> {
209
        InvalidValue(&'a str),
210
    }
211

            
212
    impl_debug_as_display!(PageBreakParseError<'a>);
213
    impl_display! { PageBreakParseError<'a>, {
214
        InvalidValue(v) => format!("Invalid break value: \"{}\"", v),
215
    }}
216

            
217
    /// Owned version of [`PageBreakParseError`] for FFI and storage.
218
    #[derive(Debug, Clone, PartialEq)]
219
    #[repr(C, u8)]
220
    pub enum PageBreakParseErrorOwned {
221
        InvalidValue(AzString),
222
    }
223

            
224
    impl<'a> PageBreakParseError<'a> {
225
        pub fn to_contained(&self) -> PageBreakParseErrorOwned {
226
            match self {
227
                Self::InvalidValue(s) => PageBreakParseErrorOwned::InvalidValue(s.to_string().into()),
228
            }
229
        }
230
    }
231

            
232
    impl PageBreakParseErrorOwned {
233
        pub fn to_shared<'a>(&'a self) -> PageBreakParseError<'a> {
234
            match self {
235
                Self::InvalidValue(s) => PageBreakParseError::InvalidValue(s.as_str()),
236
            }
237
        }
238
    }
239

            
240
4
    pub fn parse_page_break<'a>(input: &'a str) -> Result<PageBreak, PageBreakParseError<'a>> {
241
4
        match input.trim() {
242
4
            "auto" => Ok(PageBreak::Auto),
243
3
            "avoid" => Ok(PageBreak::Avoid),
244
3
            "always" => Ok(PageBreak::Always),
245
3
            "all" => Ok(PageBreak::All),
246
3
            "page" => Ok(PageBreak::Page),
247
2
            "avoid-page" => Ok(PageBreak::AvoidPage),
248
2
            "left" => Ok(PageBreak::Left),
249
2
            "right" => Ok(PageBreak::Right),
250
2
            "recto" => Ok(PageBreak::Recto),
251
2
            "verso" => Ok(PageBreak::Verso),
252
2
            "column" => Ok(PageBreak::Column),
253
2
            "avoid-column" => Ok(PageBreak::AvoidColumn),
254
1
            _ => Err(PageBreakParseError::InvalidValue(input)),
255
        }
256
4
    }
257

            
258
    // -- BreakInside parser
259

            
260
    /// Error returned when parsing a `break-inside` value.
261
    #[derive(Clone, PartialEq)]
262
    pub enum BreakInsideParseError<'a> {
263
        InvalidValue(&'a str),
264
    }
265

            
266
    impl_debug_as_display!(BreakInsideParseError<'a>);
267
    impl_display! { BreakInsideParseError<'a>, {
268
        InvalidValue(v) => format!("Invalid break-inside value: \"{}\"", v),
269
    }}
270

            
271
    /// Owned version of [`BreakInsideParseError`] for FFI and storage.
272
    #[derive(Debug, Clone, PartialEq)]
273
    #[repr(C, u8)]
274
    pub enum BreakInsideParseErrorOwned {
275
        InvalidValue(AzString),
276
    }
277

            
278
    impl<'a> BreakInsideParseError<'a> {
279
        pub fn to_contained(&self) -> BreakInsideParseErrorOwned {
280
            match self {
281
                Self::InvalidValue(s) => BreakInsideParseErrorOwned::InvalidValue(s.to_string().into()),
282
            }
283
        }
284
    }
285

            
286
    impl BreakInsideParseErrorOwned {
287
        pub fn to_shared<'a>(&'a self) -> BreakInsideParseError<'a> {
288
            match self {
289
                Self::InvalidValue(s) => BreakInsideParseError::InvalidValue(s.as_str()),
290
            }
291
        }
292
    }
293

            
294
3
    pub fn parse_break_inside<'a>(
295
3
        input: &'a str,
296
3
    ) -> Result<BreakInside, BreakInsideParseError<'a>> {
297
3
        match input.trim() {
298
3
            "auto" => Ok(BreakInside::Auto),
299
2
            "avoid" => Ok(BreakInside::Avoid),
300
1
            "avoid-page" => Ok(BreakInside::AvoidPage),
301
1
            "avoid-column" => Ok(BreakInside::AvoidColumn),
302
1
            _ => Err(BreakInsideParseError::InvalidValue(input)),
303
        }
304
3
    }
305

            
306
    // -- Widows / Orphans parsers
307

            
308
    macro_rules! define_widow_orphan_parser {
309
        ($fn_name:ident, $struct_name:ident, $error_name:ident, $error_owned_name:ident, $prop_name:expr) => {
310
            #[derive(Clone, PartialEq)]
311
            pub enum $error_name<'a> {
312
                ParseInt(ParseIntError, &'a str),
313
                ParseIntOwned(&'a str, &'a str),
314
                NegativeValue(&'a str),
315
            }
316

            
317
            impl_debug_as_display!($error_name<'a>);
318
            impl_display! { $error_name<'a>, {
319
                ParseInt(e, s) => format!("Invalid integer for {}: \"{}\". Reason: {}", $prop_name, s, e),
320
                ParseIntOwned(e, s) => format!("Invalid integer for {}: \"{}\". Reason: {}", $prop_name, s, e),
321
                NegativeValue(s) => format!("Invalid value for {}: \"{}\". Value cannot be negative.", $prop_name, s),
322
            }}
323

            
324
            #[derive(Debug, Clone, PartialEq)]
325
            #[repr(C, u8)]
326
            pub enum $error_owned_name {
327
                ParseInt(ParseIntErrorWithInput),
328
                NegativeValue(AzString),
329
            }
330

            
331
            impl<'a> $error_name<'a> {
332
                pub fn to_contained(&self) -> $error_owned_name {
333
                    match self {
334
                        Self::ParseInt(e, s) => $error_owned_name::ParseInt(ParseIntErrorWithInput { error: e.to_string().into(), input: s.to_string().into() }),
335
                        Self::ParseIntOwned(e, s) => $error_owned_name::ParseInt(ParseIntErrorWithInput { error: e.to_string().into(), input: s.to_string().into() }),
336
                        Self::NegativeValue(s) => $error_owned_name::NegativeValue(s.to_string().into()),
337
                    }
338
                }
339
            }
340

            
341
            impl $error_owned_name {
342
                pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
343
                     match self {
344
                        Self::ParseInt(e) => $error_name::ParseIntOwned(e.error.as_str(), e.input.as_str()),
345
                        Self::NegativeValue(s) => $error_name::NegativeValue(s),
346
                    }
347
                }
348
            }
349

            
350
4
            pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
351
4
                let trimmed = input.trim();
352
4
                let val: i32 = trimmed.parse().map_err(|e| $error_name::ParseInt(e, trimmed))?;
353
3
                if val < 0 {
354
1
                    return Err($error_name::NegativeValue(trimmed));
355
2
                }
356
2
                Ok($struct_name { inner: val as u32 })
357
4
            }
358
        };
359
    }
360

            
361
    define_widow_orphan_parser!(
362
        parse_widows,
363
        Widows,
364
        WidowsParseError,
365
        WidowsParseErrorOwned,
366
        "widows"
367
    );
368
    define_widow_orphan_parser!(
369
        parse_orphans,
370
        Orphans,
371
        OrphansParseError,
372
        OrphansParseErrorOwned,
373
        "orphans"
374
    );
375

            
376
    // -- BoxDecorationBreak parser
377

            
378
    /// Error returned when parsing a `box-decoration-break` value.
379
    #[derive(Clone, PartialEq)]
380
    pub enum BoxDecorationBreakParseError<'a> {
381
        InvalidValue(&'a str),
382
    }
383

            
384
    impl_debug_as_display!(BoxDecorationBreakParseError<'a>);
385
    impl_display! { BoxDecorationBreakParseError<'a>, {
386
        InvalidValue(v) => format!("Invalid box-decoration-break value: \"{}\"", v),
387
    }}
388

            
389
    /// Owned version of [`BoxDecorationBreakParseError`] for FFI and storage.
390
    #[derive(Debug, Clone, PartialEq)]
391
    #[repr(C, u8)]
392
    pub enum BoxDecorationBreakParseErrorOwned {
393
        InvalidValue(AzString),
394
    }
395

            
396
    impl<'a> BoxDecorationBreakParseError<'a> {
397
        pub fn to_contained(&self) -> BoxDecorationBreakParseErrorOwned {
398
            match self {
399
                Self::InvalidValue(s) => {
400
                    BoxDecorationBreakParseErrorOwned::InvalidValue(s.to_string().into())
401
                }
402
            }
403
        }
404
    }
405

            
406
    impl BoxDecorationBreakParseErrorOwned {
407
        pub fn to_shared<'a>(&'a self) -> BoxDecorationBreakParseError<'a> {
408
            match self {
409
                Self::InvalidValue(s) => BoxDecorationBreakParseError::InvalidValue(s.as_str()),
410
            }
411
        }
412
    }
413

            
414
3
    pub fn parse_box_decoration_break<'a>(
415
3
        input: &'a str,
416
3
    ) -> Result<BoxDecorationBreak, BoxDecorationBreakParseError<'a>> {
417
3
        match input.trim() {
418
3
            "slice" => Ok(BoxDecorationBreak::Slice),
419
2
            "clone" => Ok(BoxDecorationBreak::Clone),
420
1
            _ => Err(BoxDecorationBreakParseError::InvalidValue(input)),
421
        }
422
3
    }
423
}
424

            
425
#[cfg(feature = "parser")]
426
pub use parser::*;
427

            
428
#[cfg(all(test, feature = "parser"))]
429
mod tests {
430
    use super::*;
431

            
432
    #[test]
433
1
    fn test_parse_page_break() {
434
1
        assert_eq!(parse_page_break("auto").unwrap(), PageBreak::Auto);
435
1
        assert_eq!(parse_page_break("page").unwrap(), PageBreak::Page);
436
1
        assert_eq!(
437
1
            parse_page_break("avoid-column").unwrap(),
438
            PageBreak::AvoidColumn
439
        );
440
1
        assert!(parse_page_break("invalid").is_err());
441
1
    }
442

            
443
    #[test]
444
1
    fn test_parse_break_inside() {
445
1
        assert_eq!(parse_break_inside("auto").unwrap(), BreakInside::Auto);
446
1
        assert_eq!(parse_break_inside("avoid").unwrap(), BreakInside::Avoid);
447
1
        assert!(parse_break_inside("always").is_err());
448
1
    }
449

            
450
    #[test]
451
1
    fn test_parse_widows_orphans() {
452
1
        assert_eq!(parse_widows("3").unwrap().inner, 3);
453
1
        assert_eq!(parse_orphans("  1  ").unwrap().inner, 1);
454
1
        assert!(parse_widows("-2").is_err());
455
1
        assert!(parse_orphans("auto").is_err());
456
1
    }
457

            
458
    #[test]
459
1
    fn test_parse_box_decoration_break() {
460
1
        assert_eq!(
461
1
            parse_box_decoration_break("slice").unwrap(),
462
            BoxDecorationBreak::Slice
463
        );
464
1
        assert_eq!(
465
1
            parse_box_decoration_break("clone").unwrap(),
466
            BoxDecorationBreak::Clone
467
        );
468
1
        assert!(parse_box_decoration_break("copy").is_err());
469
1
    }
470
}