pub mod build;
pub mod cell;
pub mod draw_command;
pub mod float;
pub mod fragment;
pub mod header_footer;
pub mod line;
pub mod measurer;
pub mod page;
pub mod paragraph;
pub mod section;
pub mod table;
use crate::render::dimension::Pt;
use crate::render::geometry::{PtEdgeInsets, PtSize};
pub(crate) enum McBranch<'a> {
Choices(&'a [crate::model::McChoice]),
Fallback(&'a [crate::model::Inline]),
Neither,
}
pub(crate) fn live_mc_branch(ac: &crate::model::AlternateContent) -> McBranch<'_> {
use crate::model::{ImagePlacement, Inline};
fn draws_an_anchor(inlines: &[Inline]) -> bool {
inlines.iter().any(|inline| match inline {
Inline::Image(img) => matches!(img.placement, ImagePlacement::Anchor(_)),
Inline::Hyperlink(link) => draws_an_anchor(&link.content),
Inline::Field(f) => draws_an_anchor(&f.content),
Inline::AlternateContent(inner) => {
matches!(live_mc_branch(inner), McBranch::Choices(_))
}
_ => false,
})
}
if ac.choices.iter().any(|c| draws_an_anchor(&c.content)) {
McBranch::Choices(&ac.choices)
} else {
match ac.fallback {
Some(ref fallback) => McBranch::Fallback(fallback),
None => McBranch::Neither,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ShapeAutoFit {
font_scale: f32,
line_spacing_scale: f32,
}
impl Default for ShapeAutoFit {
fn default() -> Self {
Self::NONE
}
}
impl ShapeAutoFit {
pub const NONE: Self = Self {
font_scale: 1.0,
line_spacing_scale: 1.0,
};
pub fn from_body(auto_fit: Option<crate::model::TextAutoFit>) -> Self {
use crate::model::TextAutoFit;
let Some(TextAutoFit::NormalAutoFit(na)) = auto_fit else {
return Self::NONE;
};
let font_scale = na
.font_scale
.map_or(1.0, |p| p.to_fraction())
.clamp(0.01, 10.0);
let line_spacing_scale =
(1.0 - na.line_spacing_reduction.map_or(0.0, |p| p.to_fraction())).clamp(0.01, 10.0);
Self {
font_scale,
line_spacing_scale,
}
}
pub fn scale_font(self, size: Pt) -> Pt {
if self.font_scale == 1.0 {
size
} else {
Pt::new(size.raw() * self.font_scale)
}
}
pub fn scale_line_height(self, height: Pt) -> Pt {
if self.line_spacing_scale == 1.0 {
height
} else {
Pt::new(height.raw() * self.line_spacing_scale)
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct BoxConstraints {
pub min_width: Pt,
pub max_width: Pt,
pub min_height: Pt,
pub max_height: Pt,
}
impl BoxConstraints {
pub fn new(min_width: Pt, max_width: Pt, min_height: Pt, max_height: Pt) -> Self {
debug_assert!(min_width.raw() <= max_width.raw());
debug_assert!(min_height.raw() <= max_height.raw());
Self {
min_width,
max_width,
min_height,
max_height,
}
}
pub fn tight(size: PtSize) -> Self {
Self {
min_width: size.width,
max_width: size.width,
min_height: size.height,
max_height: size.height,
}
}
pub fn tight_width(width: Pt, max_height: Pt) -> Self {
Self {
min_width: width,
max_width: width,
min_height: Pt::ZERO,
max_height,
}
}
pub fn loose(max_size: PtSize) -> Self {
Self {
min_width: Pt::ZERO,
max_width: max_size.width,
min_height: Pt::ZERO,
max_height: max_size.height,
}
}
pub fn unbounded() -> Self {
Self {
min_width: Pt::ZERO,
max_width: Pt::INFINITY,
min_height: Pt::ZERO,
max_height: Pt::INFINITY,
}
}
pub fn is_tight_width(&self) -> bool {
self.min_width == self.max_width
}
pub fn is_tight_height(&self) -> bool {
self.min_height == self.max_height
}
pub fn is_tight(&self) -> bool {
self.is_tight_width() && self.is_tight_height()
}
pub fn enforce(&self, other: &BoxConstraints) -> BoxConstraints {
BoxConstraints {
min_width: self.min_width.max(other.min_width).min(self.max_width),
max_width: self.max_width.min(other.max_width).max(self.min_width),
min_height: self.min_height.max(other.min_height).min(self.max_height),
max_height: self.max_height.min(other.max_height).max(self.min_height),
}
}
pub fn deflate(&self, edges: &PtEdgeInsets) -> BoxConstraints {
let h = edges.horizontal();
let v = edges.vertical();
BoxConstraints {
min_width: (self.min_width - h).max(Pt::ZERO),
max_width: (self.max_width - h).max(Pt::ZERO),
min_height: (self.min_height - v).max(Pt::ZERO),
max_height: (self.max_height - v).max(Pt::ZERO),
}
}
pub fn constrain(&self, size: PtSize) -> PtSize {
PtSize {
width: size.width.max(self.min_width).min(self.max_width),
height: size.height.max(self.min_height).min(self.max_height),
}
}
pub fn biggest(&self) -> PtSize {
PtSize {
width: self.max_width,
height: self.max_height,
}
}
pub fn smallest(&self) -> PtSize {
PtSize {
width: self.min_width,
height: self.min_height,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tight_constraints() {
let c = BoxConstraints::tight(PtSize::new(Pt::new(100.0), Pt::new(200.0)));
assert!(c.is_tight());
assert!(c.is_tight_width());
assert!(c.is_tight_height());
assert_eq!(c.min_width.raw(), 100.0);
assert_eq!(c.max_width.raw(), 100.0);
assert_eq!(c.min_height.raw(), 200.0);
assert_eq!(c.max_height.raw(), 200.0);
}
#[test]
fn tight_width_loose_height() {
let c = BoxConstraints::tight_width(Pt::new(300.0), Pt::new(500.0));
assert!(c.is_tight_width());
assert!(!c.is_tight_height());
assert_eq!(c.min_height.raw(), 0.0);
assert_eq!(c.max_height.raw(), 500.0);
}
#[test]
fn loose_constraints() {
let c = BoxConstraints::loose(PtSize::new(Pt::new(400.0), Pt::new(600.0)));
assert!(!c.is_tight());
assert_eq!(c.min_width.raw(), 0.0);
assert_eq!(c.max_width.raw(), 400.0);
assert_eq!(c.min_height.raw(), 0.0);
assert_eq!(c.max_height.raw(), 600.0);
}
#[test]
fn unbounded_constraints() {
let c = BoxConstraints::unbounded();
assert!(c.max_width.raw().is_infinite());
assert!(c.max_height.raw().is_infinite());
}
#[test]
fn biggest_and_smallest() {
let c = BoxConstraints::new(Pt::new(10.0), Pt::new(100.0), Pt::new(20.0), Pt::new(200.0));
assert_eq!(c.biggest(), PtSize::new(Pt::new(100.0), Pt::new(200.0)));
assert_eq!(c.smallest(), PtSize::new(Pt::new(10.0), Pt::new(20.0)));
}
#[test]
fn constrain_clamps_to_bounds() {
let c = BoxConstraints::new(Pt::new(50.0), Pt::new(200.0), Pt::new(50.0), Pt::new(200.0));
let s1 = c.constrain(PtSize::new(Pt::new(10.0), Pt::new(10.0)));
assert_eq!(s1, PtSize::new(Pt::new(50.0), Pt::new(50.0)));
let s2 = c.constrain(PtSize::new(Pt::new(999.0), Pt::new(999.0)));
assert_eq!(s2, PtSize::new(Pt::new(200.0), Pt::new(200.0)));
let s3 = c.constrain(PtSize::new(Pt::new(100.0), Pt::new(100.0)));
assert_eq!(s3, PtSize::new(Pt::new(100.0), Pt::new(100.0)));
}
#[test]
fn deflate_shrinks_constraints() {
let c = BoxConstraints::tight(PtSize::new(Pt::new(400.0), Pt::new(600.0)));
let edges = PtEdgeInsets::new(
Pt::new(10.0), Pt::new(20.0), Pt::new(30.0), Pt::new(40.0), );
let d = c.deflate(&edges);
assert_eq!(d.max_width.raw(), 340.0);
assert_eq!(d.max_height.raw(), 560.0);
}
#[test]
fn deflate_does_not_go_negative() {
let c = BoxConstraints::tight(PtSize::new(Pt::new(10.0), Pt::new(10.0)));
let edges = PtEdgeInsets::new(
Pt::new(100.0),
Pt::new(100.0),
Pt::new(100.0),
Pt::new(100.0),
);
let d = c.deflate(&edges);
assert_eq!(d.max_width.raw(), 0.0);
assert_eq!(d.max_height.raw(), 0.0);
}
#[test]
fn enforce_intersects_constraints() {
let parent =
BoxConstraints::new(Pt::new(0.0), Pt::new(400.0), Pt::new(0.0), Pt::new(600.0));
let child = BoxConstraints::new(
Pt::new(100.0),
Pt::new(300.0),
Pt::new(50.0),
Pt::new(500.0),
);
let result = parent.enforce(&child);
assert_eq!(result.min_width.raw(), 100.0);
assert_eq!(result.max_width.raw(), 300.0);
assert_eq!(result.min_height.raw(), 50.0);
assert_eq!(result.max_height.raw(), 500.0);
}
#[test]
fn enforce_tight_parent_wins() {
let parent = BoxConstraints::tight(PtSize::new(Pt::new(200.0), Pt::new(300.0)));
let child = BoxConstraints::loose(PtSize::new(Pt::new(400.0), Pt::new(600.0)));
let result = parent.enforce(&child);
assert!(result.is_tight());
assert_eq!(result.max_width.raw(), 200.0);
assert_eq!(result.max_height.raw(), 300.0);
}
#[test]
fn enforce_wider_child_gets_clamped() {
let parent =
BoxConstraints::new(Pt::new(0.0), Pt::new(200.0), Pt::new(0.0), Pt::new(200.0));
let child = BoxConstraints::new(Pt::new(0.0), Pt::new(999.0), Pt::new(0.0), Pt::new(999.0));
let result = parent.enforce(&child);
assert_eq!(result.max_width.raw(), 200.0, "child can't exceed parent");
assert_eq!(result.max_height.raw(), 200.0);
}
#[test]
fn page_to_body_to_cell_cascade() {
let page = BoxConstraints::tight(PtSize::new(Pt::new(612.0), Pt::new(792.0)));
let margins = PtEdgeInsets::new(Pt::new(72.0), Pt::new(72.0), Pt::new(72.0), Pt::new(72.0));
let body = page.deflate(&margins);
assert_eq!(body.max_width.raw(), 468.0); assert_eq!(body.max_height.raw(), 648.0);
let cell = BoxConstraints::tight_width(Pt::new(200.0), body.max_height);
let cell_padding =
PtEdgeInsets::new(Pt::new(5.0), Pt::new(10.0), Pt::new(5.0), Pt::new(10.0));
let cell_content = cell.deflate(&cell_padding);
assert_eq!(cell_content.max_width.raw(), 180.0); assert!(cell_content.is_tight_width());
}
}