mod frame;
pub use frame::{Frame, ListPlacement, PlacedBlock, PlacedListItem, PlacedTableCell, PlacedTableRow, TablePlacement};
use uzor::types::Rect;
pub(crate) fn column_rect(body: Rect, columns: usize, gap: f64, index: usize) -> Rect {
let columns = columns.max(1);
let total_gap = gap * (columns - 1) as f64;
let column_width = ((body.width - total_gap) / columns as f64).max(0.0);
let x = body.x + index as f64 * (column_width + gap);
Rect::new(x, body.y, column_width, body.height)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Region {
pub rect: Rect,
}
pub trait RegionSequence {
fn next(&mut self) -> Option<Region>;
}
pub struct PageRegionSequence {
body_rect: Rect,
columns: usize,
gap: f64,
column_index: usize,
}
impl PageRegionSequence {
pub fn new(body_rect: Rect) -> Self {
Self { body_rect, columns: 1, gap: 0.0, column_index: 0 }
}
pub fn with_columns(mut self, columns: usize, gap: f64) -> Self {
self.columns = columns.max(1);
self.gap = gap;
self
}
}
impl RegionSequence for PageRegionSequence {
fn next(&mut self) -> Option<Region> {
let index = self.column_index % self.columns;
self.column_index += 1;
Some(Region { rect: column_rect(self.body_rect, self.columns, self.gap, index) })
}
}
pub struct FixedRegionSequence {
rect: Option<Rect>,
}
impl FixedRegionSequence {
pub fn new(rect: Rect) -> Self {
Self { rect: Some(rect) }
}
}
impl RegionSequence for FixedRegionSequence {
fn next(&mut self) -> Option<Region> {
self.rect.take().map(|rect| Region { rect })
}
}
const UNBOUNDED_HEIGHT: f64 = 1.0e9;
pub struct CardRegionSequence {
width: f64,
}
impl CardRegionSequence {
pub fn new(width: f64) -> Self {
Self { width }
}
}
impl RegionSequence for CardRegionSequence {
fn next(&mut self) -> Option<Region> {
Some(Region { rect: Rect::new(0.0, 0.0, self.width, UNBOUNDED_HEIGHT) })
}
}
pub struct ColumnRegionSequence {
body: Rect,
columns: usize,
gap: f64,
next_index: usize,
}
impl ColumnRegionSequence {
pub fn new(body: Rect, columns: usize, gap: f64) -> Self {
Self { body, columns: columns.max(1), gap, next_index: 0 }
}
}
impl RegionSequence for ColumnRegionSequence {
fn next(&mut self) -> Option<Region> {
if self.next_index >= self.columns {
return None;
}
let rect = column_rect(self.body, self.columns, self.gap, self.next_index);
self.next_index += 1;
Some(Region { rect })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn page_region_sequence_never_ends_and_repeats_the_same_body_rect() {
let body = Rect::new(10.0, 20.0, 400.0, 600.0);
let mut regions = PageRegionSequence::new(body);
for _ in 0..5 {
let region = regions.next().expect("page region sequence is infinite");
assert_eq!(region.rect, body);
}
}
#[test]
fn fixed_region_sequence_hands_back_its_rect_exactly_once_then_ends() {
let rect = Rect::new(0.0, 0.0, 200.0, f64::MAX);
let mut regions = FixedRegionSequence::new(rect);
let first = regions.next().expect("first pull must return the fixed rect");
assert_eq!(first.rect, rect);
assert!(regions.next().is_none(), "a fixed region sequence is finite — exactly one region");
}
#[test]
fn card_region_sequence_never_ends_and_hands_back_the_same_full_width_unbounded_height_rect() {
let mut regions = CardRegionSequence::new(320.0);
for _ in 0..5 {
let region = regions.next().expect("card region sequence is infinite");
assert_eq!(region.rect.x, 0.0);
assert_eq!(region.rect.y, 0.0);
assert_eq!(region.rect.width, 320.0);
assert!(region.rect.height > 1.0e8, "card region height must be effectively unbounded");
}
}
#[test]
fn column_region_sequence_yields_exactly_n_equal_width_columns_then_ends() {
let body = Rect::new(10.0, 20.0, 424.0, 600.0); let mut regions = ColumnRegionSequence::new(body, 2, 24.0);
let col1 = regions.next().expect("first column").rect;
assert_eq!(col1, Rect::new(10.0, 20.0, 200.0, 600.0));
let col2 = regions.next().expect("second column").rect;
assert_eq!(col2, Rect::new(234.0, 20.0, 200.0, 600.0));
assert!(regions.next().is_none(), "a column region sequence is finite — exactly `columns` regions");
}
#[test]
fn column_region_sequence_with_one_column_reproduces_the_body_rect_exactly() {
let body = Rect::new(0.0, 0.0, 300.0, 400.0);
let mut regions = ColumnRegionSequence::new(body, 1, 18.0);
assert_eq!(regions.next().expect("one column").rect, body);
assert!(regions.next().is_none());
}
#[test]
fn page_region_sequence_with_columns_cycles_column1_then_column2_then_repeats_on_the_next_page() {
let body = Rect::new(0.0, 0.0, 424.0, 600.0);
let mut regions = PageRegionSequence::new(body).with_columns(2, 24.0);
let page1_col1 = regions.next().expect("page 1 column 1").rect;
let page1_col2 = regions.next().expect("page 1 column 2").rect;
let page2_col1 = regions.next().expect("page 2 column 1").rect;
let page2_col2 = regions.next().expect("page 2 column 2").rect;
assert_eq!(page1_col1, Rect::new(0.0, 0.0, 200.0, 600.0));
assert_eq!(page1_col2, Rect::new(224.0, 0.0, 200.0, 600.0));
assert_eq!(page2_col1, page1_col1, "page 2 must repeat the SAME column 1 rect as page 1");
assert_eq!(page2_col2, page1_col2, "page 2 must repeat the SAME column 2 rect as page 1");
}
#[test]
fn page_region_sequence_default_columns_is_one_and_matches_pre_column_behavior() {
let body = Rect::new(5.0, 5.0, 500.0, 700.0);
let mut regions = PageRegionSequence::new(body);
for _ in 0..3 {
assert_eq!(regions.next().expect("infinite").rect, body, "no `with_columns` call must behave exactly like before this feature");
}
}
}