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}