1use crate::layout::{properties::OverflowBehavior, TextAlign};
6use fop_types::{Color, Length, Rect};
7
8#[derive(Debug, Clone)]
10pub enum AreaContent {
11 Text(String),
13
14 ImageData(Vec<u8>),
16}
17
18#[derive(Debug, Clone)]
20pub struct Area {
21 pub area_type: AreaType,
23
24 pub geometry: Rect,
26
27 pub traits: TraitSet,
29
30 pub content: Option<AreaContent>,
32
33 pub keep_constraint: Option<crate::layout::KeepConstraint>,
35
36 pub break_before: Option<crate::layout::BreakValue>,
38
39 pub break_after: Option<crate::layout::BreakValue>,
41
42 pub widows: i32,
44
45 pub orphans: i32,
47}
48
49impl Area {
50 pub fn new(area_type: AreaType, geometry: Rect) -> Self {
52 Self {
53 area_type,
54 geometry,
55 traits: TraitSet::default(),
56 content: None,
57 keep_constraint: None,
58 break_before: None,
59 break_after: None,
60 widows: 2,
61 orphans: 2,
62 }
63 }
64
65 pub fn text(geometry: Rect, content: String) -> Self {
67 Self {
68 area_type: AreaType::Text,
69 geometry,
70 traits: TraitSet::default(),
71 content: Some(AreaContent::Text(content)),
72 keep_constraint: None,
73 break_before: None,
74 break_after: None,
75 widows: 2,
76 orphans: 2,
77 }
78 }
79
80 pub fn viewport_with_image(geometry: Rect, image_data: Vec<u8>) -> Self {
82 Self {
83 area_type: AreaType::Viewport,
84 geometry,
85 traits: TraitSet::default(),
86 content: Some(AreaContent::ImageData(image_data)),
87 keep_constraint: None,
88 break_before: None,
89 break_after: None,
90 widows: 2,
91 orphans: 2,
92 }
93 }
94
95 pub fn with_traits(mut self, traits: TraitSet) -> Self {
97 self.traits = traits;
98 self
99 }
100
101 pub fn with_keep_constraint(mut self, keep_constraint: crate::layout::KeepConstraint) -> Self {
103 self.keep_constraint = Some(keep_constraint);
104 self
105 }
106
107 pub fn with_break_before(mut self, break_before: crate::layout::BreakValue) -> Self {
109 self.break_before = Some(break_before);
110 self
111 }
112
113 pub fn with_break_after(mut self, break_after: crate::layout::BreakValue) -> Self {
115 self.break_after = Some(break_after);
116 self
117 }
118
119 pub fn with_widows(mut self, widows: i32) -> Self {
121 self.widows = widows;
122 self
123 }
124
125 pub fn with_orphans(mut self, orphans: i32) -> Self {
127 self.orphans = orphans;
128 self
129 }
130
131 pub fn has_text(&self) -> bool {
133 matches!(self.content, Some(AreaContent::Text(_)))
134 }
135
136 pub fn has_image_data(&self) -> bool {
138 matches!(self.content, Some(AreaContent::ImageData(_)))
139 }
140
141 pub fn text_content(&self) -> Option<&str> {
143 match &self.content {
144 Some(AreaContent::Text(s)) => Some(s),
145 _ => None,
146 }
147 }
148
149 pub fn image_data(&self) -> Option<&[u8]> {
151 match &self.content {
152 Some(AreaContent::ImageData(data)) => Some(data),
153 _ => None,
154 }
155 }
156
157 pub fn width(&self) -> Length {
159 self.geometry.width
160 }
161
162 pub fn height(&self) -> Length {
164 self.geometry.height
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum AreaType {
171 Page,
173
174 Region,
176
177 Header,
179
180 Footer,
182
183 Block,
185
186 Line,
188
189 Inline,
191
192 Text,
194
195 Space,
197
198 Viewport,
200
201 Footnote,
203
204 FootnoteSeparator,
206
207 Column,
209
210 FloatArea,
212
213 SidebarStart,
215
216 SidebarEnd,
218}
219
220#[derive(Debug, Clone, Default)]
224pub struct TraitSet {
225 pub color: Option<Color>,
227
228 pub background_color: Option<Color>,
230
231 pub font_family: Option<String>,
233
234 pub font_size: Option<Length>,
236
237 pub font_weight: Option<u16>,
239
240 pub font_style: Option<FontStyle>,
242
243 pub text_decoration: Option<TextDecoration>,
245
246 pub border_width: Option<[Length; 4]>,
248
249 pub border_color: Option<[Color; 4]>,
251
252 pub border_style: Option<[BorderStyle; 4]>,
254
255 pub padding: Option<[Length; 4]>,
257
258 pub text_align: Option<TextAlign>,
260
261 pub link_destination: Option<String>,
263
264 pub is_leader: Option<String>,
266
267 pub rule_thickness: Option<Length>,
269
270 pub rule_style: Option<String>,
272
273 pub line_height: Option<Length>,
275
276 pub letter_spacing: Option<Length>,
278
279 pub word_spacing: Option<Length>,
281
282 pub border_radius: Option<[Length; 4]>,
285
286 pub overflow: Option<OverflowBehavior>,
288
289 pub opacity: Option<f64>,
291
292 pub text_transform: Option<TextTransform>,
294
295 pub font_variant: Option<FontVariant>,
297
298 pub display_align: Option<DisplayAlign>,
300
301 pub baseline_shift: Option<f64>,
303
304 pub hyphenate: Option<bool>,
306
307 pub hyphenation_min_word_chars: Option<u32>,
309
310 pub hyphenation_push_chars: Option<u32>,
312
313 pub hyphenation_remain_chars: Option<u32>,
315
316 pub font_stretch: Option<FontStretch>,
318
319 pub text_align_last: Option<TextAlign>,
321
322 pub change_bar_color: Option<fop_types::Color>,
324
325 pub span: Span,
327
328 pub role: Option<String>,
330
331 pub xml_lang: Option<String>,
333
334 pub writing_mode: WritingMode,
336
337 pub direction: Direction,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
343pub enum WritingMode {
344 #[default]
346 LrTb,
347 RlTb,
349 TbRl,
351 TbLr,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
357pub enum Direction {
358 #[default]
360 Ltr,
361 Rtl,
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
367pub enum Span {
368 #[default]
370 None,
371 All,
373}
374
375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub enum FontStyle {
378 Normal,
379 Italic,
380 Oblique,
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq)]
385pub enum BorderStyle {
386 None,
388 Solid,
390 Dashed,
392 Dotted,
394 Double,
396 Groove,
398 Ridge,
400 Inset,
402 Outset,
404 Hidden,
406}
407
408#[derive(Debug, Clone, Copy, PartialEq, Eq)]
410pub struct TextDecoration {
411 pub underline: bool,
412 pub overline: bool,
413 pub line_through: bool,
414}
415
416impl TextDecoration {
417 pub const NONE: Self = Self {
418 underline: false,
419 overline: false,
420 line_through: false,
421 };
422
423 pub const UNDERLINE: Self = Self {
424 underline: true,
425 overline: false,
426 line_through: false,
427 };
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
432pub enum TextTransform {
433 #[default]
434 None,
435 Uppercase,
436 Lowercase,
437 Capitalize,
438}
439
440#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
442pub enum FontVariant {
443 #[default]
444 Normal,
445 SmallCaps,
446}
447
448#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
450pub enum FontStretch {
451 UltraCondensed,
452 ExtraCondensed,
453 Condensed,
454 SemiCondensed,
455 #[default]
456 Normal,
457 SemiExpanded,
458 Expanded,
459 ExtraExpanded,
460 UltraExpanded,
461}
462
463#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
465pub enum DisplayAlign {
466 #[default]
467 Before,
468 Center,
469 After,
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475 use fop_types::{Length, Point, Size};
476
477 #[test]
478 fn test_area_creation() {
479 let rect = Rect::from_point_size(
480 Point::new(Length::from_pt(10.0), Length::from_pt(20.0)),
481 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
482 );
483
484 let area = Area::new(AreaType::Block, rect);
485
486 assert_eq!(area.area_type, AreaType::Block);
487 assert_eq!(area.width(), Length::from_pt(100.0));
488 assert_eq!(area.height(), Length::from_pt(50.0));
489 assert!(!area.has_text());
490 }
491
492 #[test]
493 fn test_text_area() {
494 let rect = Rect::from_point_size(
495 Point::ZERO,
496 Size::new(Length::from_pt(50.0), Length::from_pt(12.0)),
497 );
498
499 let area = Area::text(rect, "Hello".to_string());
500
501 assert_eq!(area.area_type, AreaType::Text);
502 assert!(area.has_text());
503 assert_eq!(area.text_content().expect("test: should succeed"), "Hello");
504 }
505
506 #[test]
507 fn test_traits() {
508 let traits = TraitSet {
509 color: Some(Color::RED),
510 font_size: Some(Length::from_pt(12.0)),
511 ..Default::default()
512 };
513
514 assert_eq!(traits.color, Some(Color::RED));
515 assert_eq!(traits.font_size, Some(Length::from_pt(12.0)));
516 }
517}
518
519#[cfg(test)]
520mod extended_tests {
521 use super::*;
522 use fop_types::{Length, Point, Rect, Size};
523
524 #[test]
527 fn test_area_with_traits() {
528 let rect = Rect::from_point_size(
529 Point::ZERO,
530 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
531 );
532 let traits = TraitSet {
533 font_size: Some(Length::from_pt(14.0)),
534 ..Default::default()
535 };
536 let area = Area::new(AreaType::Block, rect).with_traits(traits);
537 assert_eq!(area.traits.font_size, Some(Length::from_pt(14.0)));
538 }
539
540 #[test]
541 fn test_area_widows_orphans_defaults() {
542 let rect = Rect::from_point_size(
543 Point::ZERO,
544 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
545 );
546 let area = Area::new(AreaType::Block, rect);
547 assert_eq!(area.widows, 2);
548 assert_eq!(area.orphans, 2);
549 }
550
551 #[test]
552 fn test_area_with_widows_and_orphans() {
553 let rect = Rect::from_point_size(
554 Point::ZERO,
555 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
556 );
557 let area = Area::new(AreaType::Block, rect)
558 .with_widows(4)
559 .with_orphans(3);
560 assert_eq!(area.widows, 4);
561 assert_eq!(area.orphans, 3);
562 }
563
564 #[test]
565 fn test_area_break_before_none_by_default() {
566 let rect = Rect::from_point_size(
567 Point::ZERO,
568 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
569 );
570 let area = Area::new(AreaType::Block, rect);
571 assert!(area.break_before.is_none());
572 assert!(area.break_after.is_none());
573 }
574
575 #[test]
576 fn test_area_with_break_before() {
577 use crate::layout::BreakValue;
578 let rect = Rect::from_point_size(
579 Point::ZERO,
580 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
581 );
582 let area = Area::new(AreaType::Block, rect).with_break_before(BreakValue::Page);
583 assert!(area.break_before.is_some());
584 }
585
586 #[test]
587 fn test_area_with_break_after() {
588 use crate::layout::BreakValue;
589 let rect = Rect::from_point_size(
590 Point::ZERO,
591 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
592 );
593 let area = Area::new(AreaType::Block, rect).with_break_after(BreakValue::Page);
594 assert!(area.break_after.is_some());
595 }
596
597 #[test]
598 fn test_area_viewport_with_image() {
599 let rect = Rect::from_point_size(
600 Point::ZERO,
601 Size::new(Length::from_pt(50.0), Length::from_pt(50.0)),
602 );
603 let image_data = vec![0u8, 1, 2, 3, 4];
604 let area = Area::viewport_with_image(rect, image_data.clone());
605 assert_eq!(area.area_type, AreaType::Viewport);
606 assert!(area.has_image_data());
607 assert_eq!(
608 area.image_data().expect("test: should succeed"),
609 image_data.as_slice()
610 );
611 }
612
613 #[test]
614 fn test_area_text_content_none_for_non_text() {
615 let rect = Rect::from_point_size(
616 Point::ZERO,
617 Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
618 );
619 let area = Area::new(AreaType::Block, rect);
620 assert!(area.text_content().is_none());
621 assert!(!area.has_text());
622 assert!(!area.has_image_data());
623 }
624
625 #[test]
626 fn test_area_image_data_none_for_text_area() {
627 let rect = Rect::from_point_size(
628 Point::ZERO,
629 Size::new(Length::from_pt(50.0), Length::from_pt(12.0)),
630 );
631 let area = Area::text(rect, "Test".to_string());
632 assert!(area.image_data().is_none());
633 }
634
635 #[test]
638 fn test_traitset_default_all_none() {
639 let traits = TraitSet::default();
640 assert!(traits.color.is_none());
641 assert!(traits.background_color.is_none());
642 assert!(traits.font_family.is_none());
643 assert!(traits.font_size.is_none());
644 assert!(traits.font_weight.is_none());
645 assert!(traits.font_style.is_none());
646 assert!(traits.text_decoration.is_none());
647 assert!(traits.border_width.is_none());
648 assert!(traits.padding.is_none());
649 assert!(traits.text_align.is_none());
650 assert!(traits.line_height.is_none());
651 assert!(traits.letter_spacing.is_none());
652 assert!(traits.word_spacing.is_none());
653 }
654
655 #[test]
656 fn test_traitset_writing_mode_default_lr_tb() {
657 let traits = TraitSet::default();
658 assert_eq!(traits.writing_mode, WritingMode::LrTb);
659 }
660
661 #[test]
662 fn test_traitset_direction_default_ltr() {
663 let traits = TraitSet::default();
664 assert_eq!(traits.direction, Direction::Ltr);
665 }
666
667 #[test]
668 fn test_traitset_span_default_none() {
669 let traits = TraitSet::default();
670 assert_eq!(traits.span, Span::None);
671 }
672
673 #[test]
674 fn test_traitset_display_align_default_before() {
675 let traits = TraitSet::default();
676 assert_eq!(traits.display_align, None);
678 }
679
680 #[test]
681 fn test_traitset_font_variant_default_normal() {
682 let traits = TraitSet::default();
683 assert_eq!(traits.font_variant, None);
685 }
686
687 #[test]
688 fn test_traitset_text_transform_default_none() {
689 let traits = TraitSet::default();
690 assert_eq!(traits.text_transform, None);
692 }
693
694 #[test]
695 fn test_traitset_font_stretch_default_normal() {
696 let traits = TraitSet::default();
697 assert_eq!(traits.font_stretch, None);
699 }
700
701 #[test]
704 fn test_area_types_are_distinct() {
705 assert_ne!(AreaType::Page, AreaType::Region);
706 assert_ne!(AreaType::Block, AreaType::Line);
707 assert_ne!(AreaType::Inline, AreaType::Text);
708 assert_ne!(AreaType::Header, AreaType::Footer);
709 assert_ne!(AreaType::Column, AreaType::Footnote);
710 }
711
712 #[test]
713 fn test_area_width_and_height() {
714 let rect = Rect::from_point_size(
715 Point::new(Length::from_pt(5.0), Length::from_pt(10.0)),
716 Size::new(Length::from_pt(200.0), Length::from_pt(100.0)),
717 );
718 let area = Area::new(AreaType::Page, rect);
719 assert_eq!(area.width(), Length::from_pt(200.0));
720 assert_eq!(area.height(), Length::from_pt(100.0));
721 }
722
723 #[test]
726 fn test_text_decoration_none() {
727 let td = TextDecoration::NONE;
728 assert!(!td.underline);
729 assert!(!td.overline);
730 assert!(!td.line_through);
731 }
732
733 #[test]
734 fn test_text_decoration_underline() {
735 let td = TextDecoration::UNDERLINE;
736 assert!(td.underline);
737 assert!(!td.overline);
738 assert!(!td.line_through);
739 }
740
741 #[test]
742 fn test_text_decoration_custom() {
743 let td = TextDecoration {
744 underline: false,
745 overline: true,
746 line_through: true,
747 };
748 assert!(!td.underline);
749 assert!(td.overline);
750 assert!(td.line_through);
751 }
752
753 #[test]
756 fn test_font_style_variants() {
757 assert_ne!(FontStyle::Normal, FontStyle::Italic);
758 assert_ne!(FontStyle::Italic, FontStyle::Oblique);
759 assert_ne!(FontStyle::Normal, FontStyle::Oblique);
760 }
761
762 #[test]
763 fn test_border_style_variants() {
764 assert_ne!(BorderStyle::None, BorderStyle::Solid);
765 assert_ne!(BorderStyle::Dashed, BorderStyle::Dotted);
766 assert_ne!(BorderStyle::Hidden, BorderStyle::None);
767 }
768}