fop_layout/layout/properties/
extraction.rs1use crate::area::{
4 BorderStyle, Direction, DisplayAlign, FontStretch, FontStyle, FontVariant, Span, TextTransform,
5 TraitSet, WritingMode,
6};
7use crate::layout::TextAlign;
8use fop_core::{PropertyId, PropertyList};
9use fop_types::Length;
10
11use super::misc::{extract_border_radius, extract_opacity, extract_overflow, OverflowBehavior};
12use super::spacing::{extract_letter_spacing, extract_line_height, extract_word_spacing};
13
14pub(super) fn extract_font_size(
20 properties: &PropertyList,
21 value: &fop_core::PropertyValue,
22) -> Option<Length> {
23 if let Some(len) = value.as_length() {
25 return Some(len);
26 }
27
28 if value.as_relative_font_size().is_some() {
30 let parent_font_size = if let Some(parent) = properties.parent() {
32 parent
33 .get(PropertyId::FontSize)
34 .ok()
35 .and_then(|v| extract_font_size(parent, &v))
36 .unwrap_or(Length::from_pt(12.0)) } else {
38 Length::from_pt(12.0) };
40
41 return value.resolve_font_size(parent_font_size);
42 }
43
44 None
45}
46
47pub(super) fn parse_border_style(s: Option<&str>) -> Option<BorderStyle> {
49 match s? {
50 "none" => Some(BorderStyle::None),
51 "solid" => Some(BorderStyle::Solid),
52 "dashed" => Some(BorderStyle::Dashed),
53 "dotted" => Some(BorderStyle::Dotted),
54 "double" => Some(BorderStyle::Double),
55 "groove" => Some(BorderStyle::Groove),
56 "ridge" => Some(BorderStyle::Ridge),
57 "inset" => Some(BorderStyle::Inset),
58 "outset" => Some(BorderStyle::Outset),
59 "hidden" => Some(BorderStyle::Hidden),
60 _ => None,
61 }
62}
63
64pub fn extract_traits(properties: &PropertyList) -> TraitSet {
66 let mut traits = TraitSet::default();
67
68 if let Ok(value) = properties.get(PropertyId::Color) {
70 traits.color = value.as_color();
71 }
72
73 if let Ok(value) = properties.get(PropertyId::BackgroundColor) {
75 traits.background_color = value.as_color();
76 }
77
78 if let Ok(value) = properties.get(PropertyId::FontFamily) {
80 traits.font_family = value.as_string().map(|s| s.to_string());
81 }
82
83 if let Ok(value) = properties.get(PropertyId::FontSize) {
85 traits.font_size = extract_font_size(properties, &value);
86 }
87
88 if let Ok(value) = properties.get(PropertyId::FontWeight) {
90 if let Some(num) = value.as_integer() {
91 traits.font_weight = Some(num as u16);
92 }
93 }
94
95 if let Ok(value) = properties.get(PropertyId::FontStyle) {
97 if let Some(enum_val) = value.as_enum() {
98 traits.font_style = match enum_val {
99 1 => Some(FontStyle::Italic),
100 2 => Some(FontStyle::Oblique),
101 _ => Some(FontStyle::Normal),
102 };
103 }
104 }
105
106 let _margin_top = properties
108 .get(PropertyId::MarginTop)
109 .ok()
110 .and_then(|v| v.as_length())
111 .unwrap_or(Length::ZERO);
112 let _margin_right = properties
113 .get(PropertyId::MarginRight)
114 .ok()
115 .and_then(|v| v.as_length())
116 .unwrap_or(Length::ZERO);
117 let _margin_bottom = properties
118 .get(PropertyId::MarginBottom)
119 .ok()
120 .and_then(|v| v.as_length())
121 .unwrap_or(Length::ZERO);
122 let _margin_left = properties
123 .get(PropertyId::MarginLeft)
124 .ok()
125 .and_then(|v| v.as_length())
126 .unwrap_or(Length::ZERO);
127
128 let padding_top = properties
130 .get(PropertyId::PaddingTop)
131 .ok()
132 .and_then(|v| v.as_length())
133 .unwrap_or(Length::ZERO);
134 let padding_right = properties
135 .get(PropertyId::PaddingRight)
136 .ok()
137 .and_then(|v| v.as_length())
138 .unwrap_or(Length::ZERO);
139 let padding_bottom = properties
140 .get(PropertyId::PaddingBottom)
141 .ok()
142 .and_then(|v| v.as_length())
143 .unwrap_or(Length::ZERO);
144 let padding_left = properties
145 .get(PropertyId::PaddingLeft)
146 .ok()
147 .and_then(|v| v.as_length())
148 .unwrap_or(Length::ZERO);
149
150 traits.padding = Some([padding_top, padding_right, padding_bottom, padding_left]);
151
152 let border_top = properties
154 .get(PropertyId::BorderTopWidth)
155 .ok()
156 .and_then(|v| v.as_length())
157 .unwrap_or(Length::ZERO);
158 let border_right = properties
159 .get(PropertyId::BorderRightWidth)
160 .ok()
161 .and_then(|v| v.as_length())
162 .unwrap_or(Length::ZERO);
163 let border_bottom = properties
164 .get(PropertyId::BorderBottomWidth)
165 .ok()
166 .and_then(|v| v.as_length())
167 .unwrap_or(Length::ZERO);
168 let border_left = properties
169 .get(PropertyId::BorderLeftWidth)
170 .ok()
171 .and_then(|v| v.as_length())
172 .unwrap_or(Length::ZERO);
173
174 traits.border_width = Some([border_top, border_right, border_bottom, border_left]);
175
176 let border_top_color = properties
178 .get(PropertyId::BorderTopColor)
179 .ok()
180 .and_then(|v| v.as_color())
181 .unwrap_or(fop_types::Color::BLACK);
182 let border_right_color = properties
183 .get(PropertyId::BorderRightColor)
184 .ok()
185 .and_then(|v| v.as_color())
186 .unwrap_or(fop_types::Color::BLACK);
187 let border_bottom_color = properties
188 .get(PropertyId::BorderBottomColor)
189 .ok()
190 .and_then(|v| v.as_color())
191 .unwrap_or(fop_types::Color::BLACK);
192 let border_left_color = properties
193 .get(PropertyId::BorderLeftColor)
194 .ok()
195 .and_then(|v| v.as_color())
196 .unwrap_or(fop_types::Color::BLACK);
197
198 traits.border_color = Some([
199 border_top_color,
200 border_right_color,
201 border_bottom_color,
202 border_left_color,
203 ]);
204
205 let border_top_style = properties
207 .get(PropertyId::BorderTopStyle)
208 .ok()
209 .and_then(|v| parse_border_style(v.as_string()))
210 .unwrap_or(BorderStyle::Solid);
211 let border_right_style = properties
212 .get(PropertyId::BorderRightStyle)
213 .ok()
214 .and_then(|v| parse_border_style(v.as_string()))
215 .unwrap_or(BorderStyle::Solid);
216 let border_bottom_style = properties
217 .get(PropertyId::BorderBottomStyle)
218 .ok()
219 .and_then(|v| parse_border_style(v.as_string()))
220 .unwrap_or(BorderStyle::Solid);
221 let border_left_style = properties
222 .get(PropertyId::BorderLeftStyle)
223 .ok()
224 .and_then(|v| parse_border_style(v.as_string()))
225 .unwrap_or(BorderStyle::Solid);
226
227 traits.border_style = Some([
228 border_top_style,
229 border_right_style,
230 border_bottom_style,
231 border_left_style,
232 ]);
233
234 if let Ok(value) = properties.get(PropertyId::TextAlign) {
236 if let Some(s) = value.as_string() {
237 traits.text_align = match s {
238 "left" => Some(TextAlign::Left),
239 "right" => Some(TextAlign::Right),
240 "center" => Some(TextAlign::Center),
241 "justify" => Some(TextAlign::Justify),
242 _ => None,
243 };
244 }
245 }
246
247 traits.line_height = extract_line_height(properties);
249
250 traits.letter_spacing = extract_letter_spacing(properties);
252
253 traits.word_spacing = extract_word_spacing(properties);
255
256 traits.border_radius = extract_border_radius(properties);
258
259 let overflow = extract_overflow(properties);
261 if overflow != OverflowBehavior::Visible {
263 traits.overflow = Some(overflow);
264 }
265
266 let opacity = extract_opacity(properties);
268 if (opacity - 1.0).abs() > f64::EPSILON {
270 traits.opacity = Some(opacity);
271 }
272
273 if let Ok(value) = properties.get(PropertyId::TextTransform) {
275 if let Some(s) = value.as_string() {
276 traits.text_transform = match s {
277 "uppercase" => Some(TextTransform::Uppercase),
278 "lowercase" => Some(TextTransform::Lowercase),
279 "capitalize" => Some(TextTransform::Capitalize),
280 _ => None,
281 };
282 }
283 }
284
285 if let Ok(value) = properties.get(PropertyId::FontVariant) {
287 if let Some(s) = value.as_string() {
288 traits.font_variant = match s {
289 "small-caps" => Some(FontVariant::SmallCaps),
290 _ => None,
291 };
292 }
293 }
294
295 if let Ok(value) = properties.get(PropertyId::DisplayAlign) {
297 if let Some(s) = value.as_string() {
298 traits.display_align = match s {
299 "center" => Some(DisplayAlign::Center),
300 "after" => Some(DisplayAlign::After),
301 _ => Some(DisplayAlign::Before),
302 };
303 }
304 }
305
306 if let Ok(value) = properties.get(PropertyId::BaselineShift) {
308 if let Some(s) = value.as_string() {
309 traits.baseline_shift = match s {
310 "super" => Some(0.5), "sub" => Some(-0.3), "baseline" | "0" => Some(0.0),
313 _ => None,
314 };
315 } else if let Some(len) = value.as_length() {
316 traits.baseline_shift = Some(len.to_pt() / 12.0); }
319 }
320
321 if let Ok(value) = properties.get(PropertyId::Hyphenate) {
323 if let Some(s) = value.as_string() {
324 traits.hyphenate = Some(s == "true");
325 } else if let Some(b) = value.as_boolean() {
326 traits.hyphenate = Some(b);
327 }
328 }
329
330 if let Ok(value) = properties.get(PropertyId::HyphenationPushCharacterCount) {
331 if let Some(i) = value.as_integer() {
332 traits.hyphenation_push_chars = Some(i as u32);
333 }
334 }
335
336 if let Ok(value) = properties.get(PropertyId::HyphenationRemainCharacterCount) {
337 if let Some(i) = value.as_integer() {
338 traits.hyphenation_remain_chars = Some(i as u32);
339 }
340 }
341
342 if let Ok(value) = properties.get(PropertyId::FontStretch) {
344 if let Some(s) = value.as_string() {
345 traits.font_stretch = match s {
346 "ultra-condensed" => Some(FontStretch::UltraCondensed),
347 "extra-condensed" => Some(FontStretch::ExtraCondensed),
348 "condensed" => Some(FontStretch::Condensed),
349 "semi-condensed" => Some(FontStretch::SemiCondensed),
350 "semi-expanded" => Some(FontStretch::SemiExpanded),
351 "expanded" => Some(FontStretch::Expanded),
352 "extra-expanded" => Some(FontStretch::ExtraExpanded),
353 "ultra-expanded" => Some(FontStretch::UltraExpanded),
354 _ => None, };
356 }
357 }
358
359 if let Ok(value) = properties.get(PropertyId::TextAlignLast) {
361 if let Some(s) = value.as_string() {
362 traits.text_align_last = match s {
363 "left" | "start" => Some(TextAlign::Left),
364 "right" | "end" => Some(TextAlign::Right),
365 "center" => Some(TextAlign::Center),
366 "justify" => Some(TextAlign::Justify),
367 _ => None,
368 };
369 }
370 }
371
372 if let Ok(value) = properties.get(PropertyId::ChangeBarColor) {
374 traits.change_bar_color = value.as_color();
375 }
376
377 if let Ok(value) = properties.get(PropertyId::Span) {
379 if value.as_string() == Some("all") {
380 traits.span = Span::All;
381 }
382 }
383
384 if let Ok(value) = properties.get(PropertyId::Role) {
386 traits.role = value.as_string().map(|s| s.to_string());
387 }
388
389 if let Ok(value) = properties.get(PropertyId::XmlLang) {
391 traits.xml_lang = value.as_string().map(|s| s.to_string());
392 }
393
394 if let Ok(value) = properties.get(PropertyId::WritingMode) {
396 if let Some(s) = value.as_string() {
397 traits.writing_mode = match s {
398 "rl-tb" | "rl" => WritingMode::RlTb,
399 "tb-rl" | "tb" => WritingMode::TbRl,
400 "tb-lr" => WritingMode::TbLr,
401 _ => WritingMode::LrTb,
402 };
403 }
404 }
405
406 if let Ok(value) = properties.get(PropertyId::Direction) {
408 if let Some(s) = value.as_string() {
409 traits.direction = match s {
410 "rtl" => Direction::Rtl,
411 _ => Direction::Ltr,
412 };
413 }
414 }
415
416 traits
417}
418
419pub fn measure_text_width(text: &str, font_size: Length, font_weight: Option<u16>) -> Length {
435 if text.is_empty() {
436 return Length::ZERO;
437 }
438
439 let weight_factor = match font_weight {
444 Some(w) if w >= 600 => 0.55,
445 _ => 0.50,
446 };
447
448 let mut total_width = 0.0_f64;
449 for ch in text.chars() {
450 let char_factor = if is_cjk(ch) {
451 1.0
452 } else if ch == ' ' {
453 0.25
454 } else if r#"iIl1!|.,;'"#.contains(ch) {
455 0.3
456 } else if "mMwW".contains(ch) {
457 0.7
458 } else {
459 weight_factor
460 };
461 total_width += char_factor;
462 }
463
464 Length::from_pt(total_width * font_size.to_pt())
465}
466
467fn is_cjk(ch: char) -> bool {
469 matches!(ch,
470 '\u{3000}'..='\u{9FFF}' | '\u{AC00}'..='\u{D7AF}' | '\u{F900}'..='\u{FAFF}' | '\u{FF00}'..='\u{FFEF}' )
475}