1pub mod break_keep;
6pub mod extraction;
7pub mod misc;
8pub mod spacing;
9pub mod types;
10
11pub use break_keep::{
12 extract_break_after, extract_break_before, extract_keep_constraint, extract_orphans,
13 extract_widows,
14};
15pub use extraction::{extract_traits, measure_text_width};
16pub use misc::{
17 extract_border_radius, extract_clear, extract_column_count, extract_column_gap,
18 extract_opacity, extract_overflow, OverflowBehavior,
19};
20pub use spacing::{
21 extract_end_indent, extract_letter_spacing, extract_line_height, extract_space_after,
22 extract_space_before, extract_start_indent, extract_text_indent, extract_word_spacing,
23};
24pub use types::{BreakValue, Keep, KeepConstraint};
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29 use fop_core::{Color, PropertyId, PropertyList, PropertyValue};
30 use fop_types::Length;
31
32 #[test]
33 fn test_extract_color() {
34 let mut props = PropertyList::new();
35 props.set(PropertyId::Color, PropertyValue::Color(Color::RED));
36
37 let traits = extract_traits(&props);
38 assert_eq!(traits.color, Some(Color::RED));
39 }
40
41 #[test]
42 fn test_extract_font_size() {
43 let mut props = PropertyList::new();
44 props.set(
45 PropertyId::FontSize,
46 PropertyValue::Length(Length::from_pt(14.0)),
47 );
48
49 let traits = extract_traits(&props);
50 assert_eq!(traits.font_size, Some(Length::from_pt(14.0)));
51 }
52
53 #[test]
54 fn test_extract_font_family() {
55 let mut props = PropertyList::new();
56 props.set(
57 PropertyId::FontFamily,
58 PropertyValue::String(std::borrow::Cow::Borrowed("Arial")),
59 );
60
61 let traits = extract_traits(&props);
62 assert_eq!(traits.font_family, Some("Arial".to_string()));
63 }
64
65 #[test]
66 fn test_extract_padding() {
67 let mut props = PropertyList::new();
68 props.set(
69 PropertyId::PaddingTop,
70 PropertyValue::Length(Length::from_pt(10.0)),
71 );
72 props.set(
73 PropertyId::PaddingRight,
74 PropertyValue::Length(Length::from_pt(20.0)),
75 );
76
77 let traits = extract_traits(&props);
78 assert!(traits.padding.is_some());
79
80 let padding = traits.padding.expect("test: should succeed");
81 assert_eq!(padding[0], Length::from_pt(10.0)); assert_eq!(padding[1], Length::from_pt(20.0)); }
84
85 #[test]
86 fn test_extract_text_align() {
87 let mut props = PropertyList::new();
88 props.set(
89 PropertyId::TextAlign,
90 PropertyValue::String(std::borrow::Cow::Borrowed("center")),
91 );
92
93 let traits = extract_traits(&props);
94 assert_eq!(
95 traits.text_align,
96 Some(crate::layout::inline::TextAlign::Center)
97 );
98
99 let mut props2 = PropertyList::new();
100 props2.set(
101 PropertyId::TextAlign,
102 PropertyValue::String(std::borrow::Cow::Borrowed("right")),
103 );
104
105 let traits2 = extract_traits(&props2);
106 assert_eq!(
107 traits2.text_align,
108 Some(crate::layout::inline::TextAlign::Right)
109 );
110 }
111
112 #[test]
113 fn test_extract_space_before() {
114 let mut props = PropertyList::new();
115 props.set(
116 PropertyId::SpaceBefore,
117 PropertyValue::Length(Length::from_pt(15.0)),
118 );
119
120 let space = extract_space_before(&props);
121 assert_eq!(space, Length::from_pt(15.0));
122 }
123
124 #[test]
125 fn test_extract_space_after() {
126 let mut props = PropertyList::new();
127 props.set(
128 PropertyId::SpaceAfter,
129 PropertyValue::Length(Length::from_pt(20.0)),
130 );
131
132 let space = extract_space_after(&props);
133 assert_eq!(space, Length::from_pt(20.0));
134 }
135
136 #[test]
137 fn test_extract_space_defaults() {
138 let props = PropertyList::new();
139
140 let space_before = extract_space_before(&props);
141 let space_after = extract_space_after(&props);
142
143 assert_eq!(space_before, Length::ZERO);
144 assert_eq!(space_after, Length::ZERO);
145 }
146
147 #[test]
148 fn test_keep_auto() {
149 let keep = Keep::Auto;
150 assert!(!keep.is_active());
151 assert_eq!(keep.strength(), 0);
152 }
153
154 #[test]
155 fn test_keep_always() {
156 let keep = Keep::Always;
157 assert!(keep.is_active());
158 assert_eq!(keep.strength(), i32::MAX);
159 }
160
161 #[test]
162 fn test_keep_integer() {
163 let keep = Keep::Integer(10);
164 assert!(keep.is_active());
165 assert_eq!(keep.strength(), 10);
166 }
167
168 #[test]
169 fn test_keep_constraint_empty() {
170 let constraint = KeepConstraint::new();
171 assert!(!constraint.has_constraint());
172 assert!(!constraint.must_keep_together());
173 assert!(!constraint.must_keep_with_next());
174 assert!(!constraint.must_keep_with_previous());
175 }
176
177 #[test]
178 fn test_keep_constraint_together() {
179 let mut constraint = KeepConstraint::new();
180 constraint.keep_together = Keep::Always;
181 assert!(constraint.has_constraint());
182 assert!(constraint.must_keep_together());
183 assert!(!constraint.must_keep_with_next());
184 assert!(!constraint.must_keep_with_previous());
185 }
186
187 #[test]
188 fn test_keep_constraint_with_next() {
189 let mut constraint = KeepConstraint::new();
190 constraint.keep_with_next = Keep::Always;
191 assert!(constraint.has_constraint());
192 assert!(!constraint.must_keep_together());
193 assert!(constraint.must_keep_with_next());
194 assert!(!constraint.must_keep_with_previous());
195 }
196
197 #[test]
198 fn test_keep_constraint_with_previous() {
199 let mut constraint = KeepConstraint::new();
200 constraint.keep_with_previous = Keep::Always;
201 assert!(constraint.has_constraint());
202 assert!(!constraint.must_keep_together());
203 assert!(!constraint.must_keep_with_next());
204 assert!(constraint.must_keep_with_previous());
205 }
206
207 #[test]
208 fn test_extract_keep_constraint_empty() {
209 let props = PropertyList::new();
210 let constraint = extract_keep_constraint(&props);
211 assert!(!constraint.has_constraint());
212 }
213
214 #[test]
215 fn test_extract_keep_together() {
216 let mut props = PropertyList::new();
217 props.set(
218 PropertyId::KeepTogether,
219 PropertyValue::String(std::borrow::Cow::Borrowed("always")),
220 );
221
222 let constraint = extract_keep_constraint(&props);
223 assert!(constraint.must_keep_together());
224 }
225
226 #[test]
227 fn test_extract_keep_with_next() {
228 let mut props = PropertyList::new();
229 props.set(
230 PropertyId::KeepWithNext,
231 PropertyValue::String(std::borrow::Cow::Borrowed("always")),
232 );
233
234 let constraint = extract_keep_constraint(&props);
235 assert!(constraint.must_keep_with_next());
236 }
237
238 #[test]
239 fn test_extract_keep_with_previous() {
240 let mut props = PropertyList::new();
241 props.set(
242 PropertyId::KeepWithPrevious,
243 PropertyValue::String(std::borrow::Cow::Borrowed("always")),
244 );
245
246 let constraint = extract_keep_constraint(&props);
247 assert!(constraint.must_keep_with_previous());
248 }
249
250 #[test]
251 fn test_extract_multiple_keeps() {
252 let mut props = PropertyList::new();
253 props.set(
254 PropertyId::KeepTogether,
255 PropertyValue::String(std::borrow::Cow::Borrowed("always")),
256 );
257 props.set(
258 PropertyId::KeepWithNext,
259 PropertyValue::String(std::borrow::Cow::Borrowed("always")),
260 );
261
262 let constraint = extract_keep_constraint(&props);
263 assert!(constraint.must_keep_together());
264 assert!(constraint.must_keep_with_next());
265 assert!(!constraint.must_keep_with_previous());
266 }
267
268 #[test]
269 fn test_keep_integer_from_property() {
270 let mut props = PropertyList::new();
271 props.set(PropertyId::KeepTogether, PropertyValue::Integer(5));
272
273 let constraint = extract_keep_constraint(&props);
274 assert!(constraint.has_constraint());
275 assert_eq!(constraint.keep_together.strength(), 5);
276 }
277
278 #[test]
279 fn test_keep_auto_from_property() {
280 let mut props = PropertyList::new();
281 props.set(PropertyId::KeepTogether, PropertyValue::Auto);
282
283 let constraint = extract_keep_constraint(&props);
284 assert!(!constraint.has_constraint());
285 }
286
287 #[test]
288 fn test_extract_absolute_font_size() {
289 let mut props = PropertyList::new();
290 props.set(
291 PropertyId::FontSize,
292 PropertyValue::Length(Length::from_pt(14.0)),
293 );
294
295 let traits = extract_traits(&props);
296 assert_eq!(traits.font_size, Some(Length::from_pt(14.0)));
297 }
298
299 #[test]
300 fn test_extract_relative_font_size_larger() {
301 let mut parent = PropertyList::new();
303 parent.set(
304 PropertyId::FontSize,
305 PropertyValue::Length(Length::from_pt(12.0)),
306 );
307
308 let mut child = PropertyList::with_parent(&parent);
310 child.set(
311 PropertyId::FontSize,
312 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Larger),
313 );
314
315 let traits = extract_traits(&child);
316 assert_eq!(traits.font_size, Some(Length::from_pt(14.4)));
318 }
319
320 #[test]
321 fn test_extract_relative_font_size_smaller() {
322 let mut parent = PropertyList::new();
324 parent.set(
325 PropertyId::FontSize,
326 PropertyValue::Length(Length::from_pt(12.0)),
327 );
328
329 let mut child = PropertyList::with_parent(&parent);
331 child.set(
332 PropertyId::FontSize,
333 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Smaller),
334 );
335
336 let traits = extract_traits(&child);
337 assert_eq!(traits.font_size, Some(Length::from_pt(10.0)));
339 }
340
341 #[test]
342 fn test_extract_font_size_keyword_medium() {
343 let mut props = PropertyList::new();
344 props.set(
345 PropertyId::FontSize,
346 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Medium),
347 );
348
349 let traits = extract_traits(&props);
350 assert_eq!(traits.font_size, Some(Length::from_pt(16.0)));
352 }
353
354 #[test]
355 fn test_extract_font_size_keyword_large() {
356 let mut props = PropertyList::new();
357 props.set(
358 PropertyId::FontSize,
359 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Large),
360 );
361
362 let traits = extract_traits(&props);
363 assert_eq!(traits.font_size, Some(Length::from_pt(18.0)));
365 }
366
367 #[test]
368 fn test_extract_font_size_keyword_small() {
369 let mut props = PropertyList::new();
370 props.set(
371 PropertyId::FontSize,
372 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Small),
373 );
374
375 let traits = extract_traits(&props);
376 assert_eq!(traits.font_size, Some(Length::from_pt(13.0)));
378 }
379
380 #[test]
381 fn test_extract_font_size_keyword_xx_small() {
382 let mut props = PropertyList::new();
383 props.set(
384 PropertyId::FontSize,
385 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::XxSmall),
386 );
387
388 let traits = extract_traits(&props);
389 assert_eq!(traits.font_size, Some(Length::from_pt(9.0)));
391 }
392
393 #[test]
394 fn test_extract_font_size_keyword_xx_large() {
395 let mut props = PropertyList::new();
396 props.set(
397 PropertyId::FontSize,
398 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::XxLarge),
399 );
400
401 let traits = extract_traits(&props);
402 assert_eq!(traits.font_size, Some(Length::from_pt(32.0)));
404 }
405
406 #[test]
407 fn test_extract_font_size_larger_without_parent() {
408 let mut props = PropertyList::new();
410 props.set(
411 PropertyId::FontSize,
412 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Larger),
413 );
414
415 let traits = extract_traits(&props);
416 assert_eq!(traits.font_size, Some(Length::from_pt(14.4)));
418 }
419
420 #[test]
421 fn test_extract_font_size_nested_larger() {
422 let mut grandparent = PropertyList::new();
424 grandparent.set(
425 PropertyId::FontSize,
426 PropertyValue::Length(Length::from_pt(10.0)),
427 );
428
429 let mut parent = PropertyList::with_parent(&grandparent);
431 parent.set(
432 PropertyId::FontSize,
433 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Larger),
434 );
435
436 let mut child = PropertyList::with_parent(&parent);
438 child.set(
439 PropertyId::FontSize,
440 PropertyValue::RelativeFontSize(fop_core::RelativeFontSize::Larger),
441 );
442
443 let traits = extract_traits(&child);
444 assert_eq!(traits.font_size, Some(Length::from_pt(14.4)));
446 }
447
448 #[test]
449 fn test_keep_display() {
450 assert_eq!(format!("{}", Keep::Auto), "auto");
451 assert_eq!(format!("{}", Keep::Always), "always");
452 assert_eq!(format!("{}", Keep::Integer(5)), "5");
453 assert_eq!(format!("{}", Keep::Integer(100)), "100");
454 assert_eq!(format!("{}", Keep::Integer(0)), "0");
455 }
456
457 #[test]
458 fn test_break_value_display() {
459 assert_eq!(format!("{}", BreakValue::Auto), "auto");
460 assert_eq!(format!("{}", BreakValue::Always), "always");
461 assert_eq!(format!("{}", BreakValue::Page), "page");
462 assert_eq!(format!("{}", BreakValue::Column), "column");
463 assert_eq!(format!("{}", BreakValue::EvenPage), "even-page");
464 assert_eq!(format!("{}", BreakValue::OddPage), "odd-page");
465 }
466
467 #[test]
468 fn test_overflow_behavior_display() {
469 assert_eq!(format!("{}", OverflowBehavior::Visible), "visible");
470 assert_eq!(format!("{}", OverflowBehavior::Hidden), "hidden");
471 assert_eq!(format!("{}", OverflowBehavior::Scroll), "scroll");
472 assert_eq!(format!("{}", OverflowBehavior::Auto), "auto");
473 }
474
475 #[test]
476 fn test_extract_clear_none() {
477 use crate::layout::engine::ClearSide;
478 let props = PropertyList::new();
479 let clear = extract_clear(&props);
480 assert!(matches!(clear, ClearSide::None));
481 }
482
483 #[test]
484 fn test_extract_clear_left() {
485 use crate::layout::engine::ClearSide;
486 let mut props = PropertyList::new();
487 props.set(PropertyId::Clear, PropertyValue::Enum(66));
489 let clear = extract_clear(&props);
490 assert!(matches!(clear, ClearSide::Left));
491 }
492
493 #[test]
494 fn test_extract_clear_right() {
495 use crate::layout::engine::ClearSide;
496 let mut props = PropertyList::new();
497 props.set(PropertyId::Clear, PropertyValue::Enum(96));
499 let clear = extract_clear(&props);
500 assert!(matches!(clear, ClearSide::Right));
501 }
502
503 #[test]
504 fn test_extract_clear_both() {
505 use crate::layout::engine::ClearSide;
506 let mut props = PropertyList::new();
507 props.set(PropertyId::Clear, PropertyValue::Enum(19));
509 let clear = extract_clear(&props);
510 assert!(matches!(clear, ClearSide::Both));
511 }
512
513 #[test]
514 fn test_extract_clear_string_both() {
515 use crate::layout::engine::ClearSide;
516 let mut props = PropertyList::new();
517 props.set(
518 PropertyId::Clear,
519 PropertyValue::String(std::borrow::Cow::Borrowed("both")),
520 );
521 let clear = extract_clear(&props);
522 assert!(matches!(clear, ClearSide::Both));
523 }
524
525 #[test]
528 fn test_measure_text_width_empty() {
529 let width = measure_text_width("", Length::from_pt(12.0), None);
530 assert_eq!(width, Length::ZERO);
531 }
532
533 #[test]
534 fn test_measure_text_width_single_latin_char() {
535 let w = measure_text_width("A", Length::from_pt(12.0), None);
536 assert!((w.to_pt() - 6.0).abs() < 0.01);
538 }
539
540 #[test]
541 fn test_measure_text_width_cjk_wider_than_latin() {
542 let w_latin = measure_text_width("A", Length::from_pt(12.0), None);
543 let w_cjk = measure_text_width("\u{5b57}", Length::from_pt(12.0), None);
544 assert!(w_cjk > w_latin, "CJK char should be wider than Latin");
546 }
547
548 #[test]
549 fn test_measure_text_width_bold_wider() {
550 let w_normal = measure_text_width("Hello", Length::from_pt(12.0), None);
551 let w_bold = measure_text_width("Hello", Length::from_pt(12.0), Some(700));
552 assert!(
553 w_bold >= w_normal,
554 "Bold should be wider or equal to normal"
555 );
556 }
557
558 #[test]
559 fn test_measure_text_width_scales_with_font_size() {
560 let w_12 = measure_text_width("Hello", Length::from_pt(12.0), None);
561 let w_24 = measure_text_width("Hello", Length::from_pt(24.0), None);
562 let ratio = w_24.to_pt() / w_12.to_pt();
563 assert!(
564 (ratio - 2.0).abs() < 0.01,
565 "Width should scale linearly with font size, got ratio {}",
566 ratio
567 );
568 }
569
570 #[test]
571 fn test_measure_text_width_spaces_narrower_than_chars() {
572 let w_spaces = measure_text_width(" ", Length::from_pt(12.0), None);
573 let w_chars = measure_text_width("AAA", Length::from_pt(12.0), None);
574 assert!(
575 w_spaces < w_chars,
576 "Spaces should be narrower than regular chars"
577 );
578 }
579
580 #[test]
581 fn test_measure_text_width_narrow_chars() {
582 let w_narrow = measure_text_width("i", Length::from_pt(12.0), None);
584 let w_normal = measure_text_width("A", Length::from_pt(12.0), None);
585 assert!(w_narrow < w_normal, "Narrow chars should be narrower");
586 }
587
588 #[test]
589 fn test_measure_text_width_wide_chars() {
590 let w_wide = measure_text_width("M", Length::from_pt(12.0), None);
592 let w_normal = measure_text_width("A", Length::from_pt(12.0), None);
593 assert!(w_wide > w_normal, "Wide chars should be wider than normal");
594 }
595
596 #[test]
597 fn test_measure_text_width_proportional_to_length() {
598 let w_one = measure_text_width("A", Length::from_pt(12.0), None);
599 let w_three = measure_text_width("AAA", Length::from_pt(12.0), None);
600 assert!(
602 (w_three.to_pt() - 3.0 * w_one.to_pt()).abs() < 0.01,
603 "Width should be proportional to text length for same chars"
604 );
605 }
606
607 #[test]
608 fn test_measure_text_width_weight_below_600_normal() {
609 let w_no_weight = measure_text_width("Test", Length::from_pt(12.0), None);
611 let w_weight_400 = measure_text_width("Test", Length::from_pt(12.0), Some(400));
612 assert_eq!(w_no_weight, w_weight_400);
613 }
614
615 #[test]
618 fn test_extract_background_color() {
619 let mut props = PropertyList::new();
620 props.set(
621 PropertyId::BackgroundColor,
622 PropertyValue::Color(Color::rgba(0, 128, 255, 255)),
623 );
624
625 let traits = extract_traits(&props);
626 assert_eq!(traits.background_color, Some(Color::rgba(0, 128, 255, 255)));
627 }
628
629 #[test]
630 fn test_extract_font_style_italic() {
631 let mut props = PropertyList::new();
632 props.set(PropertyId::FontStyle, PropertyValue::Enum(1));
634
635 let traits = extract_traits(&props);
636 use crate::area::types::FontStyle;
637 assert_eq!(traits.font_style, Some(FontStyle::Italic));
638 }
639
640 #[test]
641 fn test_extract_font_style_oblique() {
642 let mut props = PropertyList::new();
643 props.set(PropertyId::FontStyle, PropertyValue::Enum(2));
645
646 let traits = extract_traits(&props);
647 use crate::area::types::FontStyle;
648 assert_eq!(traits.font_style, Some(FontStyle::Oblique));
649 }
650
651 #[test]
652 fn test_extract_font_weight_bold() {
653 let mut props = PropertyList::new();
654 props.set(PropertyId::FontWeight, PropertyValue::Integer(700));
655
656 let traits = extract_traits(&props);
657 assert_eq!(traits.font_weight, Some(700));
658 }
659
660 #[test]
661 fn test_extract_text_transform_uppercase() {
662 let mut props = PropertyList::new();
663 props.set(
664 PropertyId::TextTransform,
665 PropertyValue::String(std::borrow::Cow::Borrowed("uppercase")),
666 );
667
668 let traits = extract_traits(&props);
669 use crate::area::types::TextTransform;
670 assert_eq!(traits.text_transform, Some(TextTransform::Uppercase));
671 }
672
673 #[test]
674 fn test_extract_text_transform_lowercase() {
675 let mut props = PropertyList::new();
676 props.set(
677 PropertyId::TextTransform,
678 PropertyValue::String(std::borrow::Cow::Borrowed("lowercase")),
679 );
680
681 let traits = extract_traits(&props);
682 use crate::area::types::TextTransform;
683 assert_eq!(traits.text_transform, Some(TextTransform::Lowercase));
684 }
685
686 #[test]
687 fn test_extract_font_variant_small_caps() {
688 let mut props = PropertyList::new();
689 props.set(
690 PropertyId::FontVariant,
691 PropertyValue::String(std::borrow::Cow::Borrowed("small-caps")),
692 );
693
694 let traits = extract_traits(&props);
695 use crate::area::types::FontVariant;
696 assert_eq!(traits.font_variant, Some(FontVariant::SmallCaps));
697 }
698
699 #[test]
700 fn test_extract_display_align_center() {
701 let mut props = PropertyList::new();
702 props.set(
703 PropertyId::DisplayAlign,
704 PropertyValue::String(std::borrow::Cow::Borrowed("center")),
705 );
706
707 let traits = extract_traits(&props);
708 use crate::area::types::DisplayAlign;
709 assert_eq!(traits.display_align, Some(DisplayAlign::Center));
710 }
711
712 #[test]
713 fn test_extract_display_align_after() {
714 let mut props = PropertyList::new();
715 props.set(
716 PropertyId::DisplayAlign,
717 PropertyValue::String(std::borrow::Cow::Borrowed("after")),
718 );
719
720 let traits = extract_traits(&props);
721 use crate::area::types::DisplayAlign;
722 assert_eq!(traits.display_align, Some(DisplayAlign::After));
723 }
724
725 #[test]
726 fn test_extract_border_widths() {
727 let mut props = PropertyList::new();
728 props.set(
729 PropertyId::BorderTopWidth,
730 PropertyValue::Length(Length::from_pt(1.0)),
731 );
732 props.set(
733 PropertyId::BorderRightWidth,
734 PropertyValue::Length(Length::from_pt(2.0)),
735 );
736 props.set(
737 PropertyId::BorderBottomWidth,
738 PropertyValue::Length(Length::from_pt(3.0)),
739 );
740 props.set(
741 PropertyId::BorderLeftWidth,
742 PropertyValue::Length(Length::from_pt(4.0)),
743 );
744
745 let traits = extract_traits(&props);
746 let bw = traits.border_width.expect("test: should succeed");
747 assert_eq!(bw[0], Length::from_pt(1.0)); assert_eq!(bw[1], Length::from_pt(2.0)); assert_eq!(bw[2], Length::from_pt(3.0)); assert_eq!(bw[3], Length::from_pt(4.0)); }
752
753 #[test]
754 fn test_extract_writing_mode_rl() {
755 let mut props = PropertyList::new();
756 props.set(
757 PropertyId::WritingMode,
758 PropertyValue::String(std::borrow::Cow::Borrowed("rl-tb")),
759 );
760
761 let traits = extract_traits(&props);
762 use crate::area::types::WritingMode;
763 assert_eq!(traits.writing_mode, WritingMode::RlTb);
764 }
765
766 #[test]
767 fn test_extract_direction_rtl() {
768 let mut props = PropertyList::new();
769 props.set(
770 PropertyId::Direction,
771 PropertyValue::String(std::borrow::Cow::Borrowed("rtl")),
772 );
773
774 let traits = extract_traits(&props);
775 use crate::area::types::Direction;
776 assert_eq!(traits.direction, Direction::Rtl);
777 }
778
779 #[test]
780 fn test_extract_hyphenate_true() {
781 let mut props = PropertyList::new();
782 props.set(
783 PropertyId::Hyphenate,
784 PropertyValue::String(std::borrow::Cow::Borrowed("true")),
785 );
786
787 let traits = extract_traits(&props);
788 assert_eq!(traits.hyphenate, Some(true));
789 }
790
791 #[test]
792 fn test_extract_hyphenate_false() {
793 let mut props = PropertyList::new();
794 props.set(
795 PropertyId::Hyphenate,
796 PropertyValue::String(std::borrow::Cow::Borrowed("false")),
797 );
798
799 let traits = extract_traits(&props);
800 assert_eq!(traits.hyphenate, Some(false));
801 }
802
803 #[test]
804 fn test_extract_baseline_shift_super() {
805 let mut props = PropertyList::new();
806 props.set(
807 PropertyId::BaselineShift,
808 PropertyValue::String(std::borrow::Cow::Borrowed("super")),
809 );
810
811 let traits = extract_traits(&props);
812 assert_eq!(traits.baseline_shift, Some(0.5));
813 }
814
815 #[test]
816 fn test_extract_baseline_shift_sub() {
817 let mut props = PropertyList::new();
818 props.set(
819 PropertyId::BaselineShift,
820 PropertyValue::String(std::borrow::Cow::Borrowed("sub")),
821 );
822
823 let traits = extract_traits(&props);
824 assert_eq!(traits.baseline_shift, Some(-0.3));
825 }
826
827 #[test]
828 fn test_extract_traits_does_not_panic_on_empty_properties() {
829 let props = PropertyList::new();
832 let _traits = extract_traits(&props); }
834}
835
836#[cfg(test)]
838mod extended_tests {
839 use super::*;
840 use fop_core::{PropertyId, PropertyList, PropertyValue};
841 use fop_types::Length;
842
843 #[test]
846 fn test_extract_line_height_as_length() {
847 let mut props = PropertyList::new();
848 props.set(
849 PropertyId::LineHeight,
850 PropertyValue::Length(Length::from_pt(18.0)),
851 );
852 let lh = extract_line_height(&props);
853 assert_eq!(lh, Some(Length::from_pt(18.0)));
854 }
855
856 #[test]
857 fn test_extract_line_height_as_multiplier() {
858 let mut props = PropertyList::new();
859 props.set(
860 PropertyId::FontSize,
861 PropertyValue::Length(Length::from_pt(10.0)),
862 );
863 props.set(PropertyId::LineHeight, PropertyValue::Number(1.5));
864 let lh = extract_line_height(&props);
865 assert!(lh.is_some());
867 let pt = lh.expect("test: should succeed").to_pt();
868 assert!((pt - 15.0).abs() < 0.1, "Expected ~15pt, got {}pt", pt);
869 }
870
871 #[test]
872 fn test_extract_line_height_normal_keyword_returns_none() {
873 let mut props = PropertyList::new();
874 props.set(
875 PropertyId::LineHeight,
876 PropertyValue::String(std::borrow::Cow::Borrowed("normal")),
877 );
878 let lh = extract_line_height(&props);
879 assert_eq!(lh, None);
881 }
882
883 #[test]
884 fn test_extract_line_height_not_set_returns_none() {
885 let props = PropertyList::new();
886 let lh = extract_line_height(&props);
887 let _ = lh; }
891
892 #[test]
895 fn test_extract_start_indent() {
896 let mut props = PropertyList::new();
897 props.set(
898 PropertyId::StartIndent,
899 PropertyValue::Length(Length::from_pt(36.0)),
900 );
901 let indent = extract_start_indent(&props);
902 assert_eq!(indent, Length::from_pt(36.0));
903 }
904
905 #[test]
906 fn test_extract_start_indent_default_zero() {
907 let props = PropertyList::new();
908 let indent = extract_start_indent(&props);
909 assert_eq!(indent, Length::ZERO);
910 }
911
912 #[test]
913 fn test_extract_end_indent() {
914 let mut props = PropertyList::new();
915 props.set(
916 PropertyId::EndIndent,
917 PropertyValue::Length(Length::from_pt(18.0)),
918 );
919 let indent = extract_end_indent(&props);
920 assert_eq!(indent, Length::from_pt(18.0));
921 }
922
923 #[test]
924 fn test_extract_end_indent_default_zero() {
925 let props = PropertyList::new();
926 let indent = extract_end_indent(&props);
927 assert_eq!(indent, Length::ZERO);
928 }
929
930 #[test]
931 fn test_extract_text_indent() {
932 let mut props = PropertyList::new();
933 props.set(
934 PropertyId::TextIndent,
935 PropertyValue::Length(Length::from_pt(24.0)),
936 );
937 let indent = extract_text_indent(&props);
938 assert_eq!(indent, Length::from_pt(24.0));
939 }
940
941 #[test]
942 fn test_extract_text_indent_default_zero() {
943 let props = PropertyList::new();
944 let indent = extract_text_indent(&props);
945 assert_eq!(indent, Length::ZERO);
946 }
947
948 #[test]
951 fn test_extract_letter_spacing() {
952 let mut props = PropertyList::new();
953 props.set(
954 PropertyId::LetterSpacing,
955 PropertyValue::Length(Length::from_pt(2.0)),
956 );
957 let spacing = extract_letter_spacing(&props);
958 assert_eq!(spacing, Some(Length::from_pt(2.0)));
959 }
960
961 #[test]
962 fn test_extract_letter_spacing_not_set_returns_none() {
963 let props = PropertyList::new();
964 let spacing = extract_letter_spacing(&props);
965 assert_eq!(spacing, None);
966 }
967
968 #[test]
969 fn test_extract_word_spacing() {
970 let mut props = PropertyList::new();
971 props.set(
972 PropertyId::WordSpacing,
973 PropertyValue::Length(Length::from_pt(5.0)),
974 );
975 let spacing = extract_word_spacing(&props);
976 assert_eq!(spacing, Some(Length::from_pt(5.0)));
977 }
978
979 #[test]
980 fn test_extract_word_spacing_not_set_returns_none() {
981 let props = PropertyList::new();
982 let spacing = extract_word_spacing(&props);
983 assert_eq!(spacing, None);
984 }
985
986 #[test]
989 fn test_extract_widows_default_is_two() {
990 let props = PropertyList::new();
991 let widows = extract_widows(&props);
992 assert_eq!(widows, 2);
993 }
994
995 #[test]
996 fn test_extract_widows_custom() {
997 let mut props = PropertyList::new();
998 props.set(PropertyId::Widows, PropertyValue::Integer(4));
999 let widows = extract_widows(&props);
1000 assert_eq!(widows, 4);
1001 }
1002
1003 #[test]
1004 fn test_extract_orphans_default_is_two() {
1005 let props = PropertyList::new();
1006 let orphans = extract_orphans(&props);
1007 assert_eq!(orphans, 2);
1008 }
1009
1010 #[test]
1011 fn test_extract_orphans_custom() {
1012 let mut props = PropertyList::new();
1013 props.set(PropertyId::Orphans, PropertyValue::Integer(3));
1014 let orphans = extract_orphans(&props);
1015 assert_eq!(orphans, 3);
1016 }
1017
1018 #[test]
1021 fn test_extract_column_count_default_is_one() {
1022 let props = PropertyList::new();
1023 let count = extract_column_count(&props);
1024 assert_eq!(count, 1);
1025 }
1026
1027 #[test]
1028 fn test_extract_column_count_custom() {
1029 let mut props = PropertyList::new();
1030 props.set(PropertyId::ColumnCount, PropertyValue::Integer(3));
1031 let count = extract_column_count(&props);
1032 assert_eq!(count, 3);
1033 }
1034
1035 #[test]
1036 fn test_extract_column_count_zero_clamps_to_one() {
1037 let mut props = PropertyList::new();
1038 props.set(PropertyId::ColumnCount, PropertyValue::Integer(0));
1039 let count = extract_column_count(&props);
1040 assert_eq!(count, 1, "Column count should be at least 1");
1041 }
1042
1043 #[test]
1044 fn test_extract_column_gap_default() {
1045 let props = PropertyList::new();
1046 let gap = extract_column_gap(&props);
1047 assert_eq!(gap, Length::from_pt(12.0));
1048 }
1049
1050 #[test]
1051 fn test_extract_column_gap_custom() {
1052 let mut props = PropertyList::new();
1053 props.set(
1054 PropertyId::ColumnGap,
1055 PropertyValue::Length(Length::from_pt(24.0)),
1056 );
1057 let gap = extract_column_gap(&props);
1058 assert_eq!(gap, Length::from_pt(24.0));
1059 }
1060
1061 #[test]
1064 fn test_extract_opacity_default_is_one() {
1065 let props = PropertyList::new();
1066 let opacity = extract_opacity(&props);
1067 assert_eq!(opacity, 1.0);
1068 }
1069
1070 #[test]
1071 fn test_extract_opacity_custom() {
1072 let mut props = PropertyList::new();
1075 props.set(PropertyId::Opacity, PropertyValue::Number(0.5));
1076 let opacity = extract_opacity(&props);
1077 assert_eq!(opacity, 1.0);
1079 }
1080
1081 #[test]
1082 fn test_extract_opacity_clamps_above_one() {
1083 assert!(OverflowBehavior::Hidden.clips_content());
1085 assert!(OverflowBehavior::Scroll.clips_content());
1086 assert!(!OverflowBehavior::Visible.clips_content());
1087 assert!(!OverflowBehavior::Auto.clips_content());
1088 }
1089
1090 #[test]
1091 fn test_extract_opacity_clamps_below_zero() {
1092 let props = PropertyList::new();
1094 let opacity = extract_opacity(&props);
1095 assert!(
1096 (0.0..=1.0).contains(&opacity),
1097 "Opacity must be in [0,1], got {}",
1098 opacity
1099 );
1100 }
1101
1102 #[test]
1105 fn test_extract_overflow_default_is_visible() {
1106 let props = PropertyList::new();
1107 let overflow = extract_overflow(&props);
1108 assert_eq!(overflow, OverflowBehavior::Visible);
1109 }
1110
1111 #[test]
1112 fn test_extract_overflow_hidden() {
1113 let mut props = PropertyList::new();
1114 props.set(
1115 PropertyId::Overflow,
1116 PropertyValue::String(std::borrow::Cow::Borrowed("hidden")),
1117 );
1118 let overflow = extract_overflow(&props);
1119 assert_eq!(overflow, OverflowBehavior::Hidden);
1120 assert!(overflow.clips_content());
1121 }
1122
1123 #[test]
1124 fn test_extract_overflow_scroll() {
1125 let mut props = PropertyList::new();
1126 props.set(
1127 PropertyId::Overflow,
1128 PropertyValue::String(std::borrow::Cow::Borrowed("scroll")),
1129 );
1130 let overflow = extract_overflow(&props);
1131 assert_eq!(overflow, OverflowBehavior::Scroll);
1132 assert!(overflow.clips_content());
1133 }
1134
1135 #[test]
1136 fn test_overflow_visible_does_not_clip() {
1137 assert!(!OverflowBehavior::Visible.clips_content());
1138 assert!(!OverflowBehavior::Auto.clips_content());
1139 }
1140
1141 #[test]
1144 fn test_extract_border_radius_uniform() {
1145 let mut props = PropertyList::new();
1146 props.set(
1147 PropertyId::XBorderRadius,
1148 PropertyValue::Length(Length::from_pt(5.0)),
1149 );
1150 let radii = extract_border_radius(&props);
1151 assert!(radii.is_some());
1152 let r = radii.expect("test: should succeed");
1153 assert_eq!(r[0], Length::from_pt(5.0));
1154 assert_eq!(r[1], Length::from_pt(5.0));
1155 assert_eq!(r[2], Length::from_pt(5.0));
1156 assert_eq!(r[3], Length::from_pt(5.0));
1157 }
1158
1159 #[test]
1160 fn test_extract_border_radius_none_when_all_zero() {
1161 let props = PropertyList::new();
1162 let radii = extract_border_radius(&props);
1163 assert_eq!(radii, None, "All-zero radii should return None");
1164 }
1165
1166 #[test]
1167 fn test_extract_border_radius_individual_corners() {
1168 let mut props = PropertyList::new();
1169 props.set(
1170 PropertyId::XBorderBeforeStartRadius,
1171 PropertyValue::Length(Length::from_pt(10.0)),
1172 );
1173 props.set(
1174 PropertyId::XBorderBeforeEndRadius,
1175 PropertyValue::Length(Length::from_pt(20.0)),
1176 );
1177 let radii = extract_border_radius(&props);
1178 assert!(radii.is_some());
1179 let r = radii.expect("test: should succeed");
1180 assert_eq!(r[0], Length::from_pt(10.0)); assert_eq!(r[1], Length::from_pt(20.0)); }
1183
1184 #[test]
1187 fn test_break_value_auto_does_not_force() {
1188 assert!(!BreakValue::Auto.forces_break());
1189 assert!(!BreakValue::Auto.forces_page_break());
1190 }
1191
1192 #[test]
1193 fn test_break_value_page_forces_break() {
1194 assert!(BreakValue::Page.forces_break());
1195 assert!(BreakValue::Page.forces_page_break());
1196 assert!(!BreakValue::Page.requires_even_page());
1197 assert!(!BreakValue::Page.requires_odd_page());
1198 }
1199
1200 #[test]
1201 fn test_break_value_column_forces_break_not_page() {
1202 assert!(BreakValue::Column.forces_break());
1203 assert!(!BreakValue::Column.forces_page_break());
1204 }
1205
1206 #[test]
1207 fn test_break_value_even_page() {
1208 assert!(BreakValue::EvenPage.forces_page_break());
1209 assert!(BreakValue::EvenPage.requires_even_page());
1210 assert!(!BreakValue::EvenPage.requires_odd_page());
1211 }
1212
1213 #[test]
1214 fn test_break_value_odd_page() {
1215 assert!(BreakValue::OddPage.forces_page_break());
1216 assert!(!BreakValue::OddPage.requires_even_page());
1217 assert!(BreakValue::OddPage.requires_odd_page());
1218 }
1219
1220 #[test]
1221 fn test_extract_break_before_page() {
1222 let mut props = PropertyList::new();
1223 props.set(
1224 PropertyId::BreakBefore,
1225 PropertyValue::String(std::borrow::Cow::Borrowed("page")),
1226 );
1227 let bv = extract_break_before(&props);
1228 assert_eq!(bv, BreakValue::Page);
1229 }
1230
1231 #[test]
1232 fn test_extract_break_after_page() {
1233 let mut props = PropertyList::new();
1234 props.set(
1235 PropertyId::BreakAfter,
1236 PropertyValue::String(std::borrow::Cow::Borrowed("page")),
1237 );
1238 let bv = extract_break_after(&props);
1239 assert_eq!(bv, BreakValue::Page);
1240 }
1241
1242 #[test]
1243 fn test_extract_break_before_default_auto() {
1244 let props = PropertyList::new();
1245 let bv = extract_break_before(&props);
1246 assert_eq!(bv, BreakValue::Auto);
1247 }
1248
1249 #[test]
1250 fn test_extract_break_after_default_auto() {
1251 let props = PropertyList::new();
1252 let bv = extract_break_after(&props);
1253 assert_eq!(bv, BreakValue::Auto);
1254 }
1255
1256 #[test]
1259 fn test_keep_strength_ordering() {
1260 assert!(Keep::Always.strength() > Keep::Integer(100).strength());
1261 assert!(Keep::Integer(100).strength() > Keep::Integer(1).strength());
1262 assert!(Keep::Integer(1).strength() > Keep::Auto.strength());
1263 }
1264
1265 #[test]
1266 fn test_keep_constraint_has_constraint_false_when_all_auto() {
1267 let constraint = KeepConstraint::new();
1268 assert!(!constraint.has_constraint());
1269 }
1270
1271 #[test]
1272 fn test_keep_constraint_has_constraint_true_when_keep_together() {
1273 let mut constraint = KeepConstraint::new();
1274 constraint.keep_together = Keep::Always;
1275 assert!(constraint.has_constraint());
1276 }
1277
1278 #[test]
1279 fn test_keep_constraint_has_constraint_true_when_keep_with_next() {
1280 let mut constraint = KeepConstraint::new();
1281 constraint.keep_with_next = Keep::Integer(5);
1282 assert!(constraint.has_constraint());
1283 }
1284
1285 #[test]
1286 fn test_keep_constraint_has_constraint_true_when_keep_with_previous() {
1287 let mut constraint = KeepConstraint::new();
1288 constraint.keep_with_previous = Keep::Always;
1289 assert!(constraint.has_constraint());
1290 }
1291
1292 #[test]
1295 fn test_extract_traits_xml_lang() {
1296 let mut props = PropertyList::new();
1297 props.set(
1298 PropertyId::XmlLang,
1299 PropertyValue::String(std::borrow::Cow::Borrowed("ja")),
1300 );
1301 let traits = extract_traits(&props);
1302 assert_eq!(traits.xml_lang, Some("ja".to_string()));
1303 }
1304
1305 #[test]
1306 fn test_extract_traits_role() {
1307 let mut props = PropertyList::new();
1308 props.set(
1309 PropertyId::Role,
1310 PropertyValue::String(std::borrow::Cow::Borrowed("Heading")),
1311 );
1312 let traits = extract_traits(&props);
1313 assert_eq!(traits.role, Some("Heading".to_string()));
1314 }
1315
1316 #[test]
1317 fn test_extract_traits_writing_mode_tb_rl() {
1318 let mut props = PropertyList::new();
1319 props.set(
1320 PropertyId::WritingMode,
1321 PropertyValue::String(std::borrow::Cow::Borrowed("tb-rl")),
1322 );
1323 let traits = extract_traits(&props);
1324 use crate::area::types::WritingMode;
1325 assert_eq!(traits.writing_mode, WritingMode::TbRl);
1326 }
1327
1328 #[test]
1329 fn test_extract_traits_writing_mode_lr_tb_default() {
1330 let props = PropertyList::new();
1331 let traits = extract_traits(&props);
1332 use crate::area::types::WritingMode;
1333 assert_eq!(traits.writing_mode, WritingMode::LrTb);
1334 }
1335
1336 #[test]
1337 fn test_extract_traits_direction_ltr_default() {
1338 let props = PropertyList::new();
1339 let traits = extract_traits(&props);
1340 use crate::area::types::Direction;
1341 assert_eq!(traits.direction, Direction::Ltr);
1342 }
1343
1344 #[test]
1345 fn test_extract_traits_span_all() {
1346 let mut props = PropertyList::new();
1347 props.set(
1348 PropertyId::Span,
1349 PropertyValue::String(std::borrow::Cow::Borrowed("all")),
1350 );
1351 let traits = extract_traits(&props);
1352 use crate::area::types::Span;
1353 assert_eq!(traits.span, Span::All);
1354 }
1355
1356 #[test]
1357 fn test_extract_traits_font_stretch_condensed() {
1358 let mut props = PropertyList::new();
1359 props.set(
1360 PropertyId::FontStretch,
1361 PropertyValue::String(std::borrow::Cow::Borrowed("condensed")),
1362 );
1363 let traits = extract_traits(&props);
1364 use crate::area::types::FontStretch;
1365 assert_eq!(traits.font_stretch, Some(FontStretch::Condensed));
1366 }
1367
1368 #[test]
1369 fn test_extract_traits_text_align_last_left() {
1370 let mut props = PropertyList::new();
1371 props.set(
1372 PropertyId::TextAlignLast,
1373 PropertyValue::String(std::borrow::Cow::Borrowed("left")),
1374 );
1375 let traits = extract_traits(&props);
1376 use crate::layout::inline::TextAlign;
1377 assert_eq!(traits.text_align_last, Some(TextAlign::Left));
1378 }
1379
1380 #[test]
1383 fn test_measure_text_width_empty_string() {
1384 let w = measure_text_width("", Length::from_pt(12.0), None);
1385 assert_eq!(w, Length::ZERO);
1386 }
1387
1388 #[test]
1389 fn test_measure_text_width_digits() {
1390 let w = measure_text_width("5", Length::from_pt(12.0), None);
1392 assert!(w.to_pt() > 0.0);
1393 }
1394
1395 #[test]
1396 fn test_measure_text_width_punctuation() {
1397 let w_punct = measure_text_width(".", Length::from_pt(12.0), None);
1399 let w_normal = measure_text_width("A", Length::from_pt(12.0), None);
1400 assert!(w_punct.to_pt() > 0.0);
1401 assert!(w_punct <= w_normal);
1402 }
1403}