use crate::model::{Columns, SectionProperties};
use crate::render::dimension::Pt;
use crate::render::geometry::{PtEdgeInsets, PtSize};
const SPEC_DEFAULT_PAGE_WIDTH: Pt = Pt::new(612.0);
const SPEC_DEFAULT_PAGE_HEIGHT: Pt = Pt::new(792.0);
const SPEC_DEFAULT_MARGIN: Pt = Pt::new(72.0);
#[derive(Debug, Clone, Copy)]
pub struct ColumnGeometry {
pub x_offset: Pt,
pub width: Pt,
}
#[derive(Debug, Clone)]
pub struct PageConfig {
pub page_size: PtSize,
pub margins: PtEdgeInsets,
pub header_margin: Pt,
pub footer_margin: Pt,
pub columns: Vec<ColumnGeometry>,
}
impl Default for PageConfig {
fn default() -> Self {
let content_width = SPEC_DEFAULT_PAGE_WIDTH - SPEC_DEFAULT_MARGIN - SPEC_DEFAULT_MARGIN;
Self {
page_size: PtSize::new(SPEC_DEFAULT_PAGE_WIDTH, SPEC_DEFAULT_PAGE_HEIGHT),
margins: PtEdgeInsets::new(
SPEC_DEFAULT_MARGIN,
SPEC_DEFAULT_MARGIN,
SPEC_DEFAULT_MARGIN,
SPEC_DEFAULT_MARGIN,
),
header_margin: SPEC_DEFAULT_MARGIN / 2.0,
footer_margin: SPEC_DEFAULT_MARGIN / 2.0,
columns: vec![ColumnGeometry {
x_offset: Pt::ZERO,
width: content_width,
}],
}
}
}
impl PageConfig {
pub fn from_section(sect: &SectionProperties) -> Self {
let mut cfg = Self::default();
if let Some(ref ps) = sect.page_size {
if let Some(w) = ps.width {
cfg.page_size.width = Pt::from(w);
}
if let Some(h) = ps.height {
cfg.page_size.height = Pt::from(h);
}
}
if let Some(ref pm) = sect.page_margins {
if let Some(t) = pm.top {
cfg.margins.top = Pt::from(t);
}
if let Some(r) = pm.right {
cfg.margins.right = Pt::from(r);
}
if let Some(b) = pm.bottom {
cfg.margins.bottom = Pt::from(b);
}
if let Some(l) = pm.left {
cfg.margins.left = Pt::from(l);
}
if let Some(h) = pm.header {
cfg.header_margin = Pt::from(h);
}
if let Some(f) = pm.footer {
cfg.footer_margin = Pt::from(f);
}
}
let content_width = cfg.page_size.width - cfg.margins.left - cfg.margins.right;
cfg.columns = compute_columns(content_width, §.columns);
cfg
}
pub fn content_width(&self) -> Pt {
self.page_size.width - self.margins.left - self.margins.right
}
pub fn num_columns(&self) -> usize {
self.columns.len()
}
pub fn content_height(&self) -> Pt {
self.page_size.height - self.margins.top - self.margins.bottom
}
}
fn compute_columns(content_width: Pt, columns: &Option<Columns>) -> Vec<ColumnGeometry> {
let cols = match columns {
Some(c) if c.count.unwrap_or(1) > 1 => c,
_ => {
return vec![ColumnGeometry {
x_offset: Pt::ZERO,
width: content_width,
}]
}
};
let num = cols.count.unwrap_or(1) as usize;
let default_space = cols.space.map(Pt::from).unwrap_or(Pt::new(36.0));
if !cols.columns.is_empty() && cols.equal_width != Some(true) {
let mut result = Vec::with_capacity(cols.columns.len());
let mut x = Pt::ZERO;
for (i, col_def) in cols.columns.iter().enumerate() {
let w = col_def
.width
.map(Pt::from)
.unwrap_or(content_width / num as f32);
result.push(ColumnGeometry {
x_offset: x,
width: w,
});
if i < cols.columns.len() - 1 {
let gap = col_def.space.map(Pt::from).unwrap_or(default_space);
x += w + gap;
}
}
return result;
}
let total_gap = default_space * (num as f32 - 1.0);
let col_width = (content_width - total_gap) / num as f32;
let mut result = Vec::with_capacity(num);
for i in 0..num {
let x = (col_width + default_space) * i as f32;
result.push(ColumnGeometry {
x_offset: x,
width: col_width,
});
}
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::dimension::{Dimension, Twips};
use crate::model::{PageMargins, PageSize};
#[test]
fn default_is_us_letter() {
let cfg = PageConfig::default();
assert_eq!(cfg.page_size.width.raw(), 612.0);
assert_eq!(cfg.page_size.height.raw(), 792.0);
assert_eq!(cfg.margins.top.raw(), 72.0);
}
#[test]
fn content_dimensions() {
let cfg = PageConfig::default();
assert_eq!(cfg.content_width().raw(), 468.0); assert_eq!(cfg.content_height().raw(), 648.0); }
#[test]
fn from_section_with_page_size() {
let sect = SectionProperties {
page_size: Some(PageSize {
width: Some(Dimension::<Twips>::new(12240)), height: Some(Dimension::<Twips>::new(15840)), orientation: None,
}),
..Default::default()
};
let cfg = PageConfig::from_section(§);
assert_eq!(cfg.page_size.width.raw(), 612.0);
assert_eq!(cfg.page_size.height.raw(), 792.0);
}
#[test]
fn from_section_with_margins() {
let sect = SectionProperties {
page_margins: Some(PageMargins {
top: Some(Dimension::<Twips>::new(1440)), right: Some(Dimension::<Twips>::new(1440)),
bottom: Some(Dimension::<Twips>::new(1440)),
left: Some(Dimension::<Twips>::new(1440)),
header: Some(Dimension::<Twips>::new(720)), footer: Some(Dimension::<Twips>::new(720)),
gutter: None,
}),
..Default::default()
};
let cfg = PageConfig::from_section(§);
assert_eq!(cfg.margins.top.raw(), 72.0);
assert_eq!(cfg.header_margin.raw(), 36.0);
}
#[test]
fn from_section_partial_uses_defaults() {
let sect = SectionProperties {
page_margins: Some(PageMargins {
top: Some(Dimension::<Twips>::new(2880)), right: None,
bottom: None,
left: None,
header: None,
footer: None,
gutter: None,
}),
..Default::default()
};
let cfg = PageConfig::from_section(§);
assert_eq!(cfg.margins.top.raw(), 144.0, "custom top margin");
assert_eq!(cfg.margins.right.raw(), 72.0, "default right margin");
}
}