//! CSS 2.1 10.3.3 block width resolution, isolated behind one function with plain-data //! inputs and outputs (`roadmap.md` -> "Phase 1 — Engine bootstrap" step 4, "Generated //! algorithms, one category at a time"). A generated candidate implements //! [`WidthResolver`] against the types in this module alone -- no `dom`, no Stylo -- which //! is what lets a candidate crate compile independently of the DOM/style layer and lets //! the runner swap resolvers without touching anything else in `fragment.rs`. //! //! [`resolve_block_width_baseline`] is the hand rule `fragment.rs` used before this //! extraction, unchanged: `auto` fills the containing block minus this box's own margins //! and edges, and every other `auto` combination (a single auto margin, both auto //! margins, over-constraint) falls back to each margin's own definite value rather than //! being resolved. It is this seam's baseline candidate. use dom::computed::LengthPercentageOrAuto; /// The containing block's `direction`, the one property CSS 2.1 10.3.3's over-constraint /// rule branches on. The current corpus is exclusively left-to-right /// (`crates/layout/src/fragment.rs`'s module doc), so `Ltr` is the only value any case /// exercises today; a candidate's handling of `Rtl` is unverified by this corpus. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { Ltr, Rtl, } /// Plain-data inputs to the width equation, narrowed to what CSS 2.1 10.3.3 reads: the /// containing block's inline size, the box's own computed `width` and margins, its /// padding/border/box-sizing (needed to convert a `border-box`-sized `width` back to a /// content width), `direction` (which margin over-constraint discards), and min/max width /// (10.4's clamping, which no candidate at this seam's first landing implements yet -- /// carried here so the signature does not have to change when one does). #[derive(Debug, Clone, Copy, PartialEq)] pub struct WidthInputs { pub containing_block_width: f64, pub width: LengthPercentageOrAuto, pub margin_left: LengthPercentageOrAuto, pub margin_right: LengthPercentageOrAuto, pub padding_left: f64, pub padding_right: f64, pub border_left: f64, pub border_right: f64, pub border_box_sizing: bool, pub direction: Direction, /// `None` when `min-width` is not a definite length (`auto`, an intrinsic-sizing /// keyword, or a percentage) -- everything this seam's first candidates leave /// unclamped (`dom::computed::min_width`). pub min_width: Option, /// Same resolution boundary as `min_width`, for `max-width` /// (`dom::computed::max_width`). pub max_width: Option, } /// The used width (content-box, independent of `box-sizing` -- CSS 2.1's box model /// predates it) and used margins CSS 2.1 10.3.3 resolves. #[derive(Debug, Clone, Copy, PartialEq)] pub struct WidthOutputs { pub used_width: f64, pub used_margin_left: f64, pub used_margin_right: f64, } /// One candidate's implementation of the seam: plain data in, plain data out. Every /// candidate module the generation loop compiles exports a function of this type named /// `resolve`. pub type WidthResolver = fn(&WidthInputs) -> WidthOutputs; /// The hand rule `fragment.rs` shipped before this extraction: `auto` fills the /// containing block minus this box's own margins and edges, the one width-equation /// branch with no auto-margin balancing to do; every other `auto` combination falls back /// to treating each margin's own definite value as final. This is the seam's baseline /// candidate, not a spec-complete implementation. pub fn resolve_block_width_baseline(inputs: &WidthInputs) -> WidthOutputs { let edge_lr = inputs.padding_left + inputs.padding_right + inputs.border_left + inputs.border_right; let margin_left = length_or_auto_zero(inputs.margin_left); let margin_right = length_or_auto_zero(inputs.margin_right); let used_width = match inputs.width { LengthPercentageOrAuto::Length(w) if inputs.border_box_sizing => (w - edge_lr).max(0.0), LengthPercentageOrAuto::Length(w) => w, LengthPercentageOrAuto::Auto => { (inputs.containing_block_width - margin_left - margin_right - edge_lr).max(0.0) } // A percentage width is not resolved at this seam -- that is the // percentage-resolution seam's job -- so it falls back to the same zero every // other not-yet-modelled shape (`calc()`, an intrinsic-sizing keyword) does. LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => 0.0, }; WidthOutputs { used_width, used_margin_left: margin_left, used_margin_right: margin_right } } fn length_or_auto_zero(lp: LengthPercentageOrAuto) -> f64 { match lp { LengthPercentageOrAuto::Length(px) => px, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => 0.0, } } #[cfg(test)] mod tests { use super::*; fn inputs(containing_block_width: f64, width: LengthPercentageOrAuto) -> WidthInputs { WidthInputs { containing_block_width, width, margin_left: LengthPercentageOrAuto::Length(0.0), margin_right: LengthPercentageOrAuto::Length(0.0), padding_left: 0.0, padding_right: 0.0, border_left: 0.0, border_right: 0.0, border_box_sizing: false, direction: Direction::Ltr, min_width: None, max_width: None, } } #[test] fn baseline_fills_available_width_when_auto() { let out = resolve_block_width_baseline(&inputs(800.0, LengthPercentageOrAuto::Auto)); assert_eq!(out.used_width, 800.0); } #[test] fn baseline_passes_an_explicit_content_box_width_through() { let out = resolve_block_width_baseline(&inputs(800.0, LengthPercentageOrAuto::Length(220.0))); assert_eq!(out.used_width, 220.0); } }