Skip to main content

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}