use crate::layout::{properties::OverflowBehavior, TextAlign};
use fop_types::{Color, Length, Rect};
#[derive(Debug, Clone)]
pub enum AreaContent {
Text(String),
ImageData(Vec<u8>),
}
#[derive(Debug, Clone)]
pub struct Area {
pub area_type: AreaType,
pub geometry: Rect,
pub traits: TraitSet,
pub content: Option<AreaContent>,
pub keep_constraint: Option<crate::layout::KeepConstraint>,
pub break_before: Option<crate::layout::BreakValue>,
pub break_after: Option<crate::layout::BreakValue>,
pub widows: i32,
pub orphans: i32,
}
impl Area {
pub fn new(area_type: AreaType, geometry: Rect) -> Self {
Self {
area_type,
geometry,
traits: TraitSet::default(),
content: None,
keep_constraint: None,
break_before: None,
break_after: None,
widows: 2,
orphans: 2,
}
}
pub fn text(geometry: Rect, content: String) -> Self {
Self {
area_type: AreaType::Text,
geometry,
traits: TraitSet::default(),
content: Some(AreaContent::Text(content)),
keep_constraint: None,
break_before: None,
break_after: None,
widows: 2,
orphans: 2,
}
}
pub fn viewport_with_image(geometry: Rect, image_data: Vec<u8>) -> Self {
Self {
area_type: AreaType::Viewport,
geometry,
traits: TraitSet::default(),
content: Some(AreaContent::ImageData(image_data)),
keep_constraint: None,
break_before: None,
break_after: None,
widows: 2,
orphans: 2,
}
}
pub fn with_traits(mut self, traits: TraitSet) -> Self {
self.traits = traits;
self
}
pub fn with_keep_constraint(mut self, keep_constraint: crate::layout::KeepConstraint) -> Self {
self.keep_constraint = Some(keep_constraint);
self
}
pub fn with_break_before(mut self, break_before: crate::layout::BreakValue) -> Self {
self.break_before = Some(break_before);
self
}
pub fn with_break_after(mut self, break_after: crate::layout::BreakValue) -> Self {
self.break_after = Some(break_after);
self
}
pub fn with_widows(mut self, widows: i32) -> Self {
self.widows = widows;
self
}
pub fn with_orphans(mut self, orphans: i32) -> Self {
self.orphans = orphans;
self
}
pub fn has_text(&self) -> bool {
matches!(self.content, Some(AreaContent::Text(_)))
}
pub fn has_image_data(&self) -> bool {
matches!(self.content, Some(AreaContent::ImageData(_)))
}
pub fn text_content(&self) -> Option<&str> {
match &self.content {
Some(AreaContent::Text(s)) => Some(s),
_ => None,
}
}
pub fn image_data(&self) -> Option<&[u8]> {
match &self.content {
Some(AreaContent::ImageData(data)) => Some(data),
_ => None,
}
}
pub fn width(&self) -> Length {
self.geometry.width
}
pub fn height(&self) -> Length {
self.geometry.height
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AreaType {
Page,
Region,
Header,
Footer,
Block,
Line,
Inline,
Text,
Space,
Viewport,
Footnote,
FootnoteSeparator,
Column,
FloatArea,
SidebarStart,
SidebarEnd,
}
#[derive(Debug, Clone, Default)]
pub struct TraitSet {
pub color: Option<Color>,
pub background_color: Option<Color>,
pub font_family: Option<String>,
pub font_size: Option<Length>,
pub font_weight: Option<u16>,
pub font_style: Option<FontStyle>,
pub text_decoration: Option<TextDecoration>,
pub border_width: Option<[Length; 4]>,
pub border_color: Option<[Color; 4]>,
pub border_style: Option<[BorderStyle; 4]>,
pub padding: Option<[Length; 4]>,
pub text_align: Option<TextAlign>,
pub link_destination: Option<String>,
pub is_leader: Option<String>,
pub rule_thickness: Option<Length>,
pub rule_style: Option<String>,
pub line_height: Option<Length>,
pub letter_spacing: Option<Length>,
pub word_spacing: Option<Length>,
pub border_radius: Option<[Length; 4]>,
pub overflow: Option<OverflowBehavior>,
pub opacity: Option<f64>,
pub text_transform: Option<TextTransform>,
pub font_variant: Option<FontVariant>,
pub display_align: Option<DisplayAlign>,
pub baseline_shift: Option<f64>,
pub hyphenate: Option<bool>,
pub hyphenation_min_word_chars: Option<u32>,
pub hyphenation_push_chars: Option<u32>,
pub hyphenation_remain_chars: Option<u32>,
pub font_stretch: Option<FontStretch>,
pub text_align_last: Option<TextAlign>,
pub change_bar_color: Option<fop_types::Color>,
pub span: Span,
pub role: Option<String>,
pub xml_lang: Option<String>,
pub writing_mode: WritingMode,
pub direction: Direction,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WritingMode {
#[default]
LrTb,
RlTb,
TbRl,
TbLr,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Direction {
#[default]
Ltr,
Rtl,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Span {
#[default]
None,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FontStyle {
Normal,
Italic,
Oblique,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BorderStyle {
None,
Solid,
Dashed,
Dotted,
Double,
Groove,
Ridge,
Inset,
Outset,
Hidden,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TextDecoration {
pub underline: bool,
pub overline: bool,
pub line_through: bool,
}
impl TextDecoration {
pub const NONE: Self = Self {
underline: false,
overline: false,
line_through: false,
};
pub const UNDERLINE: Self = Self {
underline: true,
overline: false,
line_through: false,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TextTransform {
#[default]
None,
Uppercase,
Lowercase,
Capitalize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FontVariant {
#[default]
Normal,
SmallCaps,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FontStretch {
UltraCondensed,
ExtraCondensed,
Condensed,
SemiCondensed,
#[default]
Normal,
SemiExpanded,
Expanded,
ExtraExpanded,
UltraExpanded,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DisplayAlign {
#[default]
Before,
Center,
After,
}
#[cfg(test)]
mod tests {
use super::*;
use fop_types::{Length, Point, Size};
#[test]
fn test_area_creation() {
let rect = Rect::from_point_size(
Point::new(Length::from_pt(10.0), Length::from_pt(20.0)),
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect);
assert_eq!(area.area_type, AreaType::Block);
assert_eq!(area.width(), Length::from_pt(100.0));
assert_eq!(area.height(), Length::from_pt(50.0));
assert!(!area.has_text());
}
#[test]
fn test_text_area() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(50.0), Length::from_pt(12.0)),
);
let area = Area::text(rect, "Hello".to_string());
assert_eq!(area.area_type, AreaType::Text);
assert!(area.has_text());
assert_eq!(area.text_content().expect("test: should succeed"), "Hello");
}
#[test]
fn test_traits() {
let traits = TraitSet {
color: Some(Color::RED),
font_size: Some(Length::from_pt(12.0)),
..Default::default()
};
assert_eq!(traits.color, Some(Color::RED));
assert_eq!(traits.font_size, Some(Length::from_pt(12.0)));
}
}
#[cfg(test)]
mod extended_tests {
use super::*;
use fop_types::{Length, Point, Rect, Size};
#[test]
fn test_area_with_traits() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let traits = TraitSet {
font_size: Some(Length::from_pt(14.0)),
..Default::default()
};
let area = Area::new(AreaType::Block, rect).with_traits(traits);
assert_eq!(area.traits.font_size, Some(Length::from_pt(14.0)));
}
#[test]
fn test_area_widows_orphans_defaults() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect);
assert_eq!(area.widows, 2);
assert_eq!(area.orphans, 2);
}
#[test]
fn test_area_with_widows_and_orphans() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect)
.with_widows(4)
.with_orphans(3);
assert_eq!(area.widows, 4);
assert_eq!(area.orphans, 3);
}
#[test]
fn test_area_break_before_none_by_default() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect);
assert!(area.break_before.is_none());
assert!(area.break_after.is_none());
}
#[test]
fn test_area_with_break_before() {
use crate::layout::BreakValue;
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect).with_break_before(BreakValue::Page);
assert!(area.break_before.is_some());
}
#[test]
fn test_area_with_break_after() {
use crate::layout::BreakValue;
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect).with_break_after(BreakValue::Page);
assert!(area.break_after.is_some());
}
#[test]
fn test_area_viewport_with_image() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(50.0), Length::from_pt(50.0)),
);
let image_data = vec![0u8, 1, 2, 3, 4];
let area = Area::viewport_with_image(rect, image_data.clone());
assert_eq!(area.area_type, AreaType::Viewport);
assert!(area.has_image_data());
assert_eq!(
area.image_data().expect("test: should succeed"),
image_data.as_slice()
);
}
#[test]
fn test_area_text_content_none_for_non_text() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(100.0), Length::from_pt(50.0)),
);
let area = Area::new(AreaType::Block, rect);
assert!(area.text_content().is_none());
assert!(!area.has_text());
assert!(!area.has_image_data());
}
#[test]
fn test_area_image_data_none_for_text_area() {
let rect = Rect::from_point_size(
Point::ZERO,
Size::new(Length::from_pt(50.0), Length::from_pt(12.0)),
);
let area = Area::text(rect, "Test".to_string());
assert!(area.image_data().is_none());
}
#[test]
fn test_traitset_default_all_none() {
let traits = TraitSet::default();
assert!(traits.color.is_none());
assert!(traits.background_color.is_none());
assert!(traits.font_family.is_none());
assert!(traits.font_size.is_none());
assert!(traits.font_weight.is_none());
assert!(traits.font_style.is_none());
assert!(traits.text_decoration.is_none());
assert!(traits.border_width.is_none());
assert!(traits.padding.is_none());
assert!(traits.text_align.is_none());
assert!(traits.line_height.is_none());
assert!(traits.letter_spacing.is_none());
assert!(traits.word_spacing.is_none());
}
#[test]
fn test_traitset_writing_mode_default_lr_tb() {
let traits = TraitSet::default();
assert_eq!(traits.writing_mode, WritingMode::LrTb);
}
#[test]
fn test_traitset_direction_default_ltr() {
let traits = TraitSet::default();
assert_eq!(traits.direction, Direction::Ltr);
}
#[test]
fn test_traitset_span_default_none() {
let traits = TraitSet::default();
assert_eq!(traits.span, Span::None);
}
#[test]
fn test_traitset_display_align_default_before() {
let traits = TraitSet::default();
assert_eq!(traits.display_align, None);
}
#[test]
fn test_traitset_font_variant_default_normal() {
let traits = TraitSet::default();
assert_eq!(traits.font_variant, None);
}
#[test]
fn test_traitset_text_transform_default_none() {
let traits = TraitSet::default();
assert_eq!(traits.text_transform, None);
}
#[test]
fn test_traitset_font_stretch_default_normal() {
let traits = TraitSet::default();
assert_eq!(traits.font_stretch, None);
}
#[test]
fn test_area_types_are_distinct() {
assert_ne!(AreaType::Page, AreaType::Region);
assert_ne!(AreaType::Block, AreaType::Line);
assert_ne!(AreaType::Inline, AreaType::Text);
assert_ne!(AreaType::Header, AreaType::Footer);
assert_ne!(AreaType::Column, AreaType::Footnote);
}
#[test]
fn test_area_width_and_height() {
let rect = Rect::from_point_size(
Point::new(Length::from_pt(5.0), Length::from_pt(10.0)),
Size::new(Length::from_pt(200.0), Length::from_pt(100.0)),
);
let area = Area::new(AreaType::Page, rect);
assert_eq!(area.width(), Length::from_pt(200.0));
assert_eq!(area.height(), Length::from_pt(100.0));
}
#[test]
fn test_text_decoration_none() {
let td = TextDecoration::NONE;
assert!(!td.underline);
assert!(!td.overline);
assert!(!td.line_through);
}
#[test]
fn test_text_decoration_underline() {
let td = TextDecoration::UNDERLINE;
assert!(td.underline);
assert!(!td.overline);
assert!(!td.line_through);
}
#[test]
fn test_text_decoration_custom() {
let td = TextDecoration {
underline: false,
overline: true,
line_through: true,
};
assert!(!td.underline);
assert!(td.overline);
assert!(td.line_through);
}
#[test]
fn test_font_style_variants() {
assert_ne!(FontStyle::Normal, FontStyle::Italic);
assert_ne!(FontStyle::Italic, FontStyle::Oblique);
assert_ne!(FontStyle::Normal, FontStyle::Oblique);
}
#[test]
fn test_border_style_variants() {
assert_ne!(BorderStyle::None, BorderStyle::Solid);
assert_ne!(BorderStyle::Dashed, BorderStyle::Dotted);
assert_ne!(BorderStyle::Hidden, BorderStyle::None);
}
}