Skip to main content

azul_css/props/layout/
fragmentation.rs

1//! CSS properties for controlling fragmentation (page/column breaks).
2
3use alloc::string::{String, ToString};
4use core::num::ParseIntError;
5
6use crate::props::formatter::PrintAsCssValue;
7
8// --- break-before / break-after ---
9
10/// Represents a `break-before` or `break-after` CSS property value.
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(C)]
13pub enum PageBreak {
14    Auto,
15    Avoid,
16    Always,
17    All,
18    Page,
19    AvoidPage,
20    Left,
21    Right,
22    Recto,
23    Verso,
24    Column,
25    AvoidColumn,
26}
27
28impl Default for PageBreak {
29    fn default() -> Self {
30        Self::Auto
31    }
32}
33
34impl 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)]
58pub enum BreakInside {
59    Auto,
60    Avoid,
61    AvoidPage,
62    AvoidColumn,
63}
64
65impl Default for BreakInside {
66    fn default() -> Self {
67        Self::Auto
68    }
69}
70
71impl PrintAsCssValue for BreakInside {
72    fn print_as_css_value(&self) -> String {
73        String::from(match self {
74            Self::Auto => "auto",
75            Self::Avoid => "avoid",
76            Self::AvoidPage => "avoid-page",
77            Self::AvoidColumn => "avoid-column",
78        })
79    }
80}
81
82// --- widows / orphans ---
83
84/// CSS `widows` property - minimum number of lines in a block container
85/// that must be shown at the top of a page, region, or column.
86#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
87#[repr(C)]
88pub struct Widows {
89    pub inner: u32,
90}
91
92impl Default for Widows {
93    fn default() -> Self {
94        Self { inner: 2 }
95    }
96}
97
98impl PrintAsCssValue for Widows {
99    fn print_as_css_value(&self) -> String {
100        self.inner.to_string()
101    }
102}
103
104/// CSS `orphans` property - minimum number of lines in a block container
105/// that must be shown at the bottom of a page, region, or column.
106#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
107#[repr(C)]
108pub struct Orphans {
109    pub inner: u32,
110}
111
112impl Default for Orphans {
113    fn default() -> Self {
114        Self { inner: 2 }
115    }
116}
117
118impl PrintAsCssValue for Orphans {
119    fn print_as_css_value(&self) -> String {
120        self.inner.to_string()
121    }
122}
123
124// --- box-decoration-break ---
125
126/// Represents a `box-decoration-break` CSS property value.
127#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
128#[repr(C)]
129pub enum BoxDecorationBreak {
130    Slice,
131    Clone,
132}
133
134impl Default for BoxDecorationBreak {
135    fn default() -> Self {
136        Self::Slice
137    }
138}
139
140impl PrintAsCssValue for BoxDecorationBreak {
141    fn print_as_css_value(&self) -> String {
142        String::from(match self {
143            Self::Slice => "slice",
144            Self::Clone => "clone",
145        })
146    }
147}
148
149// Formatting to Rust code
150impl crate::format_rust_code::FormatAsRustCode for PageBreak {
151    fn format_as_rust_code(&self, _tabs: usize) -> String {
152        match self {
153            PageBreak::Auto => String::from("PageBreak::Auto"),
154            PageBreak::Avoid => String::from("PageBreak::Avoid"),
155            PageBreak::Always => String::from("PageBreak::Always"),
156            PageBreak::All => String::from("PageBreak::All"),
157            PageBreak::Page => String::from("PageBreak::Page"),
158            PageBreak::AvoidPage => String::from("PageBreak::AvoidPage"),
159            PageBreak::Left => String::from("PageBreak::Left"),
160            PageBreak::Right => String::from("PageBreak::Right"),
161            PageBreak::Recto => String::from("PageBreak::Recto"),
162            PageBreak::Verso => String::from("PageBreak::Verso"),
163            PageBreak::Column => String::from("PageBreak::Column"),
164            PageBreak::AvoidColumn => String::from("PageBreak::AvoidColumn"),
165        }
166    }
167}
168
169impl crate::format_rust_code::FormatAsRustCode for BreakInside {
170    fn format_as_rust_code(&self, _tabs: usize) -> String {
171        match self {
172            BreakInside::Auto => String::from("BreakInside::Auto"),
173            BreakInside::Avoid => String::from("BreakInside::Avoid"),
174            BreakInside::AvoidPage => String::from("BreakInside::AvoidPage"),
175            BreakInside::AvoidColumn => String::from("BreakInside::AvoidColumn"),
176        }
177    }
178}
179
180impl crate::format_rust_code::FormatAsRustCode for Widows {
181    fn format_as_rust_code(&self, _tabs: usize) -> String {
182        format!("Widows {{ inner: {} }}", self.inner)
183    }
184}
185
186impl crate::format_rust_code::FormatAsRustCode for Orphans {
187    fn format_as_rust_code(&self, _tabs: usize) -> String {
188        format!("Orphans {{ inner: {} }}", self.inner)
189    }
190}
191
192impl crate::format_rust_code::FormatAsRustCode for BoxDecorationBreak {
193    fn format_as_rust_code(&self, _tabs: usize) -> String {
194        match self {
195            BoxDecorationBreak::Slice => String::from("BoxDecorationBreak::Slice"),
196            BoxDecorationBreak::Clone => String::from("BoxDecorationBreak::Clone"),
197        }
198    }
199}
200
201// --- PARSERS ---
202
203#[cfg(feature = "parser")]
204mod parser {
205    use super::*;
206
207    // -- PageBreak parser (`break-before`, `break-after`)
208
209    #[derive(Clone, PartialEq)]
210    pub enum PageBreakParseError<'a> {
211        InvalidValue(&'a str),
212    }
213
214    impl_debug_as_display!(PageBreakParseError<'a>);
215    impl_display! { PageBreakParseError<'a>, {
216        InvalidValue(v) => format!("Invalid break value: \"{}\"", v),
217    }}
218
219    #[derive(Debug, Clone, PartialEq)]
220    pub enum PageBreakParseErrorOwned {
221        InvalidValue(String),
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()),
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    pub fn parse_page_break<'a>(input: &'a str) -> Result<PageBreak, PageBreakParseError<'a>> {
241        match input.trim() {
242            "auto" => Ok(PageBreak::Auto),
243            "avoid" => Ok(PageBreak::Avoid),
244            "always" => Ok(PageBreak::Always),
245            "all" => Ok(PageBreak::All),
246            "page" => Ok(PageBreak::Page),
247            "avoid-page" => Ok(PageBreak::AvoidPage),
248            "left" => Ok(PageBreak::Left),
249            "right" => Ok(PageBreak::Right),
250            "recto" => Ok(PageBreak::Recto),
251            "verso" => Ok(PageBreak::Verso),
252            "column" => Ok(PageBreak::Column),
253            "avoid-column" => Ok(PageBreak::AvoidColumn),
254            _ => Err(PageBreakParseError::InvalidValue(input)),
255        }
256    }
257
258    // -- BreakInside parser
259
260    #[derive(Clone, PartialEq)]
261    pub enum BreakInsideParseError<'a> {
262        InvalidValue(&'a str),
263    }
264
265    impl_debug_as_display!(BreakInsideParseError<'a>);
266    impl_display! { BreakInsideParseError<'a>, {
267        InvalidValue(v) => format!("Invalid break-inside value: \"{}\"", v),
268    }}
269
270    #[derive(Debug, Clone, PartialEq)]
271    pub enum BreakInsideParseErrorOwned {
272        InvalidValue(String),
273    }
274
275    impl<'a> BreakInsideParseError<'a> {
276        pub fn to_contained(&self) -> BreakInsideParseErrorOwned {
277            match self {
278                Self::InvalidValue(s) => BreakInsideParseErrorOwned::InvalidValue(s.to_string()),
279            }
280        }
281    }
282
283    impl BreakInsideParseErrorOwned {
284        pub fn to_shared<'a>(&'a self) -> BreakInsideParseError<'a> {
285            match self {
286                Self::InvalidValue(s) => BreakInsideParseError::InvalidValue(s.as_str()),
287            }
288        }
289    }
290
291    pub fn parse_break_inside<'a>(
292        input: &'a str,
293    ) -> Result<BreakInside, BreakInsideParseError<'a>> {
294        match input.trim() {
295            "auto" => Ok(BreakInside::Auto),
296            "avoid" => Ok(BreakInside::Avoid),
297            "avoid-page" => Ok(BreakInside::AvoidPage),
298            "avoid-column" => Ok(BreakInside::AvoidColumn),
299            _ => Err(BreakInsideParseError::InvalidValue(input)),
300        }
301    }
302
303    // -- Widows / Orphans parsers
304
305    macro_rules! define_widow_orphan_parser {
306        ($fn_name:ident, $struct_name:ident, $error_name:ident, $error_owned_name:ident, $prop_name:expr) => {
307            #[derive(Clone, PartialEq)]
308            pub enum $error_name<'a> {
309                ParseInt(ParseIntError, &'a str),
310                NegativeValue(&'a str),
311            }
312
313            impl_debug_as_display!($error_name<'a>);
314            impl_display! { $error_name<'a>, {
315                ParseInt(e, s) => format!("Invalid integer for {}: \"{}\". Reason: {}", $prop_name, s, e),
316                NegativeValue(s) => format!("Invalid value for {}: \"{}\". Value cannot be negative.", $prop_name, s),
317            }}
318
319            #[derive(Debug, Clone, PartialEq)]
320            pub enum $error_owned_name {
321                ParseInt(String, String),
322                NegativeValue(String),
323            }
324
325            impl<'a> $error_name<'a> {
326                pub fn to_contained(&self) -> $error_owned_name {
327                    match self {
328                        Self::ParseInt(e, s) => $error_owned_name::ParseInt(e.to_string(), s.to_string()),
329                        Self::NegativeValue(s) => $error_owned_name::NegativeValue(s.to_string()),
330                    }
331                }
332            }
333
334            impl $error_owned_name {
335                pub fn to_shared<'a>(&'a self) -> $error_name<'a> {
336                     match self {
337                        // Can't reconstruct ParseIntError
338                        Self::ParseInt(_e, s) => $error_name::NegativeValue(s),
339                        Self::NegativeValue(s) => $error_name::NegativeValue(s),
340                    }
341                }
342            }
343
344            pub fn $fn_name<'a>(input: &'a str) -> Result<$struct_name, $error_name<'a>> {
345                let trimmed = input.trim();
346                let val: i32 = trimmed.parse().map_err(|e| $error_name::ParseInt(e, trimmed))?;
347                if val < 0 {
348                    return Err($error_name::NegativeValue(trimmed));
349                }
350                Ok($struct_name { inner: val as u32 })
351            }
352        };
353    }
354
355    define_widow_orphan_parser!(
356        parse_widows,
357        Widows,
358        WidowsParseError,
359        WidowsParseErrorOwned,
360        "widows"
361    );
362    define_widow_orphan_parser!(
363        parse_orphans,
364        Orphans,
365        OrphansParseError,
366        OrphansParseErrorOwned,
367        "orphans"
368    );
369
370    // -- BoxDecorationBreak parser
371
372    #[derive(Clone, PartialEq)]
373    pub enum BoxDecorationBreakParseError<'a> {
374        InvalidValue(&'a str),
375    }
376
377    impl_debug_as_display!(BoxDecorationBreakParseError<'a>);
378    impl_display! { BoxDecorationBreakParseError<'a>, {
379        InvalidValue(v) => format!("Invalid box-decoration-break value: \"{}\"", v),
380    }}
381
382    #[derive(Debug, Clone, PartialEq)]
383    pub enum BoxDecorationBreakParseErrorOwned {
384        InvalidValue(String),
385    }
386
387    impl<'a> BoxDecorationBreakParseError<'a> {
388        pub fn to_contained(&self) -> BoxDecorationBreakParseErrorOwned {
389            match self {
390                Self::InvalidValue(s) => {
391                    BoxDecorationBreakParseErrorOwned::InvalidValue(s.to_string())
392                }
393            }
394        }
395    }
396
397    impl BoxDecorationBreakParseErrorOwned {
398        pub fn to_shared<'a>(&'a self) -> BoxDecorationBreakParseError<'a> {
399            match self {
400                Self::InvalidValue(s) => BoxDecorationBreakParseError::InvalidValue(s.as_str()),
401            }
402        }
403    }
404
405    pub fn parse_box_decoration_break<'a>(
406        input: &'a str,
407    ) -> Result<BoxDecorationBreak, BoxDecorationBreakParseError<'a>> {
408        match input.trim() {
409            "slice" => Ok(BoxDecorationBreak::Slice),
410            "clone" => Ok(BoxDecorationBreak::Clone),
411            _ => Err(BoxDecorationBreakParseError::InvalidValue(input)),
412        }
413    }
414}
415
416#[cfg(feature = "parser")]
417pub use parser::*;
418
419#[cfg(all(test, feature = "parser"))]
420mod tests {
421    use super::*;
422
423    #[test]
424    fn test_parse_page_break() {
425        assert_eq!(parse_page_break("auto").unwrap(), PageBreak::Auto);
426        assert_eq!(parse_page_break("page").unwrap(), PageBreak::Page);
427        assert_eq!(
428            parse_page_break("avoid-column").unwrap(),
429            PageBreak::AvoidColumn
430        );
431        assert!(parse_page_break("invalid").is_err());
432    }
433
434    #[test]
435    fn test_parse_break_inside() {
436        assert_eq!(parse_break_inside("auto").unwrap(), BreakInside::Auto);
437        assert_eq!(parse_break_inside("avoid").unwrap(), BreakInside::Avoid);
438        assert!(parse_break_inside("always").is_err());
439    }
440
441    #[test]
442    fn test_parse_widows_orphans() {
443        assert_eq!(parse_widows("3").unwrap().inner, 3);
444        assert_eq!(parse_orphans("  1  ").unwrap().inner, 1);
445        assert!(parse_widows("-2").is_err());
446        assert!(parse_orphans("auto").is_err());
447    }
448
449    #[test]
450    fn test_parse_box_decoration_break() {
451        assert_eq!(
452            parse_box_decoration_break("slice").unwrap(),
453            BoxDecorationBreak::Slice
454        );
455        assert_eq!(
456            parse_box_decoration_break("clone").unwrap(),
457            BoxDecorationBreak::Clone
458        );
459        assert!(parse_box_decoration_break("copy").is_err());
460    }
461}