fop_layout/layout/properties/misc.rs
1//! Miscellaneous property extraction: overflow, border-radius, column layout, opacity, clear
2
3use fop_core::{PropertyId, PropertyList};
4use fop_types::Length;
5
6pub use super::types::OverflowBehavior;
7
8/// Extract overflow property from properties
9///
10/// Returns the overflow behavior for a container. This controls whether
11/// content that exceeds the container bounds should be clipped.
12///
13/// # Examples
14///
15/// ```
16/// use fop_core::{PropertyList, PropertyId, PropertyValue};
17/// use fop_layout::layout::extract_overflow;
18/// use std::borrow::Cow;
19///
20/// let mut props = PropertyList::new();
21/// props.set(PropertyId::Overflow, PropertyValue::String(Cow::Borrowed("hidden")));
22///
23/// let overflow = extract_overflow(&props);
24/// assert!(overflow.clips_content());
25/// ```
26pub fn extract_overflow(properties: &PropertyList) -> OverflowBehavior {
27 properties
28 .get(PropertyId::Overflow)
29 .ok()
30 .map(|v| OverflowBehavior::from_property_value(&v))
31 .unwrap_or(OverflowBehavior::Visible)
32}
33
34/// Extract border-radius from properties
35///
36/// Border radius controls the curvature of corners. Returns an array of
37/// [top-left, top-right, bottom-right, bottom-left] radii.
38/// Returns None if no border radius is specified.
39pub fn extract_border_radius(properties: &PropertyList) -> Option<[Length; 4]> {
40 // Check for shorthand x-border-radius property first
41 if let Ok(value) = properties.get(PropertyId::XBorderRadius) {
42 if let Some(len) = value.as_length() {
43 // Single value applies to all corners
44 return Some([len, len, len, len]);
45 }
46 }
47
48 // Extract individual corner radii
49 // CSS/FOP uses: top-left, top-right, bottom-right, bottom-left
50 let top_left = properties
51 .get(PropertyId::XBorderBeforeStartRadius)
52 .ok()
53 .and_then(|v| v.as_length())
54 .unwrap_or(Length::ZERO);
55
56 let top_right = properties
57 .get(PropertyId::XBorderBeforeEndRadius)
58 .ok()
59 .and_then(|v| v.as_length())
60 .unwrap_or(Length::ZERO);
61
62 let bottom_right = properties
63 .get(PropertyId::XBorderAfterEndRadius)
64 .ok()
65 .and_then(|v| v.as_length())
66 .unwrap_or(Length::ZERO);
67
68 let bottom_left = properties
69 .get(PropertyId::XBorderAfterStartRadius)
70 .ok()
71 .and_then(|v| v.as_length())
72 .unwrap_or(Length::ZERO);
73
74 // Only return Some if at least one corner has a non-zero radius
75 if top_left > Length::ZERO
76 || top_right > Length::ZERO
77 || bottom_right > Length::ZERO
78 || bottom_left > Length::ZERO
79 {
80 Some([top_left, top_right, bottom_right, bottom_left])
81 } else {
82 None
83 }
84}
85
86/// Extract column-count from properties
87///
88/// column-count specifies the number of columns for multi-column layout.
89/// The default is 1 (single column).
90///
91/// # Examples
92///
93/// ```
94/// use fop_core::{PropertyList, PropertyId, PropertyValue};
95/// use fop_layout::layout::extract_column_count;
96///
97/// let mut props = PropertyList::new();
98/// props.set(PropertyId::ColumnCount, PropertyValue::Integer(2));
99///
100/// let count = extract_column_count(&props);
101/// assert_eq!(count, 2);
102/// ```
103pub fn extract_column_count(properties: &PropertyList) -> i32 {
104 properties
105 .get(PropertyId::ColumnCount)
106 .ok()
107 .and_then(|v| v.as_integer())
108 .unwrap_or(1) // Default to single column
109 .max(1) // Ensure at least 1 column
110}
111
112/// Extract column-gap from properties
113///
114/// column-gap specifies the space between columns in multi-column layout.
115/// The default is 1em (approximately 12pt for typical fonts).
116///
117/// # Examples
118///
119/// ```
120/// use fop_core::{PropertyList, PropertyId, PropertyValue};
121/// use fop_layout::layout::extract_column_gap;
122/// use fop_types::Length;
123///
124/// let mut props = PropertyList::new();
125/// props.set(PropertyId::ColumnGap, PropertyValue::Length(Length::from_pt(20.0)));
126///
127/// let gap = extract_column_gap(&props);
128/// assert_eq!(gap, Length::from_pt(20.0));
129/// ```
130pub fn extract_column_gap(properties: &PropertyList) -> Length {
131 properties
132 .get(PropertyId::ColumnGap)
133 .ok()
134 .and_then(|v| v.as_length())
135 .unwrap_or(Length::from_pt(12.0)) // Default to 1em (approximately)
136}
137
138/// Extract opacity from properties
139///
140/// opacity specifies the transparency level for rendering.
141/// Valid values are 0.0 (fully transparent) to 1.0 (fully opaque).
142/// The default is 1.0 (opaque).
143///
144/// # Examples
145///
146/// ```
147/// use fop_core::{PropertyList, PropertyId, PropertyValue};
148/// use fop_layout::layout::extract_opacity;
149///
150/// let props = PropertyList::new();
151/// // Extract opacity returns 1.0 (fully opaque) as default
152/// let opacity = extract_opacity(&props);
153/// assert_eq!(opacity, 1.0);
154/// ```
155pub fn extract_opacity(properties: &PropertyList) -> f64 {
156 properties
157 .get(PropertyId::Opacity)
158 .ok()
159 .and_then(|v| v.as_number())
160 .unwrap_or(1.0) // Default to fully opaque
161 .clamp(0.0, 1.0) // Clamp to [0.0, 1.0]
162}
163
164/// Extract the `clear` property from a block's PropertyList.
165///
166/// Returns the XSL-FO clear value that controls float clearing behaviour.
167/// Valid values are `left`, `right`, `both`, `start`, `end`, and `none`.
168///
169/// The enum values used here map to XSL-FO integer codes:
170/// EN_NONE = 77, EN_LEFT = 66, EN_RIGHT = 96, EN_BOTH = 19,
171/// EN_START = 104, EN_END = 45.
172pub fn extract_clear(properties: &PropertyList) -> super::super::engine::ClearSide {
173 use super::super::engine::ClearSide;
174
175 if let Ok(prop) = properties.get(PropertyId::Clear) {
176 if let Some(enum_val) = prop.as_enum() {
177 return match enum_val {
178 66 => ClearSide::Left,
179 96 => ClearSide::Right,
180 19 => ClearSide::Both,
181 104 => ClearSide::Start,
182 45 => ClearSide::End,
183 _ => ClearSide::None,
184 };
185 }
186 if let Some(s) = prop.as_string() {
187 return match s {
188 "left" => ClearSide::Left,
189 "right" => ClearSide::Right,
190 "both" => ClearSide::Both,
191 "start" => ClearSide::Start,
192 "end" => ClearSide::End,
193 _ => ClearSide::None,
194 };
195 }
196 }
197 ClearSide::None
198}