render: align table track geometry
This commit is contained in:
1 parent
10509a222b
commit
771528c7ff
3 files changed
+221
-41
No files matched your search
@@ -188,6 +188,9 @@ fn apply_presentational_hints(node: &obscura_dom::tree::Node, style: &mut crate:
|
|||||||
if let Some(align) = node.get_attribute("align") {
|
if let Some(align) = node.get_attribute("align") {
|
||||||
crate::style::apply_inline(style, &format!("text-align: {}", align));
|
crate::style::apply_inline(style, &format!("text-align: {}", align));
|
||||||
}
|
}
|
||||||
|
if let Some(valign) = node.get_attribute("valign") {
|
||||||
|
crate::style::apply_inline(style, &format!("vertical-align: {}", valign));
|
||||||
|
}
|
||||||
if let Some(cellspacing) = node.get_attribute("cellspacing") {
|
if let Some(cellspacing) = node.get_attribute("cellspacing") {
|
||||||
if cellspacing.chars().all(|c| c.is_ascii_digit()) {
|
if cellspacing.chars().all(|c| c.is_ascii_digit()) {
|
||||||
crate::style::apply_inline(style, &format!("border-spacing: {}px", cellspacing));
|
crate::style::apply_inline(style, &format!("border-spacing: {}px", cellspacing));
|
||||||
@@ -223,14 +226,35 @@ fn cascade_walk(
|
|||||||
styles: &mut HashMap<NodeId, crate::LayoutStyle>,
|
styles: &mut HashMap<NodeId, crate::LayoutStyle>,
|
||||||
parent_props: &std::rc::Rc<HashMap<String, String>>,
|
parent_props: &std::rc::Rc<HashMap<String, String>>,
|
||||||
quirks_mode: bool,
|
quirks_mode: bool,
|
||||||
|
inherited_cell_padding: Option<f32>,
|
||||||
) {
|
) {
|
||||||
let Some(node) = tree.get_node(id) else { return };
|
let Some(node) = tree.get_node(id) else { return };
|
||||||
let is_element = node.is_element();
|
let is_element = node.is_element();
|
||||||
// The custom-property map in force for this node's subtree: the parent's,
|
// The custom-property map in force for this node's subtree: the parent's,
|
||||||
// unless this element declares its own `--x` (then a richer map).
|
// unless this element declares its own `--x` (then a richer map).
|
||||||
let mut this_props = parent_props.clone();
|
let mut this_props = parent_props.clone();
|
||||||
|
let mut descendant_cell_padding = inherited_cell_padding;
|
||||||
if let Some(elem) = node.as_element() {
|
if let Some(elem) = node.as_element() {
|
||||||
|
if elem.local.as_ref() == "table" {
|
||||||
|
// `cellpadding` is a table-scoped presentational hint applied to
|
||||||
|
// its cells, below author CSS. Entering any nested table resets
|
||||||
|
// the outer table's value before reading the nested attribute.
|
||||||
|
descendant_cell_padding = node
|
||||||
|
.get_attribute("cellpadding")
|
||||||
|
.and_then(|value| value.trim().parse::<f32>().ok())
|
||||||
|
.filter(|value| value.is_finite() && *value >= 0.0);
|
||||||
|
}
|
||||||
let mut style = crate::style::ua_style(elem.local.as_ref());
|
let mut style = crate::style::ua_style(elem.local.as_ref());
|
||||||
|
if matches!(elem.local.as_ref(), "td" | "th") {
|
||||||
|
if let Some(padding) = inherited_cell_padding {
|
||||||
|
style.padding = crate::Edges {
|
||||||
|
top: padding,
|
||||||
|
right: padding,
|
||||||
|
bottom: padding,
|
||||||
|
left: padding,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
if quirks_mode && elem.local.as_ref() == "form" {
|
if quirks_mode && elem.local.as_ref() == "form" {
|
||||||
// Legacy HTML/quirks rendering keeps one em after forms. Standards
|
// Legacy HTML/quirks rendering keeps one em after forms. Standards
|
||||||
// mode does not; Hacker News and many old document templates omit
|
// mode does not; Hacker News and many old document templates omit
|
||||||
@@ -319,6 +343,7 @@ fn cascade_walk(
|
|||||||
styles,
|
styles,
|
||||||
&this_props,
|
&this_props,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
descendant_cell_padding,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if is_element {
|
if is_element {
|
||||||
@@ -384,13 +409,13 @@ pub fn layout_dom_with_images(
|
|||||||
&mut styles,
|
&mut styles,
|
||||||
&root_props,
|
&root_props,
|
||||||
quirks_mode,
|
quirks_mode,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
if timing {
|
if timing {
|
||||||
let (r, i, c, l, u) = sheet.debug_stats();
|
let (r, i, c, l, u) = sheet.debug_stats();
|
||||||
eprintln!("[timing] parse+index={:?} cascade={:?} rules={} id_keys={} class_keys={} local_keys={} universal={}", t_parse, t1.elapsed(), r, i, c, l, u);
|
eprintln!("[timing] parse+index={:?} cascade={:?} rules={} id_keys={} class_keys={} local_keys={} universal={}", t_parse, t1.elapsed(), r, i, c, l, u);
|
||||||
}
|
}
|
||||||
grow_trailing_auto_cells(tree, &mut styles);
|
grow_trailing_auto_cells(tree, &mut styles);
|
||||||
propagate_border_spacing(tree, &mut styles);
|
|
||||||
|
|
||||||
// The leaf context is the index of a cosmic-text inline formatting
|
// The leaf context is the index of a cosmic-text inline formatting
|
||||||
// context in `engine`; leaves without text carry no context.
|
// context in `engine`; leaves without text carry no context.
|
||||||
@@ -429,6 +454,8 @@ pub fn layout_dom_with_images(
|
|||||||
text_transform: crate::TextTransform,
|
text_transform: crate::TextTransform,
|
||||||
italic: bool,
|
italic: bool,
|
||||||
box_sizing: crate::BoxSizing,
|
box_sizing: crate::BoxSizing,
|
||||||
|
border_collapse: bool,
|
||||||
|
table_vertical_align: Option<crate::VerticalAlign>,
|
||||||
/// Containing-block width in px for the current element, carried
|
/// Containing-block width in px for the current element, carried
|
||||||
/// down so percentage padding/margin (which resolve against the
|
/// down so percentage padding/margin (which resolve against the
|
||||||
/// containing block WIDTH, all sides) can be turned into px before
|
/// containing block WIDTH, all sides) can be turned into px before
|
||||||
@@ -453,6 +480,8 @@ pub fn layout_dom_with_images(
|
|||||||
text_transform: crate::TextTransform::None,
|
text_transform: crate::TextTransform::None,
|
||||||
italic: false,
|
italic: false,
|
||||||
box_sizing: crate::BoxSizing::ContentBox,
|
box_sizing: crate::BoxSizing::ContentBox,
|
||||||
|
border_collapse: false,
|
||||||
|
table_vertical_align: None,
|
||||||
cb_width: 0.0,
|
cb_width: 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -586,6 +615,24 @@ pub fn layout_dom_with_images(
|
|||||||
style.box_sizing = inh.box_sizing;
|
style.box_sizing = inh.box_sizing;
|
||||||
}
|
}
|
||||||
inh.box_sizing = style.box_sizing;
|
inh.box_sizing = style.box_sizing;
|
||||||
|
match style.border_collapse {
|
||||||
|
Some(value) => inh.border_collapse = value,
|
||||||
|
None => style.border_collapse = Some(inh.border_collapse),
|
||||||
|
}
|
||||||
|
let is_table_part = tree.get_node(id).map_or(false, |node| {
|
||||||
|
node.as_element().map_or(false, |name| {
|
||||||
|
matches!(
|
||||||
|
name.local.as_ref(),
|
||||||
|
"tbody" | "thead" | "tfoot" | "tr" | "td" | "th"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if is_table_part {
|
||||||
|
match style.vertical_align {
|
||||||
|
Some(value) => inh.table_vertical_align = Some(value),
|
||||||
|
None => style.vertical_align = inh.table_vertical_align,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Resolve font/viewport-relative box edges now that their
|
// Resolve font/viewport-relative box edges now that their
|
||||||
// reference sizes are known. Percentage padding/margin then
|
// reference sizes are known. Percentage padding/margin then
|
||||||
@@ -702,6 +749,11 @@ pub fn layout_dom_with_images(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Border-collapse is inherited, so only distribute a table's
|
||||||
|
// effective spacing to the legacy flex fallback after the computed
|
||||||
|
// top-down values are known.
|
||||||
|
propagate_border_spacing(tree, &mut styles);
|
||||||
|
|
||||||
// Resolve native input-control intrinsic border-box geometry after
|
// Resolve native input-control intrinsic border-box geometry after
|
||||||
// inheritance and author cascading. Text-like inputs use the HTML
|
// inheritance and author cascading. Text-like inputs use the HTML
|
||||||
// `size` attribute (20 by default) and the control's own computed
|
// `size` attribute (20 by default) and the control's own computed
|
||||||
@@ -902,9 +954,12 @@ pub fn layout_dom_with_images(
|
|||||||
// id_map iteration order is not.
|
// id_map iteration order is not.
|
||||||
tables.sort_by(|a, b| b.2.cmp(&a.2));
|
tables.sort_by(|a, b| b.2.cmp(&a.2));
|
||||||
for (tnode, dom, _depth) in tables {
|
for (tnode, dom, _depth) in tables {
|
||||||
|
let Some(table_style) = styles.get(&dom) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
// A percentage-width table resolves against its container, so
|
// A percentage-width table resolves against its container, so
|
||||||
// leave taffy's percentage handling in place.
|
// leave taffy's percentage handling in place.
|
||||||
let width_style = styles.get(&dom).map(|s| s.width.clone());
|
let width_style = Some(table_style.width);
|
||||||
if matches!(width_style, Some(crate::Dimension::Percent(_))) {
|
if matches!(width_style, Some(crate::Dimension::Percent(_))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -935,21 +990,30 @@ pub fn layout_dom_with_images(
|
|||||||
);
|
);
|
||||||
taffy_tree.layout(tnode).map(|l| l.size.width).unwrap_or(0.0)
|
taffy_tree.layout(tnode).map(|l| l.size.width).unwrap_or(0.0)
|
||||||
};
|
};
|
||||||
let preferred = match width_style {
|
let inline_outer_edges = table_inline_outer_edges(table_style);
|
||||||
|
let preferred_outer = match width_style {
|
||||||
|
Some(crate::Dimension::Px(w))
|
||||||
|
if table_style.box_sizing == crate::BoxSizing::ContentBox =>
|
||||||
|
{
|
||||||
|
w + inline_outer_edges
|
||||||
|
}
|
||||||
Some(crate::Dimension::Px(w)) => w,
|
Some(crate::Dimension::Px(w)) => w,
|
||||||
_ => max_c,
|
_ => max_c,
|
||||||
};
|
};
|
||||||
let used = preferred.max(min_c).min(viewport.0.max(min_c));
|
let used_outer = preferred_outer
|
||||||
// Distribute `used` across the columns proportionally between
|
.max(min_c)
|
||||||
// each column's own min-content and max-content width, the way
|
.min(viewport.0.max(min_c));
|
||||||
// CSS tables do, instead of letting the grid hand every auto
|
let used_declaration =
|
||||||
// track an equal share of the surplus (which over-widens narrow
|
if table_style.box_sizing == crate::BoxSizing::ContentBox {
|
||||||
// label columns and starves wide prose columns). NetSurf
|
(used_outer - inline_outer_edges).max(0.0)
|
||||||
// layout.c layout_table: col.width = col.min + (col.max - col.min)
|
} else {
|
||||||
// * (used - min) / (max - min). The table's border, padding, and
|
used_outer
|
||||||
// inter-column border-spacing are layout-invariant, so they are
|
};
|
||||||
// recovered once from the whole-table min-content width and kept
|
// Distribute the used track space proportionally between
|
||||||
// out of the interpolation.
|
// each column's own min-content and max-content width, the
|
||||||
|
// way CSS tables do, instead of letting the grid hand every
|
||||||
|
// auto track an equal share of the surplus (which over-widens
|
||||||
|
// narrow label columns and starves wide prose columns).
|
||||||
let cells: Vec<(taffy::NodeId, usize, usize)> = taffy_tree
|
let cells: Vec<(taffy::NodeId, usize, usize)> = taffy_tree
|
||||||
.children(tnode)
|
.children(tnode)
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
@@ -1033,13 +1097,19 @@ pub fn layout_dom_with_images(
|
|||||||
col_max[j] = col_min[j];
|
col_max[j] = col_min[j];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// overhead = table border + padding + inter-column spacing,
|
// Track space is the final table border box minus its
|
||||||
// recovered from the whole-table min-content width
|
// actual border/padding, the two outer spacing bands,
|
||||||
// (from the content mins, before specified widths pin
|
// and the spacing between columns. Inferring this from
|
||||||
// any column).
|
// min-content is wrong when a specified cell/column
|
||||||
let sum_min_content: f32 = col_min.iter().sum();
|
// width already inflated that measurement: the fixed
|
||||||
let overhead = (min_c - sum_min_content).max(0.0);
|
// width gets counted as "overhead", starving later
|
||||||
let target = (used - overhead).max(0.0);
|
// auto columns and forcing avoidable text wrapping.
|
||||||
|
let (horizontal_spacing, _) = table_spacing(table_style);
|
||||||
|
let interior_spacing =
|
||||||
|
horizontal_spacing * ncols.saturating_sub(1) as f32;
|
||||||
|
let target =
|
||||||
|
(used_outer - inline_outer_edges - interior_spacing)
|
||||||
|
.max(0.0);
|
||||||
// Specified column widths (a `<col>` or colspan-1 cell
|
// Specified column widths (a `<col>` or colspan-1 cell
|
||||||
// width, recorded at build time) pin their columns:
|
// width, recorded at build time) pin their columns:
|
||||||
// px directly, percent against the table's content
|
// px directly, percent against the table's content
|
||||||
@@ -1047,7 +1117,8 @@ pub fn layout_dom_with_images(
|
|||||||
// too-narrow spec never crushes its content. The
|
// too-narrow spec never crushes its content. The
|
||||||
// remaining space interpolates across the auto
|
// remaining space interpolates across the auto
|
||||||
// columns exactly as before.
|
// columns exactly as before.
|
||||||
if let Some((spec_px, spec_pct)) = ifc_items.table_cols.get(&tnode) {
|
let specified_columns = ifc_items.table_cols.get(&tnode);
|
||||||
|
if let Some((spec_px, spec_pct)) = specified_columns {
|
||||||
for j in 0..ncols {
|
for j in 0..ncols {
|
||||||
let spec = spec_px
|
let spec = spec_px
|
||||||
.get(j)
|
.get(j)
|
||||||
@@ -1063,13 +1134,58 @@ pub fn layout_dom_with_images(
|
|||||||
}
|
}
|
||||||
let sum_min: f32 = col_min.iter().sum();
|
let sum_min: f32 = col_min.iter().sum();
|
||||||
let sum_max: f32 = col_max.iter().sum();
|
let sum_max: f32 = col_max.iter().sum();
|
||||||
let widths: Vec<f32> = if sum_max <= sum_min || target <= sum_min {
|
let widths: Vec<f32> = if target <= sum_min {
|
||||||
col_min.clone()
|
col_min.clone()
|
||||||
} else if target >= sum_max {
|
} else if target >= sum_max {
|
||||||
// Table wider than max-content (explicit/percentage-ish
|
// Surplus follows the table-layout priority: auto
|
||||||
// width): hand the extra out equally, as real tables do.
|
// columns absorb it before fixed and percentage
|
||||||
let extra = (target - sum_max) / ncols as f32;
|
// columns. This is important even when every
|
||||||
col_max.iter().map(|m| m + extra).collect()
|
// column has min==max (a one-word auto cell still
|
||||||
|
// fills the remainder of a definite-width table).
|
||||||
|
let mut result = col_max.clone();
|
||||||
|
let extra = target - sum_max;
|
||||||
|
let is_px = |j: usize| {
|
||||||
|
specified_columns
|
||||||
|
.and_then(|(px, _)| px.get(j))
|
||||||
|
.copied()
|
||||||
|
.flatten()
|
||||||
|
.is_some()
|
||||||
|
};
|
||||||
|
let is_pct = |j: usize| {
|
||||||
|
specified_columns
|
||||||
|
.and_then(|(_, pct)| pct.get(j))
|
||||||
|
.copied()
|
||||||
|
.flatten()
|
||||||
|
.is_some()
|
||||||
|
};
|
||||||
|
let mut candidates: Vec<usize> = (0..ncols)
|
||||||
|
.filter(|&j| !is_px(j) && !is_pct(j) && col_max[j] > 0.0)
|
||||||
|
.collect();
|
||||||
|
if candidates.is_empty() {
|
||||||
|
candidates = (0..ncols)
|
||||||
|
.filter(|&j| !is_px(j) && !is_pct(j))
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
if candidates.is_empty() {
|
||||||
|
candidates = (0..ncols).filter(|&j| is_px(j)).collect();
|
||||||
|
}
|
||||||
|
if candidates.is_empty() {
|
||||||
|
candidates = (0..ncols).filter(|&j| is_pct(j)).collect();
|
||||||
|
}
|
||||||
|
if candidates.is_empty() {
|
||||||
|
candidates = (0..ncols).collect();
|
||||||
|
}
|
||||||
|
let weight_sum: f32 =
|
||||||
|
candidates.iter().map(|&j| col_max[j]).sum();
|
||||||
|
for &j in &candidates {
|
||||||
|
let share = if weight_sum > 0.0 {
|
||||||
|
extra * col_max[j] / weight_sum
|
||||||
|
} else {
|
||||||
|
extra / candidates.len() as f32
|
||||||
|
};
|
||||||
|
result[j] += share;
|
||||||
|
}
|
||||||
|
result
|
||||||
} else {
|
} else {
|
||||||
let scale = (target - sum_min) / (sum_max - sum_min);
|
let scale = (target - sum_min) / (sum_max - sum_min);
|
||||||
col_min
|
col_min
|
||||||
@@ -1080,7 +1196,7 @@ pub fn layout_dom_with_images(
|
|||||||
};
|
};
|
||||||
if let Ok(cur) = taffy_tree.style(tnode) {
|
if let Ok(cur) = taffy_tree.style(tnode) {
|
||||||
let mut s = cur.clone();
|
let mut s = cur.clone();
|
||||||
s.size.width = length(used);
|
s.size.width = length(used_declaration);
|
||||||
s.grid_template_columns = widths
|
s.grid_template_columns = widths
|
||||||
.iter()
|
.iter()
|
||||||
.map(|w| {
|
.map(|w| {
|
||||||
@@ -1094,7 +1210,7 @@ pub fn layout_dom_with_images(
|
|||||||
}
|
}
|
||||||
} else if let Ok(cur) = taffy_tree.style(tnode) {
|
} else if let Ok(cur) = taffy_tree.style(tnode) {
|
||||||
let mut s = cur.clone();
|
let mut s = cur.clone();
|
||||||
s.size.width = length(used);
|
s.size.width = length(used_declaration);
|
||||||
let _ = taffy_tree.set_style(tnode, s);
|
let _ = taffy_tree.set_style(tnode, s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1296,7 +1412,10 @@ fn propagate_border_spacing(tree: &DomTree, styles: &mut HashMap<NodeId, crate::
|
|||||||
if local_name(tree, id).as_deref() != Some("table") {
|
if local_name(tree, id).as_deref() != Some("table") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let Some((h, v)) = styles.get(&id).and_then(|s| s.border_spacing) else { continue };
|
let Some(table_style) = styles.get(&id) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let (h, v) = table_spacing(table_style);
|
||||||
if let Some(s) = styles.get_mut(&id) {
|
if let Some(s) = styles.get_mut(&id) {
|
||||||
s.row_gap = Some(v);
|
s.row_gap = Some(v);
|
||||||
}
|
}
|
||||||
@@ -2228,6 +2347,28 @@ fn collect_table_rows(tree: &DomTree, id: NodeId, rows: &mut Vec<NodeId>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Effective separate-border spacing. Collapsed tables contribute no spacing
|
||||||
|
/// at either the outer table edges or between tracks.
|
||||||
|
fn table_spacing(style: &crate::LayoutStyle) -> (f32, f32) {
|
||||||
|
if style.border_collapse.unwrap_or(false) {
|
||||||
|
(0.0, 0.0)
|
||||||
|
} else {
|
||||||
|
style.border_spacing.unwrap_or((0.0, 0.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Horizontal non-track area in the table's border box, excluding the gaps
|
||||||
|
/// *between* columns: authored border/padding plus one border-spacing unit at
|
||||||
|
/// each outer edge.
|
||||||
|
fn table_inline_outer_edges(style: &crate::LayoutStyle) -> f32 {
|
||||||
|
let (spacing, _) = table_spacing(style);
|
||||||
|
style.border.left
|
||||||
|
+ style.border.right
|
||||||
|
+ style.padding.left
|
||||||
|
+ style.padding.right
|
||||||
|
+ spacing * 2.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a `<table>` as a CSS grid. Modeling the table as a grid is what makes
|
/// Build a `<table>` as a CSS grid. Modeling the table as a grid is what makes
|
||||||
/// columns negotiate a shared width across every row (min-content/max-content
|
/// columns negotiate a shared width across every row (min-content/max-content
|
||||||
/// track sizing), which the old flex-row-per-`<tr>` stack could not do: each
|
/// track sizing), which the old flex-row-per-`<tr>` stack could not do: each
|
||||||
@@ -2469,7 +2610,17 @@ fn build_table(
|
|||||||
};
|
};
|
||||||
taffy::GridTemplateComponent::Single(taffy::MinMax { min, max: taffy::MaxTrackSizingFunction::auto() })
|
taffy::GridTemplateComponent::Single(taffy::MinMax { min, max: taffy::MaxTrackSizingFunction::auto() })
|
||||||
};
|
};
|
||||||
let mut tstyle = to_taffy_style(style);
|
// In the separate-border model, border-spacing also exists between the
|
||||||
|
// table edge and the first/last row and column. Grid `gap` only covers
|
||||||
|
// interior tracks, so model the two outer spacing bands as internal
|
||||||
|
// layout padding while leaving the computed CSS padding unchanged.
|
||||||
|
let (horizontal_spacing, vertical_spacing) = table_spacing(style);
|
||||||
|
let mut grid_style = style.clone();
|
||||||
|
grid_style.padding.left += horizontal_spacing;
|
||||||
|
grid_style.padding.right += horizontal_spacing;
|
||||||
|
grid_style.padding.top += vertical_spacing;
|
||||||
|
grid_style.padding.bottom += vertical_spacing;
|
||||||
|
let mut tstyle = to_taffy_style(&grid_style);
|
||||||
tstyle.display = Display::Grid;
|
tstyle.display = Display::Grid;
|
||||||
// A percentage width resolves against the container, so keep it and let the
|
// A percentage width resolves against the container, so keep it and let the
|
||||||
// used-width pass leave it to taffy. Any other width (px or auto) is forced
|
// used-width pass leave it to taffy. Any other width (px or auto) is forced
|
||||||
@@ -2479,9 +2630,10 @@ fn build_table(
|
|||||||
}
|
}
|
||||||
tstyle.grid_template_columns = (0..ncols).map(col).collect();
|
tstyle.grid_template_columns = (0..ncols).map(col).collect();
|
||||||
tstyle.grid_template_rows = (0..nrows).map(row_track).collect();
|
tstyle.grid_template_rows = (0..nrows).map(row_track).collect();
|
||||||
if let Some((h, v)) = style.border_spacing {
|
tstyle.gap = taffy::Size {
|
||||||
tstyle.gap = taffy::Size { width: length(h), height: length(v) };
|
width: length(horizontal_spacing),
|
||||||
}
|
height: length(vertical_spacing),
|
||||||
|
};
|
||||||
let table_node = taffy_tree.new_with_children(tstyle, &children).ok()?;
|
let table_node = taffy_tree.new_with_children(tstyle, &children).ok()?;
|
||||||
id_map.insert(table_node, id);
|
id_map.insert(table_node, id);
|
||||||
if col_px.iter().any(Option::is_some) || col_pct.iter().any(Option::is_some) {
|
if col_px.iter().any(Option::is_some) || col_pct.iter().any(Option::is_some) {
|
||||||
|
|||||||
@@ -345,6 +345,12 @@ pub struct LayoutStyle {
|
|||||||
/// display mode, so `dom::propagate_border_spacing` distributes this down
|
/// display mode, so `dom::propagate_border_spacing` distributes this down
|
||||||
/// as the table's own row gap and each descendant `<tr>`'s column gap.
|
/// as the table's own row gap and each descendant `<tr>`'s column gap.
|
||||||
pub border_spacing: Option<(f32, f32)>,
|
pub border_spacing: Option<(f32, f32)>,
|
||||||
|
/// Computed `border-collapse`. This property is inherited; `None` means
|
||||||
|
/// no value was specified on this node yet and is resolved top-down
|
||||||
|
/// before table construction. The collapsed-border conflict/paint model
|
||||||
|
/// is still approximate, but collapsed tables must at minimum contribute
|
||||||
|
/// no border-spacing to their geometry.
|
||||||
|
pub border_collapse: Option<bool>,
|
||||||
|
|
||||||
// Positioning. `position: absolute|fixed` takes the box out of normal flow.
|
// Positioning. `position: absolute|fixed` takes the box out of normal flow.
|
||||||
pub position: Option<taffy::Position>,
|
pub position: Option<taffy::Position>,
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
};
|
};
|
||||||
style.border_color = Some([118, 118, 118, 255]);
|
style.border_color = Some([118, 118, 118, 255]);
|
||||||
style.background_color = Some([255, 255, 255, 255]);
|
style.background_color = Some([255, 255, 255, 255]);
|
||||||
} else if tag == "table" || tag == "tbody" {
|
} else if matches!(tag, "table" | "tbody" | "thead" | "tfoot") {
|
||||||
style.display = Display::Flex;
|
style.display = Display::Flex;
|
||||||
style.internal_flex_container = true;
|
style.internal_flex_container = true;
|
||||||
style.flex_direction = Some(taffy::FlexDirection::Column);
|
style.flex_direction = Some(taffy::FlexDirection::Column);
|
||||||
@@ -144,8 +144,19 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
// out sideways. (Fully matching CSS auto table layout, where a table
|
// out sideways. (Fully matching CSS auto table layout, where a table
|
||||||
// grows to fit unshrinkable content, needs real table layout.)
|
// grows to fit unshrinkable content, needs real table layout.)
|
||||||
style.min_width = crate::Dimension::Px(0.0);
|
style.min_width = crate::Dimension::Px(0.0);
|
||||||
if tag == "tbody" {
|
if tag == "table" {
|
||||||
|
// Chromium's HTML UA sheet makes the table grid border-box and
|
||||||
|
// supplies the traditional two-pixel separate-border spacing.
|
||||||
|
// Author declarations and the legacy `cellspacing` hint cascade
|
||||||
|
// over these values.
|
||||||
|
style.box_sizing = crate::BoxSizing::BorderBox;
|
||||||
|
style.border_spacing = Some((2.0, 2.0));
|
||||||
|
style.border_collapse = Some(false);
|
||||||
|
} else {
|
||||||
style.width = crate::Dimension::Percent(1.0);
|
style.width = crate::Dimension::Percent(1.0);
|
||||||
|
// The row-group UA rule is the source of the effective default
|
||||||
|
// middle alignment; rows and cells inherit it below.
|
||||||
|
style.vertical_align = Some(crate::VerticalAlign::Middle);
|
||||||
}
|
}
|
||||||
} else if tag == "tr" {
|
} else if tag == "tr" {
|
||||||
style.internal_flex_container = true;
|
style.internal_flex_container = true;
|
||||||
@@ -160,12 +171,13 @@ pub fn ua_style(tag: &str) -> LayoutStyle {
|
|||||||
style.internal_flex_container = true;
|
style.internal_flex_container = true;
|
||||||
style.flex_direction = Some(taffy::FlexDirection::Column);
|
style.flex_direction = Some(taffy::FlexDirection::Column);
|
||||||
style.align_items = Some(taffy::AlignItems::FLEX_START);
|
style.align_items = Some(taffy::AlignItems::FLEX_START);
|
||||||
style.padding = Edges { top: 0.0, right: 5.0, bottom: 0.0, left: 0.0 };
|
style.padding = Edges {
|
||||||
|
top: 1.0,
|
||||||
|
right: 1.0,
|
||||||
|
bottom: 1.0,
|
||||||
|
left: 1.0,
|
||||||
|
};
|
||||||
style.min_width = crate::Dimension::Px(0.0);
|
style.min_width = crate::Dimension::Px(0.0);
|
||||||
// Cells are effectively middle-aligned by default in browsers (the
|
|
||||||
// HTML UA sheet's row-group vertical-align inherited into cells);
|
|
||||||
// an author `vertical-align` on the cell overrides this.
|
|
||||||
style.vertical_align = Some(crate::VerticalAlign::Middle);
|
|
||||||
if tag == "th" {
|
if tag == "th" {
|
||||||
style.font_weight = Some("bold".to_string());
|
style.font_weight = Some("bold".to_string());
|
||||||
}
|
}
|
||||||
@@ -688,6 +700,16 @@ fn apply_value(style: &mut LayoutStyle, name: &str, value: &str) {
|
|||||||
style.border_spacing = Some((h, *dims.get(1).unwrap_or(&h)));
|
style.border_spacing = Some((h, *dims.get(1).unwrap_or(&h)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"border-collapse" => {
|
||||||
|
style.border_collapse = match value.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"collapse" => Some(true),
|
||||||
|
"separate" | "initial" | "revert" | "revert-layer" => Some(false),
|
||||||
|
// This is an inherited property, so both an omitted value and
|
||||||
|
// an explicit inherit/unset are resolved in the top-down pass.
|
||||||
|
"inherit" | "unset" => None,
|
||||||
|
_ => style.border_collapse,
|
||||||
|
};
|
||||||
|
}
|
||||||
"grid-template-columns" => {
|
"grid-template-columns" => {
|
||||||
let (tracks, names) = parse_track_list_named(value);
|
let (tracks, names) = parse_track_list_named(value);
|
||||||
style.grid_template_columns = tracks;
|
style.grid_template_columns = tracks;
|
||||||
|
|||||||
Reference in new issue
Block a user