Skip to main content

fop_layout/layout/properties/
types.rs

1//! Core property types: Keep, KeepConstraint, BreakValue, OverflowBehavior
2
3use std::fmt;
4
5/// Keep values as per XSL-FO specification
6///
7/// Controls page and column breaking behavior to prevent orphans and widows.
8///
9/// # Examples
10///
11/// ```
12/// use fop_layout::layout::Keep;
13///
14/// let keep = Keep::Always;
15/// assert!(keep.is_active());
16/// assert_eq!(keep.strength(), i32::MAX);
17///
18/// let keep_auto = Keep::Auto;
19/// assert!(!keep_auto.is_active());
20/// ```
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
22pub enum Keep {
23    /// auto - no keep constraint
24    #[default]
25    Auto,
26    /// always - always keep together/with-next/with-previous
27    Always,
28    /// Integer strength value (higher = stronger constraint)
29    Integer(i32),
30}
31
32impl Keep {
33    /// Create a Keep value from a property value
34    pub fn from_property_value(value: &fop_core::PropertyValue) -> Self {
35        if value.is_auto() {
36            Keep::Auto
37        } else if let Some(s) = value.as_string() {
38            if s == "always" {
39                Keep::Always
40            } else {
41                Keep::Auto
42            }
43        } else if let Some(i) = value.as_integer() {
44            Keep::Integer(i)
45        } else {
46            Keep::Auto
47        }
48    }
49
50    /// Check if this is a keep constraint (not Auto)
51    pub fn is_active(&self) -> bool {
52        !matches!(self, Keep::Auto)
53    }
54
55    /// Get the strength of the keep constraint (higher = stronger)
56    pub fn strength(&self) -> i32 {
57        match self {
58            Keep::Auto => 0,
59            Keep::Always => i32::MAX,
60            Keep::Integer(i) => *i,
61        }
62    }
63}
64
65impl fmt::Display for Keep {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Keep::Auto => write!(f, "auto"),
69            Keep::Always => write!(f, "always"),
70            Keep::Integer(i) => write!(f, "{}", i),
71        }
72    }
73}
74
75/// Keep constraint for page breaking
76///
77/// Represents the XSL-FO keep properties that control page breaking behavior.
78///
79/// # Examples
80///
81/// ```
82/// use fop_layout::layout::{Keep, KeepConstraint};
83///
84/// let mut constraint = KeepConstraint::new();
85/// constraint.keep_together = Keep::Always;
86/// assert!(constraint.must_keep_together());
87///
88/// constraint.keep_with_next = Keep::Always;
89/// assert!(constraint.must_keep_with_next());
90/// ```
91#[derive(Debug, Clone, Copy, Default)]
92pub struct KeepConstraint {
93    /// keep-together.within-page
94    pub keep_together: Keep,
95    /// keep-with-next.within-page
96    pub keep_with_next: Keep,
97    /// keep-with-previous.within-page
98    pub keep_with_previous: Keep,
99}
100
101impl KeepConstraint {
102    /// Create a new empty keep constraint
103    pub fn new() -> Self {
104        Self::default()
105    }
106
107    /// Check if any keep constraint is active
108    pub fn has_constraint(&self) -> bool {
109        self.keep_together.is_active()
110            || self.keep_with_next.is_active()
111            || self.keep_with_previous.is_active()
112    }
113
114    /// Check if keep-together is active
115    pub fn must_keep_together(&self) -> bool {
116        self.keep_together.is_active()
117    }
118
119    /// Check if keep-with-next is active
120    pub fn must_keep_with_next(&self) -> bool {
121        self.keep_with_next.is_active()
122    }
123
124    /// Check if keep-with-previous is active
125    pub fn must_keep_with_previous(&self) -> bool {
126        self.keep_with_previous.is_active()
127    }
128}
129
130/// Break-before and break-after property values
131///
132/// Controls forced page/column breaks before or after an element.
133/// Based on XSL-FO 1.1 specification.
134///
135/// # Examples
136///
137/// ```
138/// use fop_layout::layout::BreakValue;
139///
140/// let break_val = BreakValue::Page;
141/// assert!(break_val.forces_break());
142///
143/// let auto = BreakValue::Auto;
144/// assert!(!auto.forces_break());
145/// ```
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
147pub enum BreakValue {
148    /// auto - no forced break
149    #[default]
150    Auto,
151    /// always - force break (deprecated, use page)
152    Always,
153    /// page - force page break
154    Page,
155    /// column - force column break
156    Column,
157    /// even-page - force break to next even-numbered page
158    EvenPage,
159    /// odd-page - force break to next odd-numbered page
160    OddPage,
161}
162
163impl BreakValue {
164    /// Create a BreakValue from a property value
165    pub fn from_property_value(value: &fop_core::PropertyValue) -> Self {
166        if value.is_auto() {
167            BreakValue::Auto
168        } else if let Some(s) = value.as_string() {
169            match s {
170                "always" => BreakValue::Always,
171                "page" => BreakValue::Page,
172                "column" => BreakValue::Column,
173                "even-page" => BreakValue::EvenPage,
174                "odd-page" => BreakValue::OddPage,
175                _ => BreakValue::Auto,
176            }
177        } else {
178            BreakValue::Auto
179        }
180    }
181
182    /// Check if this value forces a break
183    pub fn forces_break(&self) -> bool {
184        !matches!(self, BreakValue::Auto)
185    }
186
187    /// Check if this forces a page break (not column)
188    pub fn forces_page_break(&self) -> bool {
189        matches!(
190            self,
191            BreakValue::Always | BreakValue::Page | BreakValue::EvenPage | BreakValue::OddPage
192        )
193    }
194
195    /// Check if this requires an even page
196    pub fn requires_even_page(&self) -> bool {
197        matches!(self, BreakValue::EvenPage)
198    }
199
200    /// Check if this requires an odd page
201    pub fn requires_odd_page(&self) -> bool {
202        matches!(self, BreakValue::OddPage)
203    }
204}
205
206impl fmt::Display for BreakValue {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        match self {
209            BreakValue::Auto => write!(f, "auto"),
210            BreakValue::Always => write!(f, "always"),
211            BreakValue::Page => write!(f, "page"),
212            BreakValue::Column => write!(f, "column"),
213            BreakValue::EvenPage => write!(f, "even-page"),
214            BreakValue::OddPage => write!(f, "odd-page"),
215        }
216    }
217}
218
219/// Overflow behavior values
220///
221/// Controls how content that overflows a container should be handled.
222/// Based on XSL-FO and CSS overflow property.
223///
224/// # Examples
225///
226/// ```
227/// use fop_layout::layout::OverflowBehavior;
228///
229/// let overflow = OverflowBehavior::Hidden;
230/// assert!(overflow.clips_content());
231///
232/// let visible = OverflowBehavior::Visible;
233/// assert!(!visible.clips_content());
234/// ```
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
236pub enum OverflowBehavior {
237    /// visible - content is not clipped and may overflow
238    #[default]
239    Visible,
240    /// hidden - content is clipped to the container
241    Hidden,
242    /// scroll - content is clipped but scrollbars are shown (PDF renders as hidden)
243    Scroll,
244    /// auto - browser decides (PDF renders as visible)
245    Auto,
246}
247
248impl OverflowBehavior {
249    /// Create an OverflowBehavior from a property value
250    pub fn from_property_value(value: &fop_core::PropertyValue) -> Self {
251        if let Some(s) = value.as_string() {
252            match s {
253                "hidden" => OverflowBehavior::Hidden,
254                "scroll" => OverflowBehavior::Scroll,
255                "auto" => OverflowBehavior::Auto,
256                "visible" => OverflowBehavior::Visible,
257                _ => OverflowBehavior::Visible,
258            }
259        } else {
260            OverflowBehavior::Visible
261        }
262    }
263
264    /// Check if this overflow behavior requires clipping
265    pub fn clips_content(&self) -> bool {
266        matches!(self, OverflowBehavior::Hidden | OverflowBehavior::Scroll)
267    }
268}
269
270impl fmt::Display for OverflowBehavior {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        match self {
273            OverflowBehavior::Visible => write!(f, "visible"),
274            OverflowBehavior::Hidden => write!(f, "hidden"),
275            OverflowBehavior::Scroll => write!(f, "scroll"),
276            OverflowBehavior::Auto => write!(f, "auto"),
277        }
278    }
279}