Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220//! CSS 2.1 absolute positioning for block-level, non-replaced boxes with no text --//! §9.3.1 (positioning schemes), §9.3.2 (`top`/`right`/`bottom`/`left`), §10.1 (the//! containing block of an absolutely positioned box), §10.3.7 (width) and §10.6.4//! (height) -- isolated beside `percentage.rs`/`width.rs`'s seams (`roadmap.md` ->//! "Proof of concept", claim 3: the first seam attempted by the model before any//! hand-written candidate exists). One plain-data function a generated candidate//! implements://!//! - [`resolve`]-shaped ([`AbsoluteResolver`]): given the box's own (already//! percentage-resolved) offsets, size, margins, padding, border and box-sizing, plus//! its containing block's padding-box size and the static position a hypothetical//! in-flow box would have occupied, returns the used `left`/`top` (offsets from the//! containing block's padding-box origin to the box's margin edge -- `crate::width`'s//! `WidthOutputs::used_margin_left` is added on top by the caller the same way, not//! folded in here), the used content-box `width`/`height`, and the used margins (CSS//! 2.1 10.3.7's centering rule can resolve an `auto` margin to something other than//! zero, unlike the width equation's over-constraint rule, which only ever *discards*//! a margin).//!//! `crates/layout/src/fragment.rs` owns which box is the containing block for which//! absolutely positioned descendant, resolving every percentage in this seam's inputs//! against that containing block's own padding-box size before calling [`resolve`] --//! this seam's candidate sees `Length` or `Auto` only, never `Percentage` (mirroring why//! `percentage.rs` exists as its own seam rather than being folded into this one: a//! candidate here is not asked to also resolve percentages). `content_height` is `None`//! unless the box's own height needs its children's content to be known first (CSS 2.1//! 10.6.4's content-based auto-height rule, the absolute-positioning analogue of//! `crate::percentage`'s auto-containing-block-height case) -- `fragment.rs` lays the//! box's children out against the used width this same function returns from an earlier//! call with `content_height: None` before calling it again with `content_height: Some`,//! the same two-call shape `crate::percentage`'s own auto-height basis needs from its//! caller, just spread across two calls to this one function instead of one call each to//! two different resolvers.//!//! [`resolve_baseline`] is the seam's deliberately wrong starting candidate//! (`handoff.md`): every offset is ignored and the box is placed at its static position//! with whatever size its own `width`/`height` already give it in-flow -- exactly what//! `fragment.rs` did before this seam existed, since Phase 0's box tree already gives an//! absolutely positioned element a box (`crates/layout/src/boxtree.rs` is unchanged by//! this seam) but the structural half had nothing to place it against yet. Unlike every//! other seam in this codebase, no hand-written `correct` implementation is written//! before a model has had a chance at this one (`roadmap.md` -> "Proof of concept",//! claim 3).
use dom::computed::LengthPercentageOrAuto;
use crate::width::Direction;
/// Plain-data inputs to one absolutely positioned box's placement. Every/// `LengthPercentageOrAuto` field here is already resolved past `Percentage` by/// `fragment.rs` (this module's own doc comment) -- a correct [`resolve`] never needs to/// see the containing block's size to interpret one of these fields, only to solve the/// CSS 2.1 10.3.7/10.6.4 equations once `Auto` is the shape in play.#[derive(Debug, Clone, Copy, PartialEq)]pub struct AbsoluteInputs { /// The nearest positioned ancestor's padding-box width, or the initial containing /// block's (the viewport's) when there is none (CSS 2.1 10.1). pub containing_block_width: f64, pub containing_block_height: f64, /// The offset from the containing block's padding-box origin to the left/top margin /// edge of the hypothetical box this element would have generated had its `position` /// been `static` (CSS 2.1 10.3.7/10.6.4's static-position fallback, consulted only /// when both `left`/`right` -- respectively `top`/`bottom` -- are `Auto`). pub static_left: f64, pub static_top: f64, pub top: LengthPercentageOrAuto, pub right: LengthPercentageOrAuto, pub bottom: LengthPercentageOrAuto, pub left: LengthPercentageOrAuto, pub width: LengthPercentageOrAuto, pub height: LengthPercentageOrAuto, pub margin_left: LengthPercentageOrAuto, pub margin_right: LengthPercentageOrAuto, pub margin_top: LengthPercentageOrAuto, pub margin_bottom: LengthPercentageOrAuto, pub padding_left: f64, pub padding_right: f64, pub padding_top: f64, pub padding_bottom: f64, pub border_left: f64, pub border_right: f64, pub border_top: f64, pub border_bottom: f64, pub border_box_sizing: bool, /// The containing block's `direction`, the property CSS 2.1 10.3.7's over-constraint /// rule branches on (mirroring `crate::width::WidthInputs::direction`): in `Ltr`, /// `right` is the offset discarded when `left`/`width`/`right` are all given; in /// `Rtl`, `left` is. pub direction: Direction, /// The box's own used content height, once known -- `Some` only on a second call for /// a box whose height depends on its children's content (this module's own doc /// comment), `None` on every other call, including a box's first call and any box /// whose height never needs it. pub content_height: Option<f64>,}
/// The used `left`/`top` (offsets from the containing block's padding-box origin to this/// box's margin edge), used content-box `width`/`height`, and used margins CSS 2.1/// 10.3.7/10.6.4 resolve. `fragment.rs` adds `margin_left`/`margin_top` on top of/// `left`/`top` to place the box's border-box edge, the same split/// `crate::width::WidthOutputs` already uses for an in-flow box's own margin-left.#[derive(Debug, Clone, Copy, PartialEq)]pub struct AbsoluteOutputs { pub left: f64, pub top: f64, pub used_width: f64, pub used_height: f64, pub margin_left: f64, pub margin_right: f64, pub margin_top: f64, pub margin_bottom: 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 AbsoluteResolver = fn(&AbsoluteInputs) -> AbsoluteOutputs;
fn length_or_auto_zero(lp: LengthPercentageOrAuto) -> f64 { match lp { LengthPercentageOrAuto::Length(px) => px, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => 0.0, }}
/// The seam's deliberately wrong starting candidate (this module's own doc comment):/// every offset (`top`/`right`/`bottom`/`left`) is ignored outright, and the box is/// placed at its static position with whatever `width`/`height` it would have used/// in-flow -- an explicit length folds `border_box_sizing` back to a content size the/// same way `crate::width::resolve_block_width_baseline` does, and every other shape/// (`Auto`, an unresolved percentage) falls back to zero, since this baseline does not/// attempt the width/height equations at all.pub fn resolve_baseline(inputs: &AbsoluteInputs) -> AbsoluteOutputs { let edge_lr = inputs.padding_left + inputs.padding_right + inputs.border_left + inputs.border_right; let edge_tb = inputs.padding_top + inputs.padding_bottom + inputs.border_top + inputs.border_bottom; 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 | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => { 0.0 } }; let used_height = match inputs.height { LengthPercentageOrAuto::Length(h) if inputs.border_box_sizing => (h - edge_tb).max(0.0), LengthPercentageOrAuto::Length(h) => h, LengthPercentageOrAuto::Auto | LengthPercentageOrAuto::Percentage(_) | LengthPercentageOrAuto::Unresolved => { 0.0 } }; AbsoluteOutputs { left: inputs.static_left, top: inputs.static_top, used_width, used_height, margin_left: length_or_auto_zero(inputs.margin_left), margin_right: length_or_auto_zero(inputs.margin_right), margin_top: length_or_auto_zero(inputs.margin_top), margin_bottom: length_or_auto_zero(inputs.margin_bottom), }}
#[cfg(test)]mod tests { use super::*;
fn inputs() -> AbsoluteInputs { AbsoluteInputs { containing_block_width: 800.0, containing_block_height: 600.0, static_left: 10.0, static_top: 20.0, top: LengthPercentageOrAuto::Length(50.0), right: LengthPercentageOrAuto::Auto, bottom: LengthPercentageOrAuto::Auto, left: LengthPercentageOrAuto::Length(30.0), width: LengthPercentageOrAuto::Length(100.0), height: LengthPercentageOrAuto::Length(40.0), margin_left: LengthPercentageOrAuto::Length(0.0), margin_right: LengthPercentageOrAuto::Length(0.0), margin_top: LengthPercentageOrAuto::Length(0.0), margin_bottom: LengthPercentageOrAuto::Length(0.0), padding_left: 0.0, padding_right: 0.0, padding_top: 0.0, padding_bottom: 0.0, border_left: 0.0, border_right: 0.0, border_top: 0.0, border_bottom: 0.0, border_box_sizing: false, direction: Direction::Ltr, content_height: None, } }
#[test] fn baseline_ignores_every_offset_and_uses_the_static_position() { let out = resolve_baseline(&inputs()); assert_eq!(out.left, 10.0, "the static position, not the explicit left:30px"); assert_eq!(out.top, 20.0, "the static position, not the explicit top:50px"); assert_eq!(out.used_width, 100.0); assert_eq!(out.used_height, 40.0); }
#[test] fn baseline_folds_border_box_sizing_back_to_a_content_size() { let mut i = inputs(); i.border_box_sizing = true; i.padding_left = 5.0; i.padding_right = 5.0; i.border_top = 2.0; i.border_bottom = 2.0; let out = resolve_baseline(&i); assert_eq!(out.used_width, 90.0, "100 - 2*5 padding"); assert_eq!(out.used_height, 36.0, "40 - 2*2 border"); }}