Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355//! CSS 2.1's fixed table layout in the separated-borders model -- §17.5.2.1 (the fixed//! algorithm's column widths and the table's own used width), §17.5.3 (row heights),//! §17.6.1 (`border-spacing`, and borders belonging to cells alone) and §17.5's rule that//! a cell's margins are ignored -- isolated behind two functions with plain-data inputs//! and outputs (`roadmap.md` -> "Phase 1 — Engine bootstrap" step 4). A generated//! candidate implements both against the types in this module alone, the way the//! margin-collapsing function is split into `classify` and `combine`://!//! - [`columns`-shaped](TableColumnsResolver): given the table's own border-box width,//! its horizontal padding and border, the horizontal `border-spacing`, and one entry//! per cell of the first row carrying that cell's `width`, horizontal padding and//! border and `box-sizing`, returns each column's width and x offset and the table's//! used border-box width.//! - [`rows`-shaped](TableRowsResolver): given the vertical `border-spacing`, the table's//! vertical padding and border, and one entry per row carrying the row's own `height`//! and every one of its cells' border-box heights, returns each row's height and y//! offset and the table's used border-box height.//!//! The two are one algorithm split on the axis CSS 2.1 splits it on: §17.5.2.1's whole//! point is that the horizontal layout of a fixed table does not depend on cell content,//! so the columns are resolved first, cells are laid out inside them, and only then are//! the heights their content produced handed to the second function. `fragment.rs` owns//! that order and the layout in between; neither function ever lays anything out itself.//!//! **What `fragment.rs` has already done**, so that a candidate does not repeat it. The//! table's own `width` arrives as one border-box length in px, its percentage resolved//! against the table's containing block and its `box-sizing` folded in -- the same//! already-resolved discipline `crate::absolute::AbsoluteInputs` keeps. A cell's `width`//! does *not*: it arrives as written, because a percentage there is of the table and so//! is the candidate's to resolve, and `box-sizing` there decides whether the value//! already includes the cell's own padding and border, which is a step of §17.5.2.1//! itself. Cell margins never arrive at all (§17.5: margins do not apply to a table cell),//! and a cell's border-box height is measured for it, since only `fragment.rs` can see//! inside the box.//!//! **Geometry conventions.** A column's [`x`](TableColumn::x) and a row's//! [`y`](TableRow::y) are offsets from the table's *content-box* origin, and a column's//! width and a row's height are the cell border box that sits in them -- so in the//! separated model the first column starts one horizontal `border-spacing` in from the//! content-box origin, the last ends one in from its far edge, and adjacent columns are//! one apart (§17.6.1). Both functions' `used_*` answers are the table's own *border-box*//! size, padding and border included, which is why each takes the table's own edges://! §17.5.2.1 makes the table's used width the greater of its own `width` and what its//! columns need, so the answer is not always the width that went in.//!//! Scope. In: `table-layout: fixed` on a `display: table` box with an explicit `width`;//! column widths from the first row's cells as a length, a percentage or `auto`, with//! `auto` columns sharing what is left equally and a table wider than its columns//! spreading the difference over them; one- and two-value `border-spacing`; padding and//! borders on the table and on its cells under either `box-sizing`; a cell's margins//! ignored; a row's height as the tallest of its own `height` and its cells' border//! boxes, a cell's own content being block children of explicit height; and//! `vertical-align: top` on every cell.//!//! Out, and not modelled anywhere in this module: `table-layout: auto` and every table//! whose used layout is auto -- a `width: auto` table, and the anonymous table §17.2.1//! wraps around a stray row or cell, whose `table-layout` is the initial `auto`; column//! and column-group elements (`display: table-column`, whose geometry the engines are//! unlikely to report alike); row groups; captions; `colspan` and `rowspan`, which are//! HTML attributes a `<div>` cell cannot carry; the collapsing-borders model;//! `vertical-align` other than `top`, which needs cell baselines and so waits on the//! inline-atomic function's line boxes being in use by cells; a table `height` larger//! than its rows; `inline-table`; and text.//!//! [`columns_baseline`] and [`rows_baseline`] are this function's deliberately wrong//! starting candidates: every column is the full width of the table and every cell is//! stacked under the one before it, which is exactly what the engine did before this//! module existed -- `boxtree.rs` gave a `display: table-cell` element one block-level//! box and `fragment.rs` stacked it in normal flow like any other, filling its containing//! block's width.
use dom::computed::LengthPercentageOrAuto;
/// One cell of the first row, as far as §17.5.2.1 step 2 reads it. Every field is that/// one cell's own; the column it sets is the one at the same index.#[derive(Debug, Clone, Copy, PartialEq)]pub struct TableColumnInput { /// The cell's computed `width`, as written: a length, a percentage of the table, or /// `auto` (this module's own doc comment explains why this one is not pre-resolved). /// `Unresolved` -- a `calc()` mixing a length and a percentage -- is not produced by /// this function's corpus and is the candidate's to treat as `auto`. pub width: LengthPercentageOrAuto, pub padding_left: f64, pub padding_right: f64, pub border_left: f64, pub border_right: f64, /// Whether this cell's `box-sizing` is `border-box`, in which case its `width` /// already includes the padding and border above and the column it sets is that width /// alone; under `content-box` the column is that width plus both edges. pub border_box_sizing: bool,}
/// Plain-data inputs to one table's column widths, narrowed to what §17.5.2.1 reads --/// which is deliberately nothing about any cell's content, and nothing about any row but/// the first.#[derive(Debug, Clone, PartialEq)]pub struct TableColumnsInputs { /// The table's own `width` as a border-box length in CSS pixels, percentage resolved /// and `box-sizing` folded in (this module's own doc comment). §17.5.2.1's "the value /// of the `width` property for the table element", before the columns have their say. pub border_box_width: f64, pub padding_left: f64, pub padding_right: f64, pub border_left: f64, pub border_right: f64, /// The horizontal component of the table's `border-spacing` (§17.6.1): the gap /// between two adjacent columns, and between the table's content edge and the column /// beside it. A table of `n` columns has `n + 1` of these gaps inside its content /// box. pub border_spacing: f64, /// One entry per cell of the table's first row, in document order. Empty for a table /// with no row and no cell at all, which has no column either. pub columns: Vec<TableColumnInput>,}
/// Where one column sits and how wide it is, both measured as the cell border box that/// occupies it (this module's own doc comment on geometry conventions).#[derive(Debug, Clone, Copy, PartialEq)]pub struct TableColumn { pub width: f64, /// The column's left edge, as a distance right from the table's content-box origin. pub x: f64,}
/// The columns §17.5.2.1 resolves, and the table's own used width once they have had/// their say.#[derive(Debug, Clone, PartialEq)]pub struct TableColumnsOutputs { /// One column per entry of [`TableColumnsInputs::columns`], in the same order. pub columns: Vec<TableColumn>, /// The table's used border-box width: §17.5.2.1's "greater of the value of the /// `width` property for the table element and the sum of the column widths (plus cell /// spacing or borders)", with the table's own padding and border added back on. pub used_width: f64,}
/// One row, as far as §17.5.3 reads it.#[derive(Debug, Clone, PartialEq)]pub struct TableRowInput { /// The row's own computed `height`. A percentage is of the table's height, which in /// this function's scope is always its rows' own and so has no definite basis; it /// reaches a candidate as `Percentage` and is its to treat as `auto`, the way /// `Unresolved` is. pub height: LengthPercentageOrAuto, /// Every cell of this row, in document order, as the border-box height its own /// content gave it -- what §17.5.3's "the minimum height required by the content" /// comes to once `fragment.rs` has laid that content out. pub cell_heights: Vec<f64>,}
/// Where one row sits and how tall it is. Its cells' border boxes all take this same/// height (§17.5.3: a cell's used height is its row's), which is what makes/// `vertical-align` matter at all and why anything but `top` is out of scope.#[derive(Debug, Clone, Copy, PartialEq)]pub struct TableRow { pub height: f64, /// The row's top edge, as a distance down from the table's content-box origin. pub y: f64,}
/// The rows §17.5.3 resolves, and the table's own used height.#[derive(Debug, Clone, PartialEq)]pub struct TableRowsOutputs { /// One row per entry of [`TableRowsInputs::rows`], in the same order. pub rows: Vec<TableRow>, /// The table's used border-box height: its rows plus every vertical `border-spacing` /// gap between and around them, with its own padding and border added back on. pub used_height: f64,}
/// Plain-data inputs to one table's row heights, narrowed to what §17.5.3 reads.#[derive(Debug, Clone, PartialEq)]pub struct TableRowsInputs { /// The vertical component of the table's `border-spacing` (§17.6.1), the counterpart /// of [`TableColumnsInputs::border_spacing`] on the block axis: a table of `n` rows /// has `n + 1` of these gaps inside its content box. pub border_spacing: f64, pub padding_top: f64, pub padding_bottom: f64, pub border_top: f64, pub border_bottom: f64, /// Every row of the table, in document order, anonymous rows (§17.2.1) included -- /// a generated row is a row like any other here, the box tree having already decided /// which cells belong to it. pub rows: Vec<TableRowInput>,}
/// One candidate's column half: plain data in, plain data out. Every candidate module the/// generation loop compiles exports a function of this type named `columns`.pub type TableColumnsResolver = fn(&TableColumnsInputs) -> TableColumnsOutputs;
/// One candidate's row half, exported as `rows` beside `columns` -- the same two-function/// shape `crate::margin`'s `MarginClassifyResolver`/`MarginCombineResolver` pair takes.pub type TableRowsResolver = fn(&TableRowsInputs) -> TableRowsOutputs;
/// This function's deliberately wrong starting candidate for the column half (this/// module's own doc comment): every column is as wide as the table's whole content box/// and starts at its left content edge, `border-spacing` and every cell's own `width` are/// ignored outright, and the table keeps whatever width it was given -- the width a/// `display: table-cell` box got when it was stacked as an ordinary block filling its/// containing block.pub fn columns_baseline(inputs: &TableColumnsInputs) -> TableColumnsOutputs { let content_width = (inputs.border_box_width - inputs.padding_left - inputs.padding_right - inputs.border_left - inputs.border_right) .max(0.0); TableColumnsOutputs { columns: inputs.columns.iter().map(|_| TableColumn { width: content_width, x: 0.0 }).collect(), used_width: inputs.border_box_width, }}
/// This function's deliberately wrong starting candidate for the row half: a row is as/// tall as its cells stacked one under the other rather than as its tallest cell, rows/// follow each other with no `border-spacing` between them, and a row's own `height` is/// ignored -- normal-flow block stacking, which is what the engine did with these boxes/// before this module existed.pub fn rows_baseline(inputs: &TableRowsInputs) -> TableRowsOutputs { let mut rows = Vec::with_capacity(inputs.rows.len()); let mut y = 0.0; for row in &inputs.rows { let height: f64 = row.cell_heights.iter().sum(); rows.push(TableRow { height, y }); y += height; } TableRowsOutputs { rows, used_height: y + inputs.padding_top + inputs.padding_bottom + inputs.border_top + inputs.border_bottom, }}
#[cfg(test)]mod tests { use super::*;
fn cell(width: LengthPercentageOrAuto) -> TableColumnInput { TableColumnInput { width, padding_left: 0.0, padding_right: 0.0, border_left: 0.0, border_right: 0.0, border_box_sizing: false, } }
fn columns_inputs(columns: Vec<TableColumnInput>) -> TableColumnsInputs { TableColumnsInputs { border_box_width: 400.0, padding_left: 0.0, padding_right: 0.0, border_left: 0.0, border_right: 0.0, border_spacing: 10.0, columns, } }
fn rows_inputs(rows: Vec<TableRowInput>) -> TableRowsInputs { TableRowsInputs { border_spacing: 10.0, padding_top: 0.0, padding_bottom: 0.0, border_top: 0.0, border_bottom: 0.0, rows, } }
#[test] fn columns_baseline_gives_every_column_the_whole_table_width() { let out = columns_baseline(&columns_inputs(vec![ cell(LengthPercentageOrAuto::Length(50.0)), cell(LengthPercentageOrAuto::Auto), ])); assert_eq!(out.columns, vec![ TableColumn { width: 400.0, x: 0.0 }, TableColumn { width: 400.0, x: 0.0 }, ]); assert_eq!(out.used_width, 400.0, "the width that went in, never widened by the columns"); }
/// The wrongness that matters most on the horizontal axis: a cell's own `width` and /// the spacing around it change nothing. #[test] fn columns_baseline_ignores_the_cell_widths_and_the_spacing() { let mut inputs = columns_inputs(vec![cell(LengthPercentageOrAuto::Length(120.0))]); inputs.border_spacing = 0.0; let zero_spacing = columns_baseline(&inputs); inputs.border_spacing = 25.0; assert_eq!(columns_baseline(&inputs).columns, zero_spacing.columns); assert_eq!(zero_spacing.columns[0].width, 400.0, "not the 120px the cell asked for"); }
#[test] fn columns_baseline_folds_the_tables_own_edges_out_of_the_content_width() { let mut inputs = columns_inputs(vec![cell(LengthPercentageOrAuto::Auto)]); inputs.padding_left = 12.0; inputs.padding_right = 8.0; inputs.border_left = 2.0; inputs.border_right = 3.0; assert_eq!(columns_baseline(&inputs).columns[0].width, 375.0, "400 - 20 padding - 5 border"); }
#[test] fn rows_baseline_stacks_the_cells_of_one_row_instead_of_overlaying_them() { let out = rows_baseline(&rows_inputs(vec![TableRowInput { height: LengthPercentageOrAuto::Auto, cell_heights: vec![20.0, 35.0], }])); assert_eq!(out.rows, vec![TableRow { height: 55.0, y: 0.0 }], "20 + 35, not max(20, 35)"); assert_eq!(out.used_height, 55.0); }
#[test] fn rows_baseline_leaves_no_spacing_between_rows() { let out = rows_baseline(&rows_inputs(vec![ TableRowInput { height: LengthPercentageOrAuto::Auto, cell_heights: vec![20.0] }, TableRowInput { height: LengthPercentageOrAuto::Auto, cell_heights: vec![30.0] }, ])); assert_eq!(out.rows[1].y, 20.0, "flush against the first row, ignoring the 10px border-spacing"); assert_eq!(out.used_height, 50.0, "and no spacing above the first row or below the last"); }
#[test] fn rows_baseline_ignores_a_rows_own_height() { let out = rows_baseline(&rows_inputs(vec![TableRowInput { height: LengthPercentageOrAuto::Length(90.0), cell_heights: vec![20.0], }])); assert_eq!(out.rows[0].height, 20.0, "not the 90px the row asked for"); }
#[test] fn rows_baseline_adds_the_tables_own_edges_back_onto_the_used_height() { let mut inputs = rows_inputs(vec![TableRowInput { height: LengthPercentageOrAuto::Auto, cell_heights: vec![40.0] }]); inputs.padding_top = 5.0; inputs.padding_bottom = 6.0; inputs.border_top = 1.0; inputs.border_bottom = 2.0; assert_eq!(rows_baseline(&inputs).used_height, 54.0, "40 + 11 padding + 3 border"); }
#[test] fn no_rows_and_no_columns_is_an_empty_answer() { assert!(columns_baseline(&columns_inputs(Vec::new())).columns.is_empty()); let rows = rows_baseline(&rows_inputs(Vec::new())); assert!(rows.rows.is_empty()); assert_eq!(rows.used_height, 0.0); }}