gecko-dev/servo/components/layout/table.rs

1249 строки
54 KiB
Rust
Исходник Обычный вид История

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//! CSS table formatting contexts.
#![deny(unsafe_code)]
use app_units::Au;
use block::{BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer};
use block::{ISizeConstraintInput, ISizeConstraintSolution};
servo: Merge #15417 - layout: Remove cached thread local context from LayoutContext, and use LayoutContext for assign_inline_sizes() (from stshine:column-flexbox); r=emilio <!-- Please describe your changes on the following line: --> According to https://github.com/servo/servo/pull/3069 the cached thread local context is introduced for green threads. Now green threads has gone, and the existence of cache force us to create a `LayoutContext`, an `AssignISizes` and an `AssignBSizes` for each flow during parallel layout, so the pull request tries to remove it. And it also switch `assign_inline_sizes()` to accept a `LayoutContext` parameter, as according to my current design we need to do full layout to some flex items for column flexbox during assign isize traversal. Part of #14123. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [X] These changes do not require tests because refactoring <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: e2b494b1d08b8929ca7e5ae369304f41af81ace2 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 9cd92ed44acfaa3b2ba04e345b33d21aff3d752c
2017-02-08 04:16:05 +03:00
use context::LayoutContext;
use display_list::{BlockFlowDisplayListBuilding, BorderPaintingMode};
use display_list::{DisplayListBuildState, StackingContextCollectionFlags, StackingContextCollectionState};
use euclid::Point2D;
use flow::{BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ImmutableFlowUtils, GetBaseFlow, OpaqueFlow};
use flow_list::{FlowListIterator, MutFlowListIterator};
use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
use gfx_traits::print_tree::PrintTree;
use layout_debug;
use model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto};
use std::{cmp, fmt};
use style::computed_values::{border_collapse, border_spacing, table_layout};
servo: Merge #12515 - Make the style crate more concrete (from servo:concrete-style); r=bholley Background: The changes to Servo code to support Stylo began in the `selectors` crate with making pseudo-elements generic, defined be the user, so that different users (such as Servo and Gecko/Stylo) could have a different set of pseudo-elements supported and parsed. Adding a trait makes sense there since `selectors` is in its own repository and has others users (or at least [one](https://github.com/SimonSapin/kuchiki)). Then we kind of kept going with the same pattern and added a bunch of traits in the `style` crate to make everything generic, allowing Servo and Gecko/Stylo to do things differently. But we’ve also added a `gecko` Cargo feature to do conditional compilation, at first to enable or disable some CSS properties and values in the Mako templates. Since we’re doing conditional compilation anyway, it’s often easier and simpler to do it more (with `#[cfg(feature = "gecko")]` and `#[cfg(feature = "servo")]`) that to keep adding traits and making everything generic. When a type is generic, any method that we want to call on it needs to be part of some trait. ---- The first several commits move some code around, mostly from `geckolib` to `style` (with `#[cfg(feature = "gecko")]`) but otherwise don’t change much. The following commits remove some traits and many type parameters through the `style` crate, replacing them with pairs of conditionally-compiled API-compatible items (types, methods, …). Simplifying code is nice to make it more maintainable, but this is motivated by another change described in https://github.com/servo/servo/pull/12391#issuecomment-232183942. (Porting Servo for that change proved difficult because some code in the `style` crate was becoming generic over `String` vs `Atom`, and this PR will help make that concrete. That change, in turn, is motivated by removing geckolib’s `[replace]` override for string-cache, in order to enable using a single Cargo "workspace" in this repository.) r? @bholley --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [x] These changes do not require new tests because refactoring <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 2d01d41a506bcbc7f26a2284b9f42390d6ef96ab --HG-- rename : servo/ports/geckolib/selector_impl.rs => servo/components/style/gecko_selector_impl.rs rename : servo/ports/geckolib/values.rs => servo/components/style/gecko_values.rs rename : servo/ports/geckolib/properties.mako.rs => servo/components/style/properties/gecko.mako.rs
2016-07-20 10:58:34 +03:00
use style::context::SharedStyleContext;
use style::logical_geometry::LogicalSize;
use style::properties::ComputedValues;
use style::properties::style_structs::Background;
use style::servo::restyle_damage::ServoRestyleDamage;
use style::values::CSSFloat;
use style::values::computed::LengthOrPercentageOrAuto;
use table_cell::TableCellFlow;
use table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance};
use table_row::{TableRowFlow, TableRowSizeData};
use table_wrapper::TableLayout;
#[allow(unsafe_code)]
unsafe impl ::flow::HasBaseFlow for TableFlow {}
/// A table flow corresponded to the table's internal table fragment under a table wrapper flow.
/// The properties `position`, `float`, and `margin-*` are used on the table wrapper fragment,
/// not table fragment per CSS 2.1 § 10.5.
#[derive(Serialize)]
#[repr(C)]
pub struct TableFlow {
pub block_flow: BlockFlow,
/// Information about the intrinsic inline-sizes of each column, computed bottom-up during
/// intrinsic inline-size bubbling.
pub column_intrinsic_inline_sizes: Vec<ColumnIntrinsicInlineSize>,
/// Information about the actual inline sizes of each column, computed top-down during actual
/// inline-size bubbling.
pub column_computed_inline_sizes: Vec<ColumnComputedInlineSize>,
/// The final width of the borders in the inline direction for each cell, computed by the
/// entire table and pushed down into each row during inline size computation.
pub collapsed_inline_direction_border_widths_for_table: Vec<Au>,
/// The final width of the borders in the block direction for each cell, computed by the
/// entire table and pushed down into each row during inline size computation.
pub collapsed_block_direction_border_widths_for_table: Vec<Au>,
/// Table-layout property
pub table_layout: TableLayout,
}
impl TableFlow {
pub fn from_fragment(fragment: Fragment) -> TableFlow {
let mut block_flow = BlockFlow::from_fragment(fragment);
let table_layout =
if block_flow.fragment().style().get_table().table_layout == table_layout::T::Fixed {
TableLayout::Fixed
} else {
TableLayout::Auto
};
TableFlow {
block_flow: block_flow,
column_intrinsic_inline_sizes: Vec::new(),
column_computed_inline_sizes: Vec::new(),
collapsed_inline_direction_border_widths_for_table: Vec::new(),
collapsed_block_direction_border_widths_for_table: Vec::new(),
table_layout: table_layout
}
}
/// Update the corresponding value of `self_inline_sizes` if a value of `kid_inline_sizes` has
/// a larger value than one of `self_inline_sizes`. Returns the minimum and preferred inline
/// sizes.
fn update_automatic_column_inline_sizes(
parent_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
child_cell_inline_sizes: &[CellIntrinsicInlineSize],
surrounding_size: Au)
-> IntrinsicISizes {
let mut total_inline_sizes = IntrinsicISizes {
minimum_inline_size: surrounding_size,
preferred_inline_size: surrounding_size,
};
let mut column_index = 0;
let mut incoming_rowspan = vec![];
for child_cell_inline_size in child_cell_inline_sizes {
// Skip any column occupied by a cell from a previous row.
while column_index < incoming_rowspan.len() && incoming_rowspan[column_index] != 1 {
if incoming_rowspan[column_index] > 1 {
incoming_rowspan[column_index] -= 1;
}
column_index += 1;
}
for _ in 0..child_cell_inline_size.column_span {
if column_index < parent_inline_sizes.len() {
// We already have some intrinsic size information for this column. Merge it in
// according to the rules specified in INTRINSIC § 4.
let parent_sizes = &mut parent_inline_sizes[column_index];
if child_cell_inline_size.column_span > 1 {
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
// 4. For now we make this column contribute no width.
} else {
let column_size = &child_cell_inline_size.column_size;
*parent_sizes = ColumnIntrinsicInlineSize {
minimum_length: cmp::max(parent_sizes.minimum_length,
column_size.minimum_length),
percentage: parent_sizes.greatest_percentage(column_size),
preferred: cmp::max(parent_sizes.preferred, column_size.preferred),
constrained: parent_sizes.constrained || column_size.constrained,
}
}
} else {
// We discovered a new column. Initialize its data.
debug_assert_eq!(column_index, parent_inline_sizes.len());
if child_cell_inline_size.column_span > 1 {
// TODO(pcwalton): Perform the recursive algorithm specified in INTRINSIC §
// 4. For now we make this column contribute no width.
parent_inline_sizes.push(ColumnIntrinsicInlineSize::new())
} else {
parent_inline_sizes.push(child_cell_inline_size.column_size)
}
}
total_inline_sizes.minimum_inline_size +=
parent_inline_sizes[column_index].minimum_length;
total_inline_sizes.preferred_inline_size +=
parent_inline_sizes[column_index].preferred;
// If this cell spans later rows, record its rowspan.
if child_cell_inline_size.row_span > 1 {
if incoming_rowspan.len() < column_index + 1 {
incoming_rowspan.resize(column_index + 1, 0);
}
incoming_rowspan[column_index] = child_cell_inline_size.row_span;
}
column_index += 1
}
}
total_inline_sizes
}
/// Updates the minimum and preferred inline-size calculation for a single row. This is
/// factored out into a separate function because we process children of rowgroups too.
fn update_column_inline_sizes_for_row(row: &TableRowFlow,
column_inline_sizes: &mut Vec<ColumnIntrinsicInlineSize>,
computation: &mut IntrinsicISizesContribution,
first_row: bool,
table_layout: TableLayout,
surrounding_inline_size: Au) {
// Read column inline-sizes from the table-row, and assign inline-size=0 for the columns
// not defined in the column group.
//
// FIXME: Need to read inline-sizes from either table-header-group OR the first table-row.
match table_layout {
TableLayout::Fixed => {
// Fixed table layout only looks at the first row.
//
// FIXME(pcwalton): This is really inefficient. We should stop after the first row!
if first_row {
for cell_inline_size in &row.cell_intrinsic_inline_sizes {
column_inline_sizes.push(cell_inline_size.column_size);
}
}
}
TableLayout::Auto => {
computation.union_block(&TableFlow::update_automatic_column_inline_sizes(
column_inline_sizes,
&row.cell_intrinsic_inline_sizes,
surrounding_inline_size))
}
}
}
/// Returns the effective spacing per cell, taking the value of `border-collapse` into account.
pub fn spacing(&self) -> border_spacing::T {
let style = self.block_flow.fragment.style();
match style.get_inheritedtable().border_collapse {
border_collapse::T::Separate => style.get_inheritedtable().border_spacing,
border_collapse::T::Collapse => border_spacing::T::zero(),
}
}
pub fn total_horizontal_spacing(&self) -> Au {
let num_columns = self.column_intrinsic_inline_sizes.len();
if num_columns == 0 {
return Au(0);
}
self.spacing().horizontal() * (num_columns as i32 + 1)
}
fn column_styles(&self) -> Vec<ColumnStyle> {
let mut styles = vec![];
for group in self.block_flow.base.child_iter()
.filter(|kid| kid.is_table_colgroup()) {
// XXXManishearth these as_foo methods should return options
// so that we can filter_map
let group = group.as_table_colgroup();
let colgroup_style = group.fragment.as_ref()
.map(|f| f.style());
// The colgroup's span attribute is only relevant when
// it has no children
// https://html.spec.whatwg.org/multipage/#forming-a-table
if group.cols.is_empty() {
let span = group.fragment.as_ref()
.map(|f| f.column_span()).unwrap_or(1);
styles.push(ColumnStyle { span, colgroup_style, col_style: None });
} else {
for col in &group.cols {
// XXXManishearth Arc-cloning colgroup_style is suboptimal
styles.push(ColumnStyle {
span: col.column_span(),
colgroup_style: colgroup_style,
col_style: Some(col.style()),
})
}
}
}
styles
}
}
impl Flow for TableFlow {
fn class(&self) -> FlowClass {
FlowClass::Table
}
fn as_mut_table(&mut self) -> &mut TableFlow {
self
}
fn as_table(&self) -> &TableFlow {
self
}
fn as_mut_block(&mut self) -> &mut BlockFlow {
&mut self.block_flow
}
fn as_block(&self) -> &BlockFlow {
&self.block_flow
}
fn mark_as_root(&mut self) {
self.block_flow.mark_as_root();
}
/// The specified column inline-sizes are set from column group and the first row for the fixed
/// table layout calculation.
/// The maximum min/pref inline-sizes of each column are set from the rows for the automatic
/// table layout calculation.
fn bubble_inline_sizes(&mut self) {
let _scope = layout_debug_scope!("table::bubble_inline_sizes {:x}",
self.block_flow.base.debug_id());
// Get column inline sizes from colgroups
for kid in self.block_flow.base.child_iter_mut().filter(|kid| kid.is_table_colgroup()) {
for specified_inline_size in &kid.as_mut_table_colgroup().inline_sizes {
self.column_intrinsic_inline_sizes.push(ColumnIntrinsicInlineSize {
minimum_length: match *specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Percentage(_) => Au(0),
LengthOrPercentageOrAuto::Length(length) => Au::from(length),
},
percentage: match *specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Length(_) => 0.0,
LengthOrPercentageOrAuto::Percentage(percentage) => percentage.0,
},
preferred: Au(0),
constrained: false,
})
}
}
self.collapsed_inline_direction_border_widths_for_table = Vec::new();
self.collapsed_block_direction_border_widths_for_table = vec![Au(0)];
let collapsing_borders = self.block_flow
.fragment
.style
.get_inheritedtable()
.border_collapse == border_collapse::T::Collapse;
let table_inline_collapsed_borders = if collapsing_borders {
Some(TableInlineCollapsedBorders {
start: CollapsedBorder::inline_start(&*self.block_flow.fragment.style,
CollapsedBorderProvenance::FromTable),
end: CollapsedBorder::inline_end(&*self.block_flow.fragment.style,
CollapsedBorderProvenance::FromTable),
})
} else {
None
};
let mut computation = IntrinsicISizesContribution::new();
let mut previous_collapsed_block_end_borders =
PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start(
&*self.block_flow.fragment.style,
CollapsedBorderProvenance::FromTable));
let mut first_row = true;
let (border_padding, _) = self.block_flow.fragment.surrounding_intrinsic_inline_size();
{
let mut iterator = TableRowIterator::new(&mut self.block_flow.base).peekable();
while let Some(row) = iterator.next() {
TableFlow::update_column_inline_sizes_for_row(
row,
&mut self.column_intrinsic_inline_sizes,
&mut computation,
first_row,
self.table_layout,
border_padding);
if collapsing_borders {
let next_index_and_sibling = iterator.peek();
let next_collapsed_borders_in_block_direction =
match next_index_and_sibling {
Some(next_sibling) => {
NextBlockCollapsedBorders::FromNextRow(
&next_sibling.as_table_row()
.preliminary_collapsed_borders
.block_start)
}
None => {
NextBlockCollapsedBorders::FromTable(
CollapsedBorder::block_end(&*self.block_flow.fragment.style,
CollapsedBorderProvenance::FromTable))
}
};
perform_border_collapse_for_row(row,
table_inline_collapsed_borders.as_ref().unwrap(),
previous_collapsed_block_end_borders,
next_collapsed_borders_in_block_direction,
&mut self.collapsed_inline_direction_border_widths_for_table,
&mut self.collapsed_block_direction_border_widths_for_table);
previous_collapsed_block_end_borders =
PreviousBlockCollapsedBorders::FromPreviousRow(
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
row.final_collapsed_borders.block_end.clone());
}
first_row = false
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
};
}
let total_horizontal_spacing = self.total_horizontal_spacing();
let mut style_specified_intrinsic_inline_size =
self.block_flow
.fragment
.style_specified_intrinsic_inline_size()
.finish();
style_specified_intrinsic_inline_size.minimum_inline_size -= total_horizontal_spacing;
style_specified_intrinsic_inline_size.preferred_inline_size -= total_horizontal_spacing;
computation.union_block(&style_specified_intrinsic_inline_size);
computation.surrounding_size += total_horizontal_spacing;
self.block_flow.base.intrinsic_inline_sizes = computation.finish()
}
/// Recursively (top-down) determines the actual inline-size of child contexts and fragments.
/// When called on this context, the context has had its inline-size set by the parent context.
servo: Merge #15417 - layout: Remove cached thread local context from LayoutContext, and use LayoutContext for assign_inline_sizes() (from stshine:column-flexbox); r=emilio <!-- Please describe your changes on the following line: --> According to https://github.com/servo/servo/pull/3069 the cached thread local context is introduced for green threads. Now green threads has gone, and the existence of cache force us to create a `LayoutContext`, an `AssignISizes` and an `AssignBSizes` for each flow during parallel layout, so the pull request tries to remove it. And it also switch `assign_inline_sizes()` to accept a `LayoutContext` parameter, as according to my current design we need to do full layout to some flex items for column flexbox during assign isize traversal. Part of #14123. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [X] These changes do not require tests because refactoring <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: e2b494b1d08b8929ca7e5ae369304f41af81ace2 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 9cd92ed44acfaa3b2ba04e345b33d21aff3d752c
2017-02-08 04:16:05 +03:00
fn assign_inline_sizes(&mut self, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("table::assign_inline_sizes {:x}",
self.block_flow.base.debug_id());
debug!("assign_inline_sizes({}): assigning inline_size for flow", "table");
servo: Merge #15417 - layout: Remove cached thread local context from LayoutContext, and use LayoutContext for assign_inline_sizes() (from stshine:column-flexbox); r=emilio <!-- Please describe your changes on the following line: --> According to https://github.com/servo/servo/pull/3069 the cached thread local context is introduced for green threads. Now green threads has gone, and the existence of cache force us to create a `LayoutContext`, an `AssignISizes` and an `AssignBSizes` for each flow during parallel layout, so the pull request tries to remove it. And it also switch `assign_inline_sizes()` to accept a `LayoutContext` parameter, as according to my current design we need to do full layout to some flex items for column flexbox during assign isize traversal. Part of #14123. --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [X] These changes do not require tests because refactoring <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: e2b494b1d08b8929ca7e5ae369304f41af81ace2 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : 9cd92ed44acfaa3b2ba04e345b33d21aff3d752c
2017-02-08 04:16:05 +03:00
let shared_context = layout_context.shared_context();
// The position was set to the containing block by the flow's parent.
// FIXME: The code for distributing column widths should really be placed under table_wrapper.rs.
let containing_block_inline_size = self.block_flow.base.block_container_inline_size;
let mut constrained_column_inline_sizes_indices = vec![];
let mut unspecified_inline_sizes_indices = vec![];
for (idx, column_inline_size) in self.column_intrinsic_inline_sizes.iter().enumerate() {
if column_inline_size.constrained {
constrained_column_inline_sizes_indices.push(idx);
} else if column_inline_size.percentage == 0.0 {
unspecified_inline_sizes_indices.push(idx);
}
}
let inline_size_computer = InternalTable;
inline_size_computer.compute_used_inline_size(&mut self.block_flow,
shared_context,
containing_block_inline_size);
let inline_start_content_edge = self.block_flow.fragment.border_padding.inline_start;
let inline_end_content_edge = self.block_flow.fragment.border_padding.inline_end;
let padding_and_borders = self.block_flow.fragment.border_padding.inline_start_end();
let spacing_per_cell = self.spacing();
let total_horizontal_spacing = self.total_horizontal_spacing();
let content_inline_size = self.block_flow.fragment.border_box.size.inline -
padding_and_borders - total_horizontal_spacing;
let mut remaining_inline_size = content_inline_size;
match self.table_layout {
TableLayout::Fixed => {
self.column_computed_inline_sizes.clear();
// https://drafts.csswg.org/css2/tables.html#fixed-table-layout
for column_inline_size in &self.column_intrinsic_inline_sizes {
if column_inline_size.constrained {
self.column_computed_inline_sizes.push(ColumnComputedInlineSize {
size: column_inline_size.minimum_length,
});
remaining_inline_size -= column_inline_size.minimum_length;
} else if column_inline_size.percentage != 0.0 {
let size = remaining_inline_size.scale_by(column_inline_size.percentage);
self.column_computed_inline_sizes.push(ColumnComputedInlineSize {
size: size,
});
remaining_inline_size -= size;
} else {
// Set the size to 0 now, distribute the remaining widths later
self.column_computed_inline_sizes.push(ColumnComputedInlineSize {
size: Au(0),
});
}
}
// Distribute remaining content inline size
if unspecified_inline_sizes_indices.len() > 0 {
for &index in &unspecified_inline_sizes_indices {
self.column_computed_inline_sizes[index].size =
remaining_inline_size.scale_by(1.0 / unspecified_inline_sizes_indices.len() as f32);
}
} else {
let total_minimum_size = self.column_intrinsic_inline_sizes
.iter()
.filter(|size| size.constrained)
.map(|size| size.minimum_length.0 as f32)
.sum::<f32>();
for &index in &constrained_column_inline_sizes_indices {
self.column_computed_inline_sizes[index].size +=
remaining_inline_size.scale_by(
self.column_computed_inline_sizes[index].size.0 as f32 / total_minimum_size);
}
}
}
_ => {
// The table wrapper already computed the inline-sizes and propagated them down
// to us.
}
}
let column_computed_inline_sizes = &self.column_computed_inline_sizes;
let collapsed_inline_direction_border_widths_for_table =
&self.collapsed_inline_direction_border_widths_for_table;
let mut collapsed_block_direction_border_widths_for_table =
self.collapsed_block_direction_border_widths_for_table.iter().peekable();
let mut incoming_rowspan = vec![];
self.block_flow.propagate_assigned_inline_size_to_children(shared_context,
inline_start_content_edge,
inline_end_content_edge,
content_inline_size,
|child_flow,
_child_index,
_content_inline_size,
writing_mode,
_inline_start_margin_edge,
_inline_end_margin_edge| {
table_row::propagate_column_inline_sizes_to_child(
child_flow,
writing_mode,
column_computed_inline_sizes,
&spacing_per_cell,
&mut incoming_rowspan);
if child_flow.is_table_row() {
let child_table_row = child_flow.as_mut_table_row();
child_table_row.populate_collapsed_border_spacing(
collapsed_inline_direction_border_widths_for_table,
&mut collapsed_block_direction_border_widths_for_table);
} else if child_flow.is_table_rowgroup() {
let child_table_rowgroup = child_flow.as_mut_table_rowgroup();
child_table_rowgroup.populate_collapsed_border_spacing(
collapsed_inline_direction_border_widths_for_table,
&mut collapsed_block_direction_border_widths_for_table);
}
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
});
}
fn assign_block_size(&mut self, lc: &LayoutContext) {
debug!("assign_block_size: assigning block_size for table");
let vertical_spacing = self.spacing().vertical();
self.block_flow.assign_block_size_for_table_like_flow(vertical_spacing, lc)
servo: Merge #2235 - Rewrite display list building and opportunistically lay out blocks in parallel even if floats are present (from pcwalton:parallel-floats); r=larsbergstrom layout: Rewrite display list building to be parallel and to handle overflow correctly, and opportunistically lay out blocks in parallel even if floats are present. This series of commits fixes the `inline-height-test` in Acid2 by implementing proper overflow as well as the inline "strut". (See CSS 2.1 § 10.8.1.) It was accidentally working before because tables' descendant flows were not being laid out properly. Display list building is now parallel and is done by bubbling up display items and layers from parent to child. Speedups of around 60%-70% are observed on Wikipedia with a 4-core HyperThreaded Core i7. More optimizations are possible; this is just a start. To minimize the amount of data that needs to be bubbled up, as well as to make proper handling of `overflow: hidden` clipping easier, the `StackingContext` abstraction is now purely internal to the display list. Instead of placing items into a stacking context directly, display items are placed into display lists with an explicit `StackingLevel` provided. The stacking level determines a display item's position within the stacking context. When a stacking context is finished, it is flattened with the the `flatten` method, which shuffles the display items that make up the context into their proper order while handling clipping properly. r? @SimonSapin and/or @larsbergstrom Source-Repo: https://github.com/servo/servo Source-Revision: 1a88996c0438212a4e77369515a243c9c24c4518
2014-05-03 03:10:20 +04:00
}
fn compute_stacking_relative_position(&mut self, layout_context: &LayoutContext) {
self.block_flow.compute_stacking_relative_position(layout_context)
}
fn generated_containing_block_size(&self, flow: OpaqueFlow) -> LogicalSize<Au> {
self.block_flow.generated_containing_block_size(flow)
}
servo: Merge #3546 - layout: Implement the correct hypothetical box behavior for absolutely-positioned elements declared with `display: inline` (from pcwalton:absolute-inline); r=glennw Although the computed `display` property of elements with `position: absolute` is `block`, `position: absolute; display: inline` can still behave differently from `position: absolute; display: block`. This is because the hypothetical box for `position: absolute` can be at the position it would have been if it had `display: inline`. CSS 2.1 § 10.3.7 describes this case in a parenthetical: "The static-position containing block is the containing block of a hypothetical box that would have been the first box of the element if its specified 'position' value had been 'static' and its specified 'float' had been 'none'. (Note that due to the rules in section 9.7 this hypothetical calculation might require also assuming a different computed value for 'display'.)" To handle this, I had to change both style computation and layout. For the former, I added an internal property `-servo-display-for-hypothetical-box`, which stores the `display` value supplied by the author, before the computed value is calculated. Flow construction now uses this value. As for layout, implementing the proper behavior is tricky because the position of an inline fragment in the inline direction cannot be determined until height assignment, which is a parallelism hazard because in parallel layout widths are computed before heights. However, in this particular case we can avoid the parallelism hazard because the inline direction of a hypothetical box only affects the layout if an absolutely-positioned element is unconstrained in the inline direction. Therefore, we can just lay out such absolutely-positioned elements with a bogus inline position and fix it up once the true inline position of the hypothetical box is computed. The name for this fix-up process is "late computation of inline position" (and the corresponding fix-up for the block position is called "late computation of block position"). This improves the header on /r/rust. r? @glennw Source-Repo: https://github.com/servo/servo Source-Revision: f7d2fb6ff86afff7a5b674f751af9370a5a6b142
2014-10-02 05:36:25 +04:00
fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) {
self.block_flow.update_late_computed_inline_position_if_necessary(inline_position)
}
fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) {
self.block_flow.update_late_computed_block_position_if_necessary(block_position)
}
fn build_display_list(&mut self, state: &mut DisplayListBuildState) {
let border_painting_mode = match self.block_flow
.fragment
.style
.get_inheritedtable()
.border_collapse {
border_collapse::T::Separate => BorderPaintingMode::Separate,
border_collapse::T::Collapse => BorderPaintingMode::Hidden,
};
self.block_flow.build_display_list_for_block(state, border_painting_mode);
let iter = TableCellStyleIterator::new(&self);
for mut style in iter {
style.build_display_list(state)
}
}
fn collect_stacking_contexts(&mut self, state: &mut StackingContextCollectionState) {
servo: Merge #18921 - Fix duplicate stacking context creation for anonymous Flows (from mrobinson:incremental-stacking-context-ids); r=emilio Anonymous nodes were previously creating duplicate stacking contexts, one for each node in the anonymous node chain. This change eliminates that for tables. Additionally the use of stacking context ids based on node addresses is no longer necessary since stacking contexts no longer control scrolling. This is the first step in eliminating the dependency between node addresses and ClipScrollNodes which causes issues like #16425. <!-- Please describe your changes on the following line: --> --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [ ] These changes fix #__ (github issue number if applicable). <!-- Either: --> - [ ] There are tests for these changes OR - [x] These changes do not require tests because they are covered by existing tests. <!-- Also, please make sure that "Allow edits from maintainers" checkbox is checked, so that we can help you if you get stuck somewhere along the way.--> <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Source-Repo: https://github.com/servo/servo Source-Revision: 865b9aea357d4b51696d4a0343521a0f2a20bcf1 --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : f732d8c55cc80ae35de430a34fb51e9856e34839
2017-10-19 05:34:23 +03:00
// Stacking contexts are collected by the table wrapper.
self.block_flow.collect_stacking_contexts_for_block(state,
StackingContextCollectionFlags::NEVER_CREATES_STACKING_CONTEXT);
}
fn repair_style(&mut self, new_style: &::ServoArc<ComputedValues>) {
self.block_flow.repair_style(new_style)
}
fn compute_overflow(&self) -> Overflow {
self.block_flow.compute_overflow()
}
fn iterate_through_fragment_border_boxes(&self,
iterator: &mut FragmentBorderBoxIterator,
level: i32,
stacking_context_position: &Point2D<Au>) {
self.block_flow.iterate_through_fragment_border_boxes(iterator, level, stacking_context_position)
}
fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) {
self.block_flow.mutate_fragments(mutator)
}
fn print_extra_flow_children(&self, print_tree: &mut PrintTree) {
self.block_flow.print_extra_flow_children(print_tree);
}
}
#[derive(Debug)]
struct ColumnStyle<'table> {
span: u32,
colgroup_style: Option<&'table ComputedValues>,
col_style: Option<&'table ComputedValues>,
}
impl fmt::Debug for TableFlow {
/// Outputs a debugging string describing this table flow.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TableFlow: {:?}", self.block_flow)
}
}
/// Table, TableRowGroup, TableRow, TableCell types.
/// Their inline-sizes are calculated in the same way and do not have margins.
pub struct InternalTable;
impl ISizeAndMarginsComputer for InternalTable {
/// Compute the used value of inline-size, taking care of min-inline-size and max-inline-size.
///
/// CSS Section 10.4: Minimum and Maximum inline-sizes
fn compute_used_inline_size(
&self,
block: &mut BlockFlow,
shared_context: &SharedStyleContext,
parent_flow_inline_size: Au
) {
let mut input = self.compute_inline_size_constraint_inputs(block,
parent_flow_inline_size,
shared_context);
// Tables are always at least as wide as their minimum inline size.
let minimum_inline_size =
block.base.intrinsic_inline_sizes.minimum_inline_size -
block.fragment.border_padding.inline_start_end();
input.available_inline_size = cmp::max(input.available_inline_size, minimum_inline_size);
let solution = self.solve_inline_size_constraints(block, &input);
self.set_inline_size_constraint_solutions(block, solution);
}
/// Solve the inline-size and margins constraints for this block flow.
fn solve_inline_size_constraints(&self, _: &mut BlockFlow, input: &ISizeConstraintInput)
-> ISizeConstraintSolution {
ISizeConstraintSolution::new(input.available_inline_size, Au(0), Au(0))
}
}
/// Information about the intrinsic inline sizes of columns within a table.
///
/// During table inline-size bubbling, we might need to store both a percentage constraint and a
/// specific width constraint. For instance, one cell might say that it wants to be 100 pixels wide
/// in the inline direction and another cell might say that it wants to take up 20% of the inline-
/// size of the table. Now because we bubble up these constraints during the bubble-inline-sizes
/// phase of layout, we don't know yet how wide the table is ultimately going to be in the inline
/// direction. As we need to pick the maximum width of all cells for a column (in this case, the
/// maximum of 100 pixels and 20% of the table), the preceding constraint means that we must
/// potentially store both a specified width *and* a specified percentage, so that the inline-size
/// assignment phase of layout will know which one to pick.
#[derive(Clone, Copy, Debug, Serialize)]
pub struct ColumnIntrinsicInlineSize {
/// The preferred intrinsic inline size.
pub preferred: Au,
/// The largest specified size of this column as a length.
pub minimum_length: Au,
/// The largest specified size of this column as a percentage (`width` property).
pub percentage: CSSFloat,
/// Whether the column inline size is *constrained* per INTRINSIC § 4.1.
pub constrained: bool,
}
impl ColumnIntrinsicInlineSize {
/// Returns a newly-initialized `ColumnIntrinsicInlineSize` with all fields blank.
pub fn new() -> ColumnIntrinsicInlineSize {
ColumnIntrinsicInlineSize {
preferred: Au(0),
minimum_length: Au(0),
percentage: 0.0,
constrained: false,
}
}
/// Returns the higher of the two percentages specified in `self` and `other`.
pub fn greatest_percentage(&self, other: &ColumnIntrinsicInlineSize) -> CSSFloat {
if self.percentage > other.percentage {
self.percentage
} else {
other.percentage
}
}
}
/// The actual inline size for each column.
///
/// TODO(pcwalton): There will probably be some `border-collapse`-related info in here too
/// eventually.
#[derive(Clone, Copy, Debug, Serialize)]
pub struct ColumnComputedInlineSize {
/// The computed size of this inline column.
pub size: Au,
}
pub trait VecExt<T> {
fn push_or_set(&mut self, index: usize, value: T) -> &mut T;
fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T;
}
impl<T> VecExt<T> for Vec<T> {
fn push_or_set(&mut self, index: usize, value: T) -> &mut T {
if index < self.len() {
self[index] = value
} else {
debug_assert_eq!(index, self.len());
self.push(value)
}
&mut self[index]
}
fn get_mut_or_push(&mut self, index: usize, zero: T) -> &mut T {
if index >= self.len() {
debug_assert_eq!(index, self.len());
self.push(zero)
}
&mut self[index]
}
}
/// Updates the border styles in the block direction for a single row. This function should
/// only be called if border collapsing is on. It is factored out into a separate function
/// because we process children of rowgroups too.
fn perform_border_collapse_for_row(child_table_row: &mut TableRowFlow,
table_inline_borders: &TableInlineCollapsedBorders,
previous_block_borders: PreviousBlockCollapsedBorders,
next_block_borders: NextBlockCollapsedBorders,
inline_spacing: &mut Vec<Au>,
block_spacing: &mut Vec<Au>) {
// TODO mbrubeck: Take rowspan and colspan into account.
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
let number_of_borders_inline_direction = child_table_row.preliminary_collapsed_borders.inline.len();
// Compute interior inline borders.
for (i, this_inline_border) in child_table_row.preliminary_collapsed_borders
.inline
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
.iter_mut()
.enumerate() {
child_table_row.final_collapsed_borders.inline.push_or_set(i, *this_inline_border);
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
if i == 0 {
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.start);
} else if i + 1 == number_of_borders_inline_direction {
child_table_row.final_collapsed_borders.inline[i].combine(&table_inline_borders.end);
}
let inline_spacing = inline_spacing.get_mut_or_push(i, Au(0));
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
*inline_spacing = cmp::max(*inline_spacing, child_table_row.final_collapsed_borders.inline[i].width)
}
// Compute block-start borders.
let block_start_borders = &mut child_table_row.final_collapsed_borders.block_start;
*block_start_borders = child_table_row.preliminary_collapsed_borders.block_start.clone();
for (i, this_border) in block_start_borders.iter_mut().enumerate() {
match previous_block_borders {
PreviousBlockCollapsedBorders::FromPreviousRow(ref previous_block_borders) => {
if previous_block_borders.len() > i {
this_border.combine(&previous_block_borders[i]);
}
}
PreviousBlockCollapsedBorders::FromTable(table_border) => {
this_border.combine(&table_border);
}
}
}
// Compute block-end borders.
let next_block = &mut child_table_row.final_collapsed_borders.block_end;
block_spacing.push(Au(0));
let block_spacing = block_spacing.last_mut().unwrap();
for (i, this_block_border) in child_table_row.preliminary_collapsed_borders
.block_end
.iter()
.enumerate() {
let next_block = next_block.push_or_set(i, *this_block_border);
match next_block_borders {
NextBlockCollapsedBorders::FromNextRow(next_block_borders) => {
if next_block_borders.len() > i {
next_block.combine(&next_block_borders[i])
}
}
NextBlockCollapsedBorders::FromTable(ref next_block_borders) => {
next_block.combine(next_block_borders);
}
}
*block_spacing = cmp::max(*block_spacing, next_block.width)
}
}
/// Encapsulates functionality shared among all table-like flows: for now, tables and table
/// rowgroups.
pub trait TableLikeFlow {
/// Lays out the rows of a table.
fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au,
layout_context: &LayoutContext);
}
impl TableLikeFlow for BlockFlow {
fn assign_block_size_for_table_like_flow(&mut self, block_direction_spacing: Au,
layout_context: &LayoutContext) {
debug_assert!(self.fragment.style.get_inheritedtable().border_collapse ==
border_collapse::T::Separate || block_direction_spacing == Au(0));
fn border_spacing_for_row(fragment: &Fragment, row: &TableRowFlow,
block_direction_spacing: Au) -> Au {
match fragment.style.get_inheritedtable().border_collapse {
border_collapse::T::Separate => block_direction_spacing,
border_collapse::T::Collapse => {
row.collapsed_border_spacing.block_start
}
}
}
if self.base.restyle_damage.contains(ServoRestyleDamage::REFLOW) {
let mut sizes = vec![Default::default()];
// The amount of border spacing up to and including this row,
// but not including the spacing beneath it
let mut cumulative_border_spacing = Au(0);
let mut incoming_rowspan_data = vec![];
let mut rowgroup_id = 0;
let mut first = true;
// First pass: Compute block-direction border spacings
// XXXManishearth this can be done in tandem with the second pass,
// provided we never hit any rowspan cases
for kid in self.base.child_iter_mut() {
if kid.is_table_row() {
// skip the first row, it is accounted for
if first {
first = false;
continue;
}
cumulative_border_spacing +=
border_spacing_for_row(&self.fragment, kid.as_table_row(),
block_direction_spacing);
sizes.push(TableRowSizeData {
// we haven't calculated sizes yet
size: Au(0),
cumulative_border_spacing,
rowgroup_id
});
} else if kid.is_table_rowgroup() && !first {
rowgroup_id += 1;
}
}
// Second pass: Compute row block sizes
// [expensive: iterates over cells]
let mut i = 0;
for kid in self.base.child_iter_mut() {
if kid.is_table_row() {
let size = kid.as_mut_table_row()
.compute_block_size_table_row_base(layout_context,
&mut incoming_rowspan_data,
&sizes,
i);
sizes[i].size = size;
i += 1;
}
}
// Our current border-box position.
let block_start_border_padding = self.fragment.border_padding.block_start;
let mut current_block_offset = block_start_border_padding;
let mut has_rows = false;
// Third pass: Assign block sizes and positions to rows, cells, and other children
// [expensive: iterates over cells]
// At this point, `current_block_offset` is at the content edge of our box. Now iterate
// over children.
let mut i = 0;
for kid in self.base.child_iter_mut() {
if kid.is_table_row() {
has_rows = true;
let row = kid.as_mut_table_row();
row.assign_block_size_to_self_and_children(&sizes, i);
row.mut_base().restyle_damage
.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW |
ServoRestyleDamage::REFLOW);
current_block_offset = current_block_offset +
border_spacing_for_row(&self.fragment, row,
block_direction_spacing);
i += 1;
}
// At this point, `current_block_offset` is at the border edge of the child.
kid.mut_base().position.start.b = current_block_offset;
// Move past the child's border box. Do not use the `translate_including_floats`
// function here because the child has already translated floats past its border
// box.
let kid_base = kid.mut_base();
current_block_offset = current_block_offset + kid_base.position.size.block;
}
// Compute any explicitly-specified block size.
// Can't use `for` because we assign to
// `candidate_block_size_iterator.candidate_value`.
let mut block_size = current_block_offset - block_start_border_padding;
let mut candidate_block_size_iterator = CandidateBSizeIterator::new(
&self.fragment,
self.base.block_container_explicit_block_size);
while let Some(candidate_block_size) = candidate_block_size_iterator.next() {
candidate_block_size_iterator.candidate_value =
match candidate_block_size {
MaybeAuto::Auto => block_size,
MaybeAuto::Specified(value) => value
};
}
// Adjust `current_block_offset` as necessary to account for the explicitly-specified
// block-size.
block_size = candidate_block_size_iterator.candidate_value;
let delta = block_size - (current_block_offset - block_start_border_padding);
current_block_offset = current_block_offset + delta;
// Take border, padding, and spacing into account.
let block_end_offset = self.fragment.border_padding.block_end +
if has_rows { block_direction_spacing } else { Au(0) };
current_block_offset = current_block_offset + block_end_offset;
// Now that `current_block_offset` is at the block-end of the border box, compute the
// final border box position.
self.fragment.border_box.size.block = current_block_offset;
self.fragment.border_box.start.b = Au(0);
self.base.position.size.block = current_block_offset;
// Fourth pass: Assign absolute position info
// Write in the size of the relative containing block for children. (This information
// is also needed to handle RTL.)
for kid in self.base.child_iter_mut() {
kid.mut_base().early_absolute_position_info = EarlyAbsolutePositionInfo {
relative_containing_block_size: self.fragment.content_box().size,
relative_containing_block_mode: self.fragment.style().writing_mode,
};
}
}
self.base.restyle_damage.remove(ServoRestyleDamage::REFLOW_OUT_OF_FLOW | ServoRestyleDamage::REFLOW);
}
}
/// Inline collapsed borders for the table itself.
servo: Merge #12437 - Handle row borders in border collapsing logic (from gpoesia:tr_margin_fix); r=pcwalton <!-- Please describe your changes on the following line: --> Handle table row border when collapsing borders for a table row. The row border is combined with the cell's border using the already implemented conflict resolution logic. This is a screenshot of the following test: ```html <!doctype html> <html><body> <style> table { border-collapse: collapse; } tr { border: 1px solid black; } </style> <table> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> <tr><td>Lorem</td><td>Ipsum</td><td>Sit</td><td>Dolor</td></tr> </table> </body> </html> ``` <img src="https://dl.dropboxusercontent.com/u/10962672/Screenshots%20Servo/servo_tr_border_collapse.png"/> The top border is missing, but I think that's a different bug, since it also does not show up when the border is in the cells, and not the rows. Also, when debugging the border collapsing structures, they seem ok (the top border seems to be there). I can look at that bug in a separate issue (or in this one too if you prefer). --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: --> - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #11527 (github issue number if applicable). <!-- Either: --> - [X] These changes do not require tests because I didn't find how to automatically test it (will be happy to provide a test if there's infrastructure for this kind of test already in place). <!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. --> Fixes #11527. Source-Repo: https://github.com/servo/servo Source-Revision: f5a546a1600fe6a6761e3fd6ac1bb6c3f1a503eb
2016-08-31 19:28:18 +03:00
#[derive(Debug)]
struct TableInlineCollapsedBorders {
/// The table border at the start of the inline direction.
start: CollapsedBorder,
/// The table border at the end of the inline direction.
end: CollapsedBorder,
}
enum PreviousBlockCollapsedBorders {
FromPreviousRow(Vec<CollapsedBorder>),
FromTable(CollapsedBorder),
}
enum NextBlockCollapsedBorders<'a> {
FromNextRow(&'a [CollapsedBorder]),
FromTable(CollapsedBorder),
}
/// Iterator over all the rows of a table, which also
/// provides the Fragment for rowgroups if any
struct TableRowAndGroupIterator<'a> {
kids: FlowListIterator<'a>,
group: Option<(&'a Fragment, FlowListIterator<'a>)>
}
impl<'a> TableRowAndGroupIterator<'a> {
fn new(base: &'a BaseFlow) -> Self {
TableRowAndGroupIterator {
kids: base.child_iter(),
group: None,
}
}
}
impl<'a> Iterator for TableRowAndGroupIterator<'a> {
type Item = (Option<&'a Fragment>, &'a TableRowFlow);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// If we're inside a rowgroup, iterate through the rowgroup's children.
if let Some(ref mut group) = self.group {
if let Some(grandkid) = group.1.next() {
return Some((Some(group.0), grandkid.as_table_row()))
}
}
// Otherwise, iterate through the table's children.
self.group = None;
match self.kids.next() {
Some(kid) => {
if kid.is_table_rowgroup() {
let mut rowgroup = kid.as_table_rowgroup();
let iter = rowgroup.block_flow.base.child_iter();
self.group = Some((&rowgroup.block_flow.fragment, iter));
self.next()
} else if kid.is_table_row() {
Some((None, kid.as_table_row()))
} else {
self.next() // Skip children that are not rows or rowgroups
}
}
None => None
}
}
}
/// Iterator over all the rows of a table, which also
/// provides the Fragment for rowgroups if any
struct MutTableRowAndGroupIterator<'a> {
kids: MutFlowListIterator<'a>,
group: Option<(&'a Fragment, MutFlowListIterator<'a>)>
}
impl<'a> MutTableRowAndGroupIterator<'a> {
fn new(base: &'a mut BaseFlow) -> Self {
MutTableRowAndGroupIterator {
kids: base.child_iter_mut(),
group: None,
}
}
}
impl<'a> Iterator for MutTableRowAndGroupIterator<'a> {
type Item = (Option<&'a Fragment>, &'a mut TableRowFlow);
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// If we're inside a rowgroup, iterate through the rowgroup's children.
if let Some(ref mut group) = self.group {
if let Some(grandkid) = group.1.next() {
return Some((Some(group.0), grandkid.as_mut_table_row()))
}
}
// Otherwise, iterate through the table's children.
self.group = None;
match self.kids.next() {
Some(kid) => {
if kid.is_table_rowgroup() {
let mut rowgroup = kid.as_mut_table_rowgroup();
let iter = rowgroup.block_flow.base.child_iter_mut();
self.group = Some((&rowgroup.block_flow.fragment, iter));
self.next()
} else if kid.is_table_row() {
Some((None, kid.as_mut_table_row()))
} else {
self.next() // Skip children that are not rows or rowgroups
}
}
None => None
}
}
}
/// Iterator over all the rows of a table
struct TableRowIterator<'a>(MutTableRowAndGroupIterator<'a>);
impl<'a> TableRowIterator<'a> {
fn new(base: &'a mut BaseFlow) -> Self {
TableRowIterator(MutTableRowAndGroupIterator::new(base))
}
}
impl<'a> Iterator for TableRowIterator<'a> {
type Item = &'a mut TableRowFlow;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|n| n.1)
}
}
/// An iterator over table cells, yielding all relevant style objects
/// for each cell
///
/// Used for correctly handling table layers from
/// https://drafts.csswg.org/css2/tables.html#table-layers
struct TableCellStyleIterator<'table> {
column_styles: Vec<ColumnStyle<'table>>,
row_iterator: TableRowAndGroupIterator<'table>,
row_info: Option<TableCellStyleIteratorRowInfo<'table>>,
column_index: TableCellColumnIndexData,
}
struct TableCellStyleIteratorRowInfo<'table> {
row: &'table TableRowFlow,
rowgroup: Option<&'table Fragment>,
cell_iterator: FlowListIterator<'table>,
}
impl<'table> TableCellStyleIterator<'table> {
fn new(table: &'table TableFlow) -> Self {
let column_styles = table.column_styles();
let mut row_iterator = TableRowAndGroupIterator::new(&table.block_flow.base);
let row_info = if let Some((group, row)) = row_iterator.next() {
Some(TableCellStyleIteratorRowInfo {
row: &row,
rowgroup: group,
cell_iterator: row.block_flow.base.child_iter()
})
} else {
None
};
TableCellStyleIterator {
column_styles, row_iterator, row_info,
column_index: Default::default(),
}
}
}
struct TableCellStyleInfo<'table> {
cell: &'table TableCellFlow,
colgroup_style: Option<&'table ComputedValues>,
col_style: Option<&'table ComputedValues>,
rowgroup_style: Option<&'table ComputedValues>,
row_style: &'table ComputedValues,
}
struct TableCellColumnIndexData {
/// Which column this is in the table
pub absolute: u32,
/// The index of the current column in column_styles
/// (i.e. which <col> element it is)
pub relative: u32,
/// In case of multispan <col>s, where we are in the
/// span of the current <col> element
pub relative_offset: u32,
}
impl Default for TableCellColumnIndexData {
fn default() -> Self {
TableCellColumnIndexData {
absolute: 0,
relative: 0,
relative_offset: 0,
}
}
}
impl TableCellColumnIndexData {
/// Moves forward by `amount` columns, updating the various indices used
///
/// This totally ignores rowspan -- if colspan and rowspan clash,
/// they just overlap, so we ignore it.
fn advance(&mut self, amount: u32, column_styles: &[ColumnStyle]) {
self.absolute += amount;
self.relative_offset += amount;
if let Some(mut current_col) =
column_styles.get(self.relative as usize) {
while self.relative_offset >= current_col.span {
// move to the next column
self.relative += 1;
self.relative_offset -= current_col.span;
if let Some(column_style) =
column_styles.get(self.relative as usize) {
current_col = column_style;
} else {
// we ran out of column_styles,
// so we don't need to update the indices
break;
}
}
}
}
}
impl<'table> Iterator for TableCellStyleIterator<'table> {
type Item = TableCellStyleInfo<'table>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// FIXME We do this awkward .take() followed by shoving it back in
// because without NLL the row_info borrow lasts too long
if let Some(mut row_info) = self.row_info.take() {
if let Some(rowspan) = row_info.row.incoming_rowspan.get(self.column_index.absolute as usize) {
// we are not allowed to use this column as a starting point. Try the next one.
if *rowspan > 1 {
self.column_index.advance(1, &self.column_styles);
// put row_info back in
self.row_info = Some(row_info);
// try again
return self.next();
}
}
if let Some(cell) = row_info.cell_iterator.next() {
let rowgroup_style = row_info.rowgroup.map(|r| r.style());
let row_style = row_info.row.block_flow.fragment.style();
let cell = cell.as_table_cell();
let (col_style, colgroup_style) = if let Some(column_style) =
self.column_styles.get(self.column_index.relative as usize) {
let styles = (column_style.col_style.clone(), column_style.colgroup_style.clone());
self.column_index.advance(cell.column_span, &self.column_styles);
styles
} else {
(None, None)
};
// put row_info back in
self.row_info = Some(row_info);
return Some(TableCellStyleInfo {
cell,
colgroup_style,
col_style,
rowgroup_style,
row_style,
})
} else {
// next row
if let Some((group, row)) = self.row_iterator.next() {
self.row_info = Some(TableCellStyleIteratorRowInfo {
row: &row,
rowgroup: group,
cell_iterator: row.block_flow.base.child_iter()
});
self.column_index = Default::default();
self.next()
} else {
// out of rows
// row_info stays None
None
}
}
} else {
// empty table
None
}
}
}
impl<'table> TableCellStyleInfo<'table> {
fn build_display_list(&self, mut state: &mut DisplayListBuildState) {
use style::computed_values::visibility::T as Visibility;
if !self.cell.visible || self.cell.block_flow.fragment.style()
.get_inheritedbox().visibility != Visibility::Visible {
return
}
let border_painting_mode = match self.cell.block_flow
.fragment
.style
.get_inheritedtable()
.border_collapse {
border_collapse::T::Separate => BorderPaintingMode::Separate,
border_collapse::T::Collapse => BorderPaintingMode::Collapse(&self.cell.collapsed_borders),
};
{
let cell_flow = &self.cell.block_flow;
let initial = ComputedValues::initial_values();
let build_dl = |sty: &ComputedValues, state: &mut &mut DisplayListBuildState| {
let background = sty.get_background();
// Don't redraw backgrounds that we've already drawn
if background as *const Background == initial.get_background() as *const _ {
return;
}
let background_color = sty.resolve_color(background.background_color);
cell_flow.build_display_list_for_background_if_applicable_with_background(
state, background, background_color
);
};
if let Some(ref sty) = self.colgroup_style {
build_dl(&sty, &mut state);
}
if let Some(ref sty) = self.col_style {
build_dl(&sty, &mut state);
}
if let Some(ref sty) = self.rowgroup_style {
build_dl(sty, &mut state);
}
build_dl(self.row_style, &mut state);
}
// the restyle damage will be set in TableCellFlow::build_display_list()
self.cell.block_flow.build_display_list_for_block_no_damage(state, border_painting_mode)
}
}