Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196//! CSS 2.1 floats for block-level, non-replaced boxes with no text -- §9.5 (floats),//! §9.5.1 (positioning the float: the nine placement rules), §9.5.2 (`clear` and//! clearance) and §9.5's rule that an in-flow box establishing a new block formatting//! context must not overlap a float's margin box -- isolated beside `absolute.rs`'s seam//! and built the same way (`tasks/floats-bfc.md`, `tasks/absolute-batches.md`): the seam//! types, corpus and prompt are hand-written, the candidate is not. One plain-data//! function a generated candidate implements://!//! - [`resolve`]-shaped ([`FloatResolver`]): given one subject (a float of either side,//! an ordinary in-flow block, or an in-flow block that establishes its own block//! formatting context), the content box of its containing block, its own margin-box//! size, the position it would take in normal flow, its `clear` value and every float//! already placed in the same block formatting context, returns where the subject goes//! and how much clearance moved it there.//!//! Scope, in: `float: left|right` on block-level non-replaced boxes with an explicit or//! shrink-to-fit width; several floats in one block formatting context, placed by//! §9.5.1's rules; `clear: left|right|both` and §9.5.2's clearance on a following block;//! a sibling that establishes its own block formatting context (`overflow` other than//! `visible`, `display: flow-root`) placed beside a float and narrowed to the space left;//! floats leaving the position of ordinary block siblings untouched (only line boxes//! move around a float, and this engine has none). Out: inline content and line boxes,//! text, floats inside inline formatting contexts, `shape-outside`, tables, replaced//! elements.//!//! Every coordinate here is in one frame: the block formatting context's border-box//! origin, `x` to the right and `y` down. `crates/layout/src/fragment.rs` owns the//! frame -- which box establishes the context (`establishes_bfc`), where each//! containing block's content box sits inside it, what the flow position is -- and//! translates a result back into the fragment tree's parent-relative offsets. It also//! owns the shrink-to-fit width (CSS 2.1 §10.3.5) a `width: auto` float gets, since with//! no inline content that is a pure function of the float's descendants' explicit//! widths, not something this seam decides.//!//! [`resolve_baseline`] is the seam's deliberately wrong starting candidate: every//! subject is placed at its flow position, a left float against the containing block's//! left content edge and a right float against its right one, as if no other float//! existed and `clear` did nothing -- what a float would do if it were merely lifted out//! of the flow and pinned to one side. Unlike the seams that predate `absolute.rs`, no//! hand-written correct implementation exists before the model has had its batches//! (`roadmap.md` -> "Proof of concept", claim 3).
pub use dom::computed::Clear;
/// Which side a float goes to (CSS 2.1 §9.5.1: `float: left` or `float: right`; `none`/// is not a float and never reaches this seam).#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum FloatSide { Left, Right,}
/// What one call to [`resolve`] is placing.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Subject { /// A float of the given side. `margin_box_width`/`margin_box_height` are its margin /// box; `flow_top` is where its margin-box top edge would sit in flow; the outputs /// give its margin box. Float(FloatSide), /// An in-flow block box that neither floats nor establishes a block formatting /// context. Only `clear` can move it (CSS 2.1 §9.5.2); nothing else about floats /// affects a block box's position (§9.5: only line boxes are shortened). `flow_top` /// is its hypothetical top border edge; the outputs give the top border edge it ends /// up at, and `left`/`right` are just the containing block's content edges. Block, /// An in-flow block box that establishes a new block formatting context. Its border /// box must not overlap the margin box of any float in the same context (CSS 2.1 /// §9.5), so it is either narrowed to the free span beside the floats at its /// position or, if that span is too narrow for `margin_box_width`, pushed down to the /// first position where a wide enough span exists. `flow_top` is its hypothetical /// top border edge; the outputs give the top border edge it ends up at and the free /// span (`left`/`right`) it is laid out in. BfcRoot,}
/// One float already placed in the same block formatting context, as its margin box.#[derive(Debug, Clone, Copy, PartialEq)]pub struct PlacedFloat { pub side: FloatSide, pub left: f64, pub top: f64, pub right: f64, pub bottom: f64,}
/// Plain-data inputs to one placement. Every coordinate is in the block formatting/// context's frame (this module's own doc comment).#[derive(Debug, Clone, PartialEq)]pub struct FloatInputs { pub subject: Subject, /// The subject's own `clear` (CSS 2.1 §9.5.2). `Clear::None` for most subjects. pub clear: Clear, /// The subject's containing block's content box: its left and right content edges /// (CSS 2.1 §9.5.1 rules 1 and 7: a float never goes outside them horizontally) and /// its top content edge (rule 4: a float's outer top is never above it). pub containing_block_left: f64, pub containing_block_right: f64, pub containing_block_top: f64, /// The subject's margin-box size. For a `Subject::BfcRoot` with `width: auto` this /// is the narrowest margin box it can take (its minimum content width plus edges and /// margins), the width a free span must offer before the box fits beside a float. pub margin_box_width: f64, pub margin_box_height: f64, /// Where the subject would sit in normal flow: for a float, its margin-box top edge /// (CSS 2.1 §9.5.1 rule 4's "as if it had an otherwise empty anonymous block parent /// taking part in the flow", which is the position after collapsing the margins /// around it); for a block, its top border edge with margins collapsed as usual -- /// §9.5.2's "hypothetical position". A result's `top` is never above this. pub flow_top: f64, /// Every float already placed in this block formatting context, in source order /// (CSS 2.1 §9.5.1 rule 5: earlier in the source means placed earlier). pub placed: Vec<PlacedFloat>,}
/// Where the subject goes. For a `Subject::Float`, `left`/`right`/`top` are its margin/// box's left, right and top edges (`right - left == margin_box_width` always). For a/// `Subject::Block` or `Subject::BfcRoot`, `top` is its top border edge and/// `left`/`right` the free horizontal span it is laid out in (the whole containing/// block's content box for a `Block`). `clearance` is how far `clear` moved the subject/// down from `flow_top` (CSS 2.1 §9.5.2), zero when `clear` did not apply -- a float/// pushed down only because it did not fit beside earlier floats reports zero here.#[derive(Debug, Clone, Copy, PartialEq)]pub struct FloatOutputs { pub left: f64, pub right: f64, pub top: f64, pub clearance: 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 FloatResolver = fn(&FloatInputs) -> FloatOutputs;
/// The seam's deliberately wrong starting candidate (this module's own doc comment):/// the subject sits at `flow_top` whatever else is in the context, a left float against/// the containing block's left content edge, a right float against its right one, a/// block across the whole content box; `clear` and `placed` are never read.pub fn resolve_baseline(inputs: &FloatInputs) -> FloatOutputs { let (left, right) = match inputs.subject { Subject::Float(FloatSide::Left) => { (inputs.containing_block_left, inputs.containing_block_left + inputs.margin_box_width) } Subject::Float(FloatSide::Right) => { (inputs.containing_block_right - inputs.margin_box_width, inputs.containing_block_right) } Subject::Block | Subject::BfcRoot => (inputs.containing_block_left, inputs.containing_block_right), }; FloatOutputs { left, right, top: inputs.flow_top, clearance: 0.0 }}
#[cfg(test)]mod tests { use super::*;
fn inputs(subject: Subject) -> FloatInputs { FloatInputs { subject, clear: Clear::Both, containing_block_left: 10.0, containing_block_right: 410.0, containing_block_top: 5.0, margin_box_width: 100.0, margin_box_height: 50.0, flow_top: 20.0, placed: vec![PlacedFloat { side: FloatSide::Left, left: 10.0, top: 5.0, right: 110.0, bottom: 300.0 }], } }
#[test] fn baseline_pins_a_left_float_to_the_left_content_edge_ignoring_earlier_floats() { let out = resolve_baseline(&inputs(Subject::Float(FloatSide::Left))); assert_eq!(out.left, 10.0, "over the earlier float, not beside it"); assert_eq!(out.right, 110.0); assert_eq!(out.top, 20.0); assert_eq!(out.clearance, 0.0, "clear: both is ignored"); }
#[test] fn baseline_pins_a_right_float_to_the_right_content_edge() { let out = resolve_baseline(&inputs(Subject::Float(FloatSide::Right))); assert_eq!(out.left, 310.0); assert_eq!(out.right, 410.0); }
#[test] fn baseline_leaves_a_block_at_its_flow_position_across_the_whole_content_box() { for subject in [Subject::Block, Subject::BfcRoot] { let out = resolve_baseline(&inputs(subject)); assert_eq!(out.top, 20.0); assert_eq!((out.left, out.right), (10.0, 410.0)); assert_eq!(out.clearance, 0.0); } }}