mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
layout: Add support for table rows, columns, rowgroups and colgroups (#31341)
This adds support for table rows, columns, rowgroups and colgroups. There are few additions here: 1. The createion of fragments, which allows script queries and hit testing to work properly. These fragments are empty as all cells are still direct descendants of the table fragment. 2. Properly handling size information from tracks and track groups as well as frustrating rules about reordering rowgroups. 3. Painting a background seemlessly across track groups and groups. This is a thing that isn't done in legacy layout (nor WebKit)! Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
parent
74c07db56c
commit
02ae1f448e
57 changed files with 4274 additions and 21000 deletions
|
@ -4,6 +4,7 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::iter::repeat;
|
||||
|
||||
use log::warn;
|
||||
use script_layout_interface::wrapper_traits::ThreadSafeLayoutNode;
|
||||
|
@ -13,7 +14,10 @@ use style::selector_parser::PseudoElement;
|
|||
use style::str::char_is_whitespace;
|
||||
use style::values::specified::TextDecorationLine;
|
||||
|
||||
use super::{Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset};
|
||||
use super::{
|
||||
Table, TableSlot, TableSlotCell, TableSlotCoordinates, TableSlotOffset, TableTrack,
|
||||
TableTrackGroup, TableTrackGroupType,
|
||||
};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::dom::{BoxSlot, NodeExt};
|
||||
use crate::dom_traversal::{Contents, NodeAndStyleInfo, NonReplacedContents, TraversalHandler};
|
||||
|
@ -224,30 +228,216 @@ impl TableBuilder {
|
|||
Self::new(ComputedValues::initial_values().to_arc())
|
||||
}
|
||||
|
||||
pub fn last_row_index_in_row_group_at_row_n(&self, n: usize) -> usize {
|
||||
// TODO: This is just a linear search, because the idea is that there are
|
||||
// generally less than or equal to three row groups, but if we notice a lot
|
||||
// of web content with more, we can consider a binary search here.
|
||||
for row_group in self.table.row_groups.iter() {
|
||||
if row_group.track_range.start > n {
|
||||
return row_group.track_range.start - 1;
|
||||
}
|
||||
}
|
||||
self.table.size.height - 1
|
||||
}
|
||||
|
||||
pub fn finish(mut self) -> Table {
|
||||
// Make sure that every row has the same number of cells.
|
||||
self.do_missing_cells_fixup();
|
||||
self.remove_extra_columns_and_column_groups();
|
||||
self.reorder_first_thead_and_tfoot();
|
||||
self.do_final_rowspan_calculation();
|
||||
self.table
|
||||
}
|
||||
|
||||
/// Do <https://drafts.csswg.org/css-tables/#missing-cells-fixup> which ensures
|
||||
/// that every row has the same number of cells.
|
||||
fn do_missing_cells_fixup(&mut self) {
|
||||
for row in self.table.slots.iter_mut() {
|
||||
row.resize_with(self.table.size.width, || TableSlot::Empty);
|
||||
}
|
||||
}
|
||||
|
||||
// Turn all rowspan=0 rows into the real value to avoid having to
|
||||
// make the calculation continually during layout. In addition, make
|
||||
// sure that there are no rowspans that extend past the end of the
|
||||
// table.
|
||||
/// It's possible to define more table columns via `<colgroup>` and `<col>` elements
|
||||
/// than actually exist in the table. In that case, remove these bogus columns
|
||||
/// to prevent using them later in layout.
|
||||
fn remove_extra_columns_and_column_groups(&mut self) {
|
||||
let number_of_actual_table_columns = self.table.size.width;
|
||||
self.table.columns.truncate(number_of_actual_table_columns);
|
||||
|
||||
let mut remove_from = None;
|
||||
for (group_index, column_group) in self.table.column_groups.iter_mut().enumerate() {
|
||||
if column_group.track_range.start >= number_of_actual_table_columns {
|
||||
remove_from = Some(group_index);
|
||||
break;
|
||||
}
|
||||
column_group.track_range.end = column_group
|
||||
.track_range
|
||||
.end
|
||||
.min(number_of_actual_table_columns);
|
||||
}
|
||||
|
||||
if let Some(remove_from) = remove_from {
|
||||
self.table.column_groups.truncate(remove_from);
|
||||
}
|
||||
}
|
||||
|
||||
/// Reorder the first `<thead>` and `<tbody>` to be the first and last row groups respectively.
|
||||
/// This requires fixing up all row group indices.
|
||||
/// See <https://drafts.csswg.org/css-tables/#table-header-group> and
|
||||
/// <https://drafts.csswg.org/css-tables/#table-footer-group>.
|
||||
fn reorder_first_thead_and_tfoot(&mut self) {
|
||||
let mut thead_index = None;
|
||||
let mut tfoot_index = None;
|
||||
for (row_group_index, row_group) in self.table.row_groups.iter().enumerate() {
|
||||
if thead_index.is_none() && row_group.group_type == TableTrackGroupType::HeaderGroup {
|
||||
thead_index = Some(row_group_index);
|
||||
}
|
||||
if tfoot_index.is_none() && row_group.group_type == TableTrackGroupType::FooterGroup {
|
||||
tfoot_index = Some(row_group_index);
|
||||
}
|
||||
if thead_index.is_some() && tfoot_index.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(thead_index) = thead_index {
|
||||
self.move_row_group_to_front(thead_index)
|
||||
}
|
||||
|
||||
if let Some(mut tfoot_index) = tfoot_index {
|
||||
// We may have moved a `<thead>` which means the original index we
|
||||
// we found for this this <tfoot>` also needs to be updated!
|
||||
if thead_index.unwrap_or(0) > tfoot_index {
|
||||
tfoot_index += 1;
|
||||
}
|
||||
self.move_row_group_to_end(tfoot_index)
|
||||
}
|
||||
}
|
||||
|
||||
fn regenerate_track_ranges(&mut self) {
|
||||
// Now update all track group ranges.
|
||||
let mut current_row_group_index = None;
|
||||
for (row_index, row) in self.table.rows.iter().enumerate() {
|
||||
if current_row_group_index == row.group_index {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Finish any row group that is currently being processed.
|
||||
if let Some(current_group_index) = current_row_group_index {
|
||||
self.table.row_groups[current_group_index].track_range.end = row_index;
|
||||
}
|
||||
|
||||
// Start processing this new row group and update its starting index.
|
||||
current_row_group_index = row.group_index;
|
||||
if let Some(current_group_index) = current_row_group_index {
|
||||
self.table.row_groups[current_group_index].track_range.start = row_index;
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the last row group.
|
||||
if let Some(current_group_index) = current_row_group_index {
|
||||
self.table.row_groups[current_group_index].track_range.end = self.table.rows.len();
|
||||
}
|
||||
}
|
||||
|
||||
fn move_row_group_to_front(&mut self, index_to_move: usize) {
|
||||
if index_to_move == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the slots associated with this group.
|
||||
let row_range = self.table.row_groups[index_to_move].track_range.clone();
|
||||
let removed_slots: Vec<Vec<TableSlot>> = self
|
||||
.table
|
||||
.slots
|
||||
.splice(row_range.clone(), std::iter::empty())
|
||||
.collect();
|
||||
self.table.slots.splice(0..0, removed_slots);
|
||||
|
||||
// Move the rows associated with this group.
|
||||
let removed_rows: Vec<TableTrack> = self
|
||||
.table
|
||||
.rows
|
||||
.splice(row_range, std::iter::empty())
|
||||
.collect();
|
||||
self.table.rows.splice(0..0, removed_rows);
|
||||
|
||||
// Move the group itself.
|
||||
let removed_row_group = self.table.row_groups.remove(index_to_move);
|
||||
self.table.row_groups.insert(0, removed_row_group);
|
||||
|
||||
for row in self.table.rows.iter_mut() {
|
||||
match row.group_index.as_mut() {
|
||||
Some(group_index) if *group_index < index_to_move => *group_index += 1,
|
||||
Some(group_index) if *group_index == index_to_move => *group_index = 0,
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
// Do this now, rather than after possibly moving a `<tfoot>` row group to the end,
|
||||
// because moving row groups depends on an accurate `track_range` in every group.
|
||||
self.regenerate_track_ranges();
|
||||
}
|
||||
|
||||
fn move_row_group_to_end(&mut self, index_to_move: usize) {
|
||||
let last_row_group_index = self.table.row_groups.len() - 1;
|
||||
if index_to_move == last_row_group_index {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move the slots associated with this group.
|
||||
let row_range = self.table.row_groups[index_to_move].track_range.clone();
|
||||
let removed_slots: Vec<Vec<TableSlot>> = self
|
||||
.table
|
||||
.slots
|
||||
.splice(row_range.clone(), std::iter::empty())
|
||||
.collect();
|
||||
self.table.slots.extend(removed_slots);
|
||||
|
||||
// Move the rows associated with this group.
|
||||
let removed_rows: Vec<TableTrack> = self
|
||||
.table
|
||||
.rows
|
||||
.splice(row_range, std::iter::empty())
|
||||
.collect();
|
||||
self.table.rows.extend(removed_rows);
|
||||
|
||||
// Move the group itself.
|
||||
let removed_row_group = self.table.row_groups.remove(index_to_move);
|
||||
self.table.row_groups.push(removed_row_group);
|
||||
|
||||
for row in self.table.rows.iter_mut() {
|
||||
match row.group_index.as_mut() {
|
||||
Some(group_index) if *group_index > index_to_move => *group_index -= 1,
|
||||
Some(group_index) if *group_index == index_to_move => {
|
||||
*group_index = last_row_group_index
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
|
||||
self.regenerate_track_ranges();
|
||||
}
|
||||
|
||||
/// Turn all rowspan=0 rows into the real value to avoid having to make the calculation
|
||||
/// continually during layout. In addition, make sure that there are no rowspans that extend
|
||||
/// past the end of their row group.
|
||||
fn do_final_rowspan_calculation(&mut self) {
|
||||
for row_index in 0..self.table.size.height {
|
||||
let last_row_index_in_group = self.last_row_index_in_row_group_at_row_n(row_index);
|
||||
for cell in self.table.slots[row_index].iter_mut() {
|
||||
if let TableSlot::Cell(ref mut cell) = cell {
|
||||
let rowspan_to_end_of_table = self.table.size.height - row_index;
|
||||
if cell.rowspan == 1 {
|
||||
continue;
|
||||
}
|
||||
let rowspan_to_end_of_group = last_row_index_in_group - row_index + 1;
|
||||
if cell.rowspan == 0 {
|
||||
cell.rowspan = rowspan_to_end_of_table;
|
||||
cell.rowspan = rowspan_to_end_of_group;
|
||||
} else {
|
||||
cell.rowspan = cell.rowspan.min(rowspan_to_end_of_table);
|
||||
cell.rowspan = cell.rowspan.min(rowspan_to_end_of_group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.table
|
||||
}
|
||||
|
||||
fn current_y(&self) -> usize {
|
||||
|
@ -408,6 +598,9 @@ pub(crate) struct TableBuilderTraversal<'style, 'dom, Node> {
|
|||
builder: TableBuilder,
|
||||
|
||||
current_anonymous_row_content: Vec<AnonymousTableContent<'dom, Node>>,
|
||||
|
||||
/// The index of the current row group, if there is one.
|
||||
current_row_group_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl<'style, 'dom, Node> TableBuilderTraversal<'style, 'dom, Node>
|
||||
|
@ -425,6 +618,7 @@ where
|
|||
propagated_text_decoration_line,
|
||||
builder: TableBuilder::new(info.style.clone()),
|
||||
current_anonymous_row_content: Vec::new(),
|
||||
current_row_group_index: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,17 +693,26 @@ where
|
|||
DisplayLayoutInternal::TableFooterGroup |
|
||||
DisplayLayoutInternal::TableHeaderGroup => {
|
||||
self.finish_anonymous_row_if_needed();
|
||||
|
||||
// TODO: Should we fixup `rowspan=0` to the actual resolved value and
|
||||
// any other rowspans that have been cut short?
|
||||
self.builder.incoming_rowspans.clear();
|
||||
|
||||
let next_row_index = self.builder.table.rows.len();
|
||||
self.builder.table.row_groups.push(TableTrackGroup {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_type: internal.into(),
|
||||
track_range: next_row_index..next_row_index,
|
||||
});
|
||||
|
||||
let new_row_group_index = self.builder.table.row_groups.len() - 1;
|
||||
self.current_row_group_index = Some(new_row_group_index);
|
||||
NonReplacedContents::try_from(contents).unwrap().traverse(
|
||||
self.context,
|
||||
info,
|
||||
self,
|
||||
);
|
||||
|
||||
// TODO: Handle style for row groups here.
|
||||
self.current_row_group_index = None;
|
||||
self.builder.incoming_rowspans.clear();
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
|
@ -527,17 +730,87 @@ where
|
|||
);
|
||||
row_builder.finish();
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
DisplayLayoutInternal::TableCaption |
|
||||
DisplayLayoutInternal::TableColumn |
|
||||
DisplayLayoutInternal::TableColumnGroup => {
|
||||
// TODO: Handle these other types of table elements.
|
||||
self.builder.table.rows.push(TableTrack {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_index: self.current_row_group_index,
|
||||
is_anonymous: false,
|
||||
});
|
||||
|
||||
let last_row = self.builder.table.rows.len();
|
||||
let row_group = self
|
||||
.current_row_group_index
|
||||
.map(|index| &mut self.builder.table.row_groups[index]);
|
||||
if let Some(row_group) = row_group {
|
||||
row_group.track_range.end = last_row;
|
||||
}
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
DisplayLayoutInternal::TableColumn => {
|
||||
let node = info.node.to_threadsafe();
|
||||
let span = (node.get_span().unwrap_or(1) as usize).min(1000);
|
||||
for _ in 0..span + 1 {
|
||||
self.builder.table.columns.push(TableTrack {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_index: None,
|
||||
is_anonymous: false,
|
||||
})
|
||||
}
|
||||
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot)
|
||||
},
|
||||
DisplayLayoutInternal::TableColumnGroup => {
|
||||
let column_group_index = self.builder.table.column_groups.len();
|
||||
let mut column_group_builder = TableColumnGroupBuilder {
|
||||
column_group_index,
|
||||
columns: Vec::new(),
|
||||
};
|
||||
|
||||
NonReplacedContents::try_from(contents).unwrap().traverse(
|
||||
self.context,
|
||||
info,
|
||||
&mut column_group_builder,
|
||||
);
|
||||
|
||||
let first_column = self.builder.table.columns.len();
|
||||
if column_group_builder.columns.is_empty() {
|
||||
let node = info.node.to_threadsafe();
|
||||
let span = (node.get_span().unwrap_or(1) as usize).min(1000);
|
||||
|
||||
self.builder.table.columns.extend(
|
||||
repeat(TableTrack {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_index: Some(column_group_index),
|
||||
is_anonymous: true,
|
||||
})
|
||||
.take(span),
|
||||
);
|
||||
} else {
|
||||
self.builder
|
||||
.table
|
||||
.columns
|
||||
.extend(column_group_builder.columns);
|
||||
}
|
||||
|
||||
self.builder.table.column_groups.push(TableTrackGroup {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_type: internal.into(),
|
||||
track_range: first_column..self.builder.table.columns.len(),
|
||||
});
|
||||
|
||||
::std::mem::forget(box_slot);
|
||||
},
|
||||
DisplayLayoutInternal::TableCaption => {
|
||||
// TODO: Handle table captions.
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot);
|
||||
},
|
||||
DisplayLayoutInternal::TableCell => {
|
||||
self.current_anonymous_row_content
|
||||
.push(AnonymousTableContent::Element {
|
||||
|
@ -682,8 +955,8 @@ where
|
|||
// when dealing with arbitrary DOM elements (perhaps created via
|
||||
// script).
|
||||
let node = info.node.to_threadsafe();
|
||||
let rowspan = std::cmp::min(node.get_rowspan() as usize, 65534);
|
||||
let colspan = std::cmp::min(node.get_colspan() as usize, 1000);
|
||||
let rowspan = (node.get_rowspan().unwrap_or(1) as usize).min(65534);
|
||||
let colspan = (node.get_colspan().unwrap_or(1) as usize).min(1000);
|
||||
|
||||
let contents = match contents.try_into() {
|
||||
Ok(non_replaced_contents) => {
|
||||
|
@ -735,3 +1008,50 @@ where
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TableColumnGroupBuilder {
|
||||
column_group_index: usize,
|
||||
columns: Vec<TableTrack>,
|
||||
}
|
||||
|
||||
impl<'dom, Node: 'dom> TraversalHandler<'dom, Node> for TableColumnGroupBuilder
|
||||
where
|
||||
Node: NodeExt<'dom>,
|
||||
{
|
||||
fn handle_text(&mut self, _info: &NodeAndStyleInfo<Node>, _text: Cow<'dom, str>) {}
|
||||
fn handle_element(
|
||||
&mut self,
|
||||
info: &NodeAndStyleInfo<Node>,
|
||||
display: DisplayGeneratingBox,
|
||||
_contents: Contents,
|
||||
box_slot: BoxSlot<'dom>,
|
||||
) {
|
||||
// We are doing this until we have actually set a Box for this `BoxSlot`.
|
||||
::std::mem::forget(box_slot);
|
||||
|
||||
if !matches!(
|
||||
display,
|
||||
DisplayGeneratingBox::LayoutInternal(DisplayLayoutInternal::TableColumn)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
self.columns.push(TableTrack {
|
||||
base_fragment_info: info.into(),
|
||||
style: info.style.clone(),
|
||||
group_index: Some(self.column_group_index),
|
||||
is_anonymous: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DisplayLayoutInternal> for TableTrackGroupType {
|
||||
fn from(value: DisplayLayoutInternal) -> Self {
|
||||
match value {
|
||||
DisplayLayoutInternal::TableColumnGroup => TableTrackGroupType::ColumnGroup,
|
||||
DisplayLayoutInternal::TableFooterGroup => TableTrackGroupType::FooterGroup,
|
||||
DisplayLayoutInternal::TableHeaderGroup => TableTrackGroupType::HeaderGroup,
|
||||
DisplayLayoutInternal::TableRowGroup => TableTrackGroupType::RowGroup,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,22 +2,26 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use std::ops::Add;
|
||||
|
||||
use app_units::{Au, MAX_AU};
|
||||
use euclid::num::Zero;
|
||||
use log::warn;
|
||||
use servo_arc::Arc;
|
||||
use style::computed_values::border_collapse::T as BorderCollapse;
|
||||
use style::logical_geometry::WritingMode;
|
||||
use style::values::computed::{CSSPixelLength, Length, Percentage};
|
||||
use style::properties::ComputedValues;
|
||||
use style::values::computed::{
|
||||
CSSPixelLength, Length, LengthPercentage as ComputedLengthPercentage, Percentage,
|
||||
};
|
||||
use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword};
|
||||
use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage};
|
||||
use style::Zero;
|
||||
|
||||
use super::{Table, TableSlot, TableSlotCell};
|
||||
use super::{Table, TableSlot, TableSlotCell, TableTrackGroup};
|
||||
use crate::context::LayoutContext;
|
||||
use crate::formatting_contexts::{Baselines, IndependentLayout};
|
||||
use crate::fragment_tree::{AnonymousFragment, BoxFragment, CollapsedBlockMargins, Fragment};
|
||||
use crate::geom::{AuOrAuto, LogicalRect, LogicalSides, LogicalVec2};
|
||||
use crate::fragment_tree::{
|
||||
BoxFragment, CollapsedBlockMargins, ExtraBackground, Fragment, PositioningFragment,
|
||||
};
|
||||
use crate::geom::{AuOrAuto, LengthPercentageOrAuto, LogicalRect, LogicalSides, LogicalVec2};
|
||||
use crate::positioned::{PositioningContext, PositioningContextLength};
|
||||
use crate::sizing::ContentSizes;
|
||||
use crate::style_ext::{Clamp, ComputedValuesExt, PaddingBorderMargin};
|
||||
|
@ -108,7 +112,7 @@ impl<'a> TableLayout<'a> {
|
|||
let writing_mode = containing_block.style.writing_mode;
|
||||
self.compute_column_constrainedness_and_has_originating_cells(writing_mode);
|
||||
self.compute_cell_measures(layout_context, containing_block);
|
||||
self.compute_column_measures();
|
||||
self.compute_column_measures(containing_block.style.writing_mode);
|
||||
self.compute_table_width(containing_block);
|
||||
self.distributed_column_widths = self.distribute_width_to_columns();
|
||||
self.do_row_layout_first_pass(layout_context, containing_block, positioning_context);
|
||||
|
@ -132,84 +136,47 @@ impl<'a> TableLayout<'a> {
|
|||
_ => continue,
|
||||
};
|
||||
|
||||
// TODO: Should `box_size` percentages be treated as zero here or resolved against
|
||||
// the containing block?
|
||||
let pbm = cell.style.padding_border_margin(containing_block);
|
||||
let min_inline_size: Au = cell
|
||||
.style
|
||||
.min_box_size(writing_mode)
|
||||
.inline
|
||||
.percentage_relative_to(Length::zero())
|
||||
.map(|value| value.into())
|
||||
.auto_is(Au::zero);
|
||||
let max_inline_size: Au = cell.style.max_box_size(writing_mode).inline.map_or_else(
|
||||
|| MAX_AU,
|
||||
|length_percentage| length_percentage.resolve(Length::zero()).into(),
|
||||
);
|
||||
let inline_size: Au = cell
|
||||
.style
|
||||
.box_size(writing_mode)
|
||||
.inline
|
||||
.percentage_relative_to(Length::zero())
|
||||
.map(|value| value.into())
|
||||
.auto_is(Au::zero);
|
||||
|
||||
let (size, min_size, max_size) = get_sizes_from_style(&cell.style, writing_mode);
|
||||
let content_sizes = cell
|
||||
.contents
|
||||
.contents
|
||||
.inline_content_sizes(layout_context, writing_mode);
|
||||
let percentage_contribution =
|
||||
get_size_percentage_contribution_from_style(&cell.style, writing_mode);
|
||||
|
||||
// > The outer min-content width of a table-cell is max(min-width, min-content width)
|
||||
// > adjusted by the cell intrinsic offsets.
|
||||
let mut outer_min_content_width = content_sizes.min_content.max(min_inline_size);
|
||||
let mut outer_min_content_width = content_sizes.min_content.max(min_size.inline);
|
||||
let mut outer_max_content_width = if !self.column_constrainedness[column_index] {
|
||||
// > The outer max-content width of a table-cell in a non-constrained column is
|
||||
// > max(min-width, width, min-content width, min(max-width, max-content width))
|
||||
// > adjusted by the cell intrinsic offsets.
|
||||
min_inline_size
|
||||
.max(inline_size)
|
||||
min_size
|
||||
.inline
|
||||
.max(size.inline)
|
||||
.max(content_sizes.min_content)
|
||||
.max(max_inline_size.min(content_sizes.max_content))
|
||||
.max(max_size.inline.min(content_sizes.max_content))
|
||||
} else {
|
||||
// > The outer max-content width of a table-cell in a constrained column is
|
||||
// > max(min-width, width, min-content width, min(max-width, width)) adjusted by the
|
||||
// > cell intrinsic offsets.
|
||||
min_inline_size
|
||||
.max(inline_size)
|
||||
min_size
|
||||
.inline
|
||||
.max(size.inline)
|
||||
.max(content_sizes.min_content)
|
||||
.max(max_inline_size.min(inline_size))
|
||||
.max(max_size.inline.min(size.inline))
|
||||
};
|
||||
|
||||
// > The percentage contribution of a table cell, column, or column group is defined
|
||||
// > in terms of the computed values of width and max-width that have computed values
|
||||
// > that are percentages:
|
||||
// > min(percentage width, percentage max-width).
|
||||
// > If the computed values are not percentages, then 0% is used for width, and an
|
||||
// > infinite percentage is used for max-width.
|
||||
let inline_size_percent = cell
|
||||
.style
|
||||
.box_size(writing_mode)
|
||||
.inline
|
||||
.non_auto()
|
||||
.and_then(|length_percentage| length_percentage.to_percentage())
|
||||
.unwrap_or(Percentage(0.));
|
||||
let max_inline_size_percent = cell
|
||||
.style
|
||||
.max_box_size(writing_mode)
|
||||
.inline
|
||||
.and_then(|length_percentage| length_percentage.to_percentage())
|
||||
.unwrap_or(Percentage(f32::INFINITY));
|
||||
let percentage_contribution =
|
||||
Percentage(inline_size_percent.0.min(max_inline_size_percent.0));
|
||||
|
||||
let pbm = cell.style.padding_border_margin(containing_block);
|
||||
outer_min_content_width += pbm.padding_border_sums.inline;
|
||||
outer_max_content_width += pbm.padding_border_sums.inline;
|
||||
|
||||
row_measures[column_index] = CellOrColumnMeasure {
|
||||
content_sizes: ContentSizes {
|
||||
min_content: outer_min_content_width,
|
||||
max_content: outer_max_content_width,
|
||||
},
|
||||
percentage_width: percentage_contribution,
|
||||
percentage_width: percentage_contribution.inline,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -254,7 +221,7 @@ impl<'a> TableLayout<'a> {
|
|||
|
||||
/// This is an implementation of *Computing Column Measures* from
|
||||
/// <https://drafts.csswg.org/css-tables/#computing-column-measures>.
|
||||
fn compute_column_measures(&mut self) {
|
||||
fn compute_column_measures(&mut self, writing_mode: WritingMode) {
|
||||
let mut column_measures = Vec::new();
|
||||
|
||||
// Compute the column measures only taking into account cells with colspan == 1.
|
||||
|
@ -289,7 +256,9 @@ impl<'a> TableLayout<'a> {
|
|||
// TODO: Take into account changes to this computation for fixed table layout.
|
||||
let mut next_span_n = usize::MAX;
|
||||
for column_index in 0..self.table.size.width {
|
||||
let mut column_measure = CellOrColumnMeasure::zero();
|
||||
let mut column_measure = self
|
||||
.table
|
||||
.get_column_measure_for_column_at_index(writing_mode, column_index);
|
||||
|
||||
for row_index in 0..self.table.size.height {
|
||||
let coords = TableSlotCoordinates::new(column_index, row_index);
|
||||
|
@ -652,8 +621,6 @@ impl<'a> TableLayout<'a> {
|
|||
let mut max_content_sizing_guesses = Vec::new();
|
||||
|
||||
for column_idx in 0..self.table.size.width {
|
||||
use style::Zero;
|
||||
|
||||
let column_measure = &self.column_measures[column_idx];
|
||||
let min_content_width = column_measure.content_sizes.min_content;
|
||||
let max_content_width = column_measure.content_sizes.max_content;
|
||||
|
@ -1040,14 +1007,20 @@ impl<'a> TableLayout<'a> {
|
|||
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
|
||||
|
||||
let mut baselines = Baselines::default();
|
||||
let border_spacing = self.table.border_spacing();
|
||||
let mut fragments = Vec::new();
|
||||
let mut row_offset = border_spacing.block;
|
||||
for row_index in 0..self.table.size.height {
|
||||
let mut column_offset = border_spacing.inline;
|
||||
let row_size = self.row_sizes[row_index];
|
||||
let row_baseline = self.row_baselines[row_index];
|
||||
|
||||
if self.table.size.width == 0 || self.table.size.height == 0 {
|
||||
return IndependentLayout {
|
||||
fragments,
|
||||
content_block_size: Au::zero(),
|
||||
baselines,
|
||||
};
|
||||
}
|
||||
|
||||
let dimensions = TableAndTrackDimensions::new(&self);
|
||||
self.make_fragments_for_columns_rows_and_groups(&dimensions, &mut fragments);
|
||||
|
||||
for row_index in 0..self.table.size.height {
|
||||
// From <https://drafts.csswg.org/css-align-3/#baseline-export>
|
||||
// > If any cells in the row participate in first baseline/last baseline alignment along
|
||||
// > the inline axis, the first/last baseline set of the row is generated from their
|
||||
|
@ -1058,8 +1031,9 @@ impl<'a> TableLayout<'a> {
|
|||
// If any cell below has baseline alignment, these values will be overwritten,
|
||||
// but they are initialized to the content edge of the first row.
|
||||
if row_index == 0 {
|
||||
baselines.first = Some(row_offset + row_size);
|
||||
baselines.last = Some(row_offset + row_size);
|
||||
let row_end = dimensions.get_row_rect(0).max_block_position();
|
||||
baselines.first = Some(row_end);
|
||||
baselines.last = Some(row_end);
|
||||
}
|
||||
|
||||
for column_index in 0..self.table.size.width {
|
||||
|
@ -1078,46 +1052,65 @@ impl<'a> TableLayout<'a> {
|
|||
},
|
||||
};
|
||||
|
||||
let cell_rect = dimensions.get_cell_rect(
|
||||
TableSlotCoordinates::new(column_index, row_index),
|
||||
cell.rowspan,
|
||||
cell.colspan,
|
||||
);
|
||||
|
||||
// If this cell has baseline alignment, it can adjust the table's overall baseline.
|
||||
let row_baseline = self.row_baselines[row_index];
|
||||
if cell.effective_vertical_align() == VerticalAlignKeyword::Baseline {
|
||||
let baseline = cell_rect.start_corner.block + row_baseline;
|
||||
if row_index == 0 {
|
||||
baselines.first = Some(row_offset + row_baseline);
|
||||
baselines.first = Some(baseline);
|
||||
}
|
||||
baselines.last = Some(row_offset + row_baseline);
|
||||
baselines.last = Some(baseline);
|
||||
}
|
||||
|
||||
// Calculate the inline and block size of all rows and columns that this cell spans.
|
||||
let inline_size: Au = (column_index..column_index + cell.colspan)
|
||||
.map(|index| self.distributed_column_widths[index])
|
||||
.fold(Au::zero(), Au::add) +
|
||||
((cell.colspan - 1) as i32 * border_spacing.inline);
|
||||
let block_size: Au = (row_index..row_index + cell.rowspan)
|
||||
.map(|index| self.row_sizes[index])
|
||||
.fold(Au::zero(), Au::add) +
|
||||
((cell.rowspan - 1) as i32 * border_spacing.block);
|
||||
let mut fragment =
|
||||
cell.create_fragment(layout, cell_rect, row_baseline, positioning_context);
|
||||
|
||||
let cell_rect: LogicalRect<Length> = LogicalRect {
|
||||
start_corner: LogicalVec2 {
|
||||
inline: column_offset.into(),
|
||||
block: row_offset.into(),
|
||||
},
|
||||
size: LogicalVec2 {
|
||||
inline: inline_size.into(),
|
||||
block: block_size.into(),
|
||||
},
|
||||
};
|
||||
let column = self.table.columns.get(column_index);
|
||||
let column_group = column
|
||||
.and_then(|column| column.group_index)
|
||||
.and_then(|index| self.table.column_groups.get(index));
|
||||
if let Some(column_group) = column_group {
|
||||
fragment.add_extra_background(ExtraBackground {
|
||||
style: column_group.style.clone(),
|
||||
rect: dimensions.get_column_group_rect(column_group),
|
||||
})
|
||||
}
|
||||
|
||||
fragments.push(Fragment::Box(cell.create_fragment(
|
||||
layout,
|
||||
cell_rect,
|
||||
row_baseline,
|
||||
positioning_context,
|
||||
)));
|
||||
if let Some(column) = column {
|
||||
if !column.is_anonymous {
|
||||
fragment.add_extra_background(ExtraBackground {
|
||||
style: column.style.clone(),
|
||||
rect: dimensions.get_column_rect(column_index),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
column_offset += inline_size + border_spacing.inline;
|
||||
let row = self.table.rows.get(row_index);
|
||||
let row_group = row
|
||||
.and_then(|row| row.group_index)
|
||||
.and_then(|index| self.table.row_groups.get(index));
|
||||
if let Some(row_group) = row_group {
|
||||
fragment.add_extra_background(ExtraBackground {
|
||||
style: row_group.style.clone(),
|
||||
rect: dimensions.get_row_group_rect(row_group),
|
||||
})
|
||||
}
|
||||
|
||||
if let Some(row) = row {
|
||||
fragment.add_extra_background(ExtraBackground {
|
||||
style: row.style.clone(),
|
||||
rect: dimensions.get_row_rect(row_index),
|
||||
})
|
||||
}
|
||||
|
||||
fragments.push(Fragment::Box(fragment));
|
||||
}
|
||||
|
||||
row_offset += row_size + border_spacing.block;
|
||||
}
|
||||
|
||||
if self.table.anonymous {
|
||||
|
@ -1127,10 +1120,173 @@ impl<'a> TableLayout<'a> {
|
|||
|
||||
IndependentLayout {
|
||||
fragments,
|
||||
content_block_size: row_offset,
|
||||
content_block_size: dimensions.table_rect.max_block_position(),
|
||||
baselines,
|
||||
}
|
||||
}
|
||||
|
||||
fn make_fragments_for_columns_rows_and_groups(
|
||||
&mut self,
|
||||
dimensions: &TableAndTrackDimensions,
|
||||
fragments: &mut Vec<Fragment>,
|
||||
) {
|
||||
for column_group in self.table.column_groups.iter() {
|
||||
if !column_group.is_empty() {
|
||||
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
|
||||
column_group.base_fragment_info,
|
||||
dimensions.get_column_group_rect(column_group).into(),
|
||||
column_group.style.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
for (column_index, column) in self.table.columns.iter().enumerate() {
|
||||
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
|
||||
column.base_fragment_info,
|
||||
dimensions.get_column_rect(column_index).into(),
|
||||
column.style.clone(),
|
||||
)));
|
||||
}
|
||||
|
||||
for row_group in self.table.row_groups.iter() {
|
||||
if !row_group.is_empty() {
|
||||
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
|
||||
row_group.base_fragment_info,
|
||||
dimensions.get_row_group_rect(row_group).into(),
|
||||
row_group.style.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
for (row_index, row) in self.table.rows.iter().enumerate() {
|
||||
fragments.push(Fragment::Positioning(PositioningFragment::new_empty(
|
||||
row.base_fragment_info,
|
||||
dimensions.get_row_rect(row_index).into(),
|
||||
row.style.clone(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TableAndTrackDimensions {
|
||||
/// The rect of the full table, not counting for borders, padding, and margin.
|
||||
table_rect: LogicalRect<Au>,
|
||||
/// The rect of the full table, not counting for borders, padding, and margin
|
||||
/// and offset by any border spacing and caption.
|
||||
table_cells_rect: LogicalRect<Au>,
|
||||
/// The min and max block offsets of each table row.
|
||||
row_dimensions: Vec<(Au, Au)>,
|
||||
/// The min and max inline offsets of each table column
|
||||
column_dimensions: Vec<(Au, Au)>,
|
||||
}
|
||||
|
||||
impl TableAndTrackDimensions {
|
||||
fn new(table_layout: &TableLayout) -> Self {
|
||||
let border_spacing = table_layout.table.border_spacing();
|
||||
|
||||
let mut column_dimensions = Vec::new();
|
||||
let mut column_offset = border_spacing.inline;
|
||||
for column_index in 0..table_layout.table.size.width {
|
||||
let column_size = table_layout.distributed_column_widths[column_index];
|
||||
column_dimensions.push((column_offset, column_offset + column_size));
|
||||
column_offset += column_size + border_spacing.inline;
|
||||
}
|
||||
|
||||
let mut row_dimensions = Vec::new();
|
||||
let mut row_offset = border_spacing.block;
|
||||
for row_index in 0..table_layout.table.size.height {
|
||||
let row_size = table_layout.row_sizes[row_index];
|
||||
row_dimensions.push((row_offset, row_offset + row_size));
|
||||
row_offset += row_size + border_spacing.block;
|
||||
}
|
||||
|
||||
let table_start_corner = LogicalVec2 {
|
||||
inline: column_dimensions[0].0,
|
||||
block: row_dimensions[0].0,
|
||||
};
|
||||
let table_size = &LogicalVec2 {
|
||||
inline: column_dimensions[column_dimensions.len() - 1].1,
|
||||
block: row_dimensions[row_dimensions.len() - 1].1,
|
||||
} - &table_start_corner;
|
||||
let table_cells_rect = LogicalRect {
|
||||
start_corner: table_start_corner,
|
||||
size: table_size,
|
||||
};
|
||||
|
||||
let table_rect = LogicalRect {
|
||||
start_corner: LogicalVec2::zero(),
|
||||
size: LogicalVec2 {
|
||||
inline: column_offset,
|
||||
block: row_offset,
|
||||
},
|
||||
};
|
||||
|
||||
Self {
|
||||
table_rect,
|
||||
table_cells_rect,
|
||||
row_dimensions,
|
||||
column_dimensions,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_row_rect(&self, row_index: usize) -> LogicalRect<Au> {
|
||||
let mut row_rect = self.table_cells_rect.clone();
|
||||
let row_dimensions = self.row_dimensions[row_index];
|
||||
row_rect.start_corner.block = row_dimensions.0;
|
||||
row_rect.size.block = row_dimensions.1 - row_dimensions.0;
|
||||
row_rect
|
||||
}
|
||||
|
||||
fn get_column_rect(&self, column_index: usize) -> LogicalRect<Au> {
|
||||
let mut row_rect = self.table_cells_rect.clone();
|
||||
let column_dimensions = self.column_dimensions[column_index];
|
||||
row_rect.start_corner.inline = column_dimensions.0;
|
||||
row_rect.size.inline = column_dimensions.1 - column_dimensions.0;
|
||||
row_rect
|
||||
}
|
||||
|
||||
fn get_row_group_rect(&self, row_group: &TableTrackGroup) -> LogicalRect<Au> {
|
||||
if row_group.is_empty() {
|
||||
return LogicalRect::zero();
|
||||
}
|
||||
|
||||
let mut row_group_rect = self.table_cells_rect.clone();
|
||||
let block_start = self.row_dimensions[row_group.track_range.start].0;
|
||||
let block_end = self.row_dimensions[row_group.track_range.end - 1].1;
|
||||
row_group_rect.start_corner.block = block_start;
|
||||
row_group_rect.size.block = block_end - block_start;
|
||||
row_group_rect
|
||||
}
|
||||
|
||||
fn get_column_group_rect(&self, column_group: &TableTrackGroup) -> LogicalRect<Au> {
|
||||
if column_group.is_empty() {
|
||||
return LogicalRect::zero();
|
||||
}
|
||||
|
||||
let mut column_group_rect = self.table_cells_rect.clone();
|
||||
let inline_start = self.column_dimensions[column_group.track_range.start].0;
|
||||
let inline_end = self.column_dimensions[column_group.track_range.end - 1].1;
|
||||
column_group_rect.start_corner.inline = inline_start;
|
||||
column_group_rect.size.inline = inline_end - inline_start;
|
||||
column_group_rect
|
||||
}
|
||||
|
||||
fn get_cell_rect(
|
||||
&self,
|
||||
coordinates: TableSlotCoordinates,
|
||||
rowspan: usize,
|
||||
colspan: usize,
|
||||
) -> LogicalRect<Au> {
|
||||
let start_corner = LogicalVec2 {
|
||||
inline: self.column_dimensions[coordinates.x].0,
|
||||
block: self.row_dimensions[coordinates.y].0,
|
||||
};
|
||||
let size = &LogicalVec2 {
|
||||
inline: self.column_dimensions[coordinates.x + colspan - 1].1,
|
||||
block: self.row_dimensions[coordinates.y + rowspan - 1].1,
|
||||
} - &start_corner;
|
||||
LogicalRect { start_corner, size }
|
||||
}
|
||||
}
|
||||
|
||||
impl Table {
|
||||
|
@ -1200,6 +1356,33 @@ impl Table {
|
|||
.0
|
||||
}
|
||||
|
||||
fn get_column_measure_for_column_at_index(
|
||||
&self,
|
||||
writing_mode: WritingMode,
|
||||
column_index: usize,
|
||||
) -> CellOrColumnMeasure {
|
||||
let column = match self.columns.get(column_index) {
|
||||
Some(column) => column,
|
||||
None => return CellOrColumnMeasure::zero(),
|
||||
};
|
||||
|
||||
let (size, min_size, max_size) = get_sizes_from_style(&column.style, writing_mode);
|
||||
let percentage_contribution =
|
||||
get_size_percentage_contribution_from_style(&column.style, writing_mode);
|
||||
|
||||
CellOrColumnMeasure {
|
||||
content_sizes: ContentSizes {
|
||||
// > The outer min-content width of a table-column or table-column-group is
|
||||
// > max(min-width, width).
|
||||
min_content: min_size.inline.max(size.inline),
|
||||
// > The outer max-content width of a table-column or table-column-group is
|
||||
// > max(min-width, min(max-width, width)).
|
||||
max_content: min_size.inline.max(max_size.inline.min(size.inline)),
|
||||
},
|
||||
percentage_width: percentage_contribution.inline,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn layout(
|
||||
&self,
|
||||
layout_context: &LayoutContext,
|
||||
|
@ -1250,13 +1433,14 @@ impl TableSlotCell {
|
|||
fn create_fragment(
|
||||
&self,
|
||||
mut layout: CellLayout,
|
||||
cell_rect: LogicalRect<Length>,
|
||||
cell_rect: LogicalRect<Au>,
|
||||
cell_baseline: Au,
|
||||
positioning_context: &mut PositioningContext,
|
||||
) -> BoxFragment {
|
||||
// This must be scoped to this function because it conflicts with euclid's Zero.
|
||||
use style::Zero as StyleZero;
|
||||
|
||||
let cell_rect: LogicalRect<Length> = cell_rect.into();
|
||||
let cell_content_rect = cell_rect.deflate(&(&layout.padding + &layout.border));
|
||||
let content_block_size = layout.layout.content_block_size.into();
|
||||
let vertical_align_offset = match self.effective_vertical_align() {
|
||||
|
@ -1278,7 +1462,7 @@ impl TableSlotCell {
|
|||
inline: Length::new(0.),
|
||||
block: vertical_align_offset,
|
||||
};
|
||||
let vertical_align_fragment = AnonymousFragment::new(
|
||||
let vertical_align_fragment = PositioningFragment::new_anonymous(
|
||||
vertical_align_fragment_rect,
|
||||
layout.layout.fragments,
|
||||
self.style.writing_mode,
|
||||
|
@ -1302,7 +1486,7 @@ impl TableSlotCell {
|
|||
BoxFragment::new(
|
||||
self.base_fragment_info,
|
||||
self.style.clone(),
|
||||
vec![Fragment::Anonymous(vertical_align_fragment)],
|
||||
vec![Fragment::Positioning(vertical_align_fragment)],
|
||||
cell_content_rect,
|
||||
layout.padding,
|
||||
layout.border,
|
||||
|
@ -1313,3 +1497,73 @@ impl TableSlotCell {
|
|||
.with_baselines(layout.layout.baselines)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_size_percentage_contribution_from_style(
|
||||
style: &Arc<ComputedValues>,
|
||||
writing_mode: WritingMode,
|
||||
) -> LogicalVec2<Percentage> {
|
||||
// From <https://drafts.csswg.org/css-tables/#percentage-contribution>
|
||||
// > The percentage contribution of a table cell, column, or column group is defined
|
||||
// > in terms of the computed values of width and max-width that have computed values
|
||||
// > that are percentages:
|
||||
// > min(percentage width, percentage max-width).
|
||||
// > If the computed values are not percentages, then 0% is used for width, and an
|
||||
// > infinite percentage is used for max-width.
|
||||
let size = style.box_size(writing_mode);
|
||||
let max_size = style.max_box_size(writing_mode);
|
||||
|
||||
let get_contribution_for_axis =
|
||||
|size: LengthPercentageOrAuto<'_>, max_size: Option<&ComputedLengthPercentage>| {
|
||||
let size_percentage = size
|
||||
.non_auto()
|
||||
.and_then(|length_percentage| length_percentage.to_percentage())
|
||||
.unwrap_or(Percentage(0.));
|
||||
let max_size_percentage = max_size
|
||||
.and_then(|length_percentage| length_percentage.to_percentage())
|
||||
.unwrap_or(Percentage(f32::INFINITY));
|
||||
Percentage(size_percentage.0.min(max_size_percentage.0))
|
||||
};
|
||||
|
||||
LogicalVec2 {
|
||||
inline: get_contribution_for_axis(size.inline, max_size.inline),
|
||||
block: get_contribution_for_axis(size.block, max_size.block),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_sizes_from_style(
|
||||
style: &Arc<ComputedValues>,
|
||||
writing_mode: WritingMode,
|
||||
) -> (LogicalVec2<Au>, LogicalVec2<Au>, LogicalVec2<Au>) {
|
||||
let get_max_size_for_axis = |size: Option<&ComputedLengthPercentage>| {
|
||||
size.map_or_else(
|
||||
|| MAX_AU,
|
||||
|length_percentage| length_percentage.resolve(Length::zero()).into(),
|
||||
)
|
||||
};
|
||||
|
||||
let max_size = style.max_box_size(writing_mode);
|
||||
let max_size = LogicalVec2 {
|
||||
inline: get_max_size_for_axis(max_size.inline),
|
||||
block: get_max_size_for_axis(max_size.block),
|
||||
};
|
||||
|
||||
let get_size_for_axis = |size: LengthPercentageOrAuto<'_>| {
|
||||
size.percentage_relative_to(Length::zero())
|
||||
.map(|value| value.into())
|
||||
.auto_is(Au::zero)
|
||||
};
|
||||
|
||||
let min_size = style.min_box_size(writing_mode);
|
||||
let min_size = LogicalVec2 {
|
||||
inline: get_size_for_axis(min_size.inline),
|
||||
block: get_size_for_axis(min_size.block),
|
||||
};
|
||||
|
||||
let size = style.box_size(writing_mode);
|
||||
let size = LogicalVec2 {
|
||||
inline: get_size_for_axis(size.inline),
|
||||
block: get_size_for_axis(size.block),
|
||||
};
|
||||
|
||||
(size, min_size, max_size)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
mod construct;
|
||||
mod layout;
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
pub(crate) use construct::AnonymousTableContent;
|
||||
pub use construct::TableBuilder;
|
||||
use euclid::{Point2D, Size2D, UnknownUnit, Vector2D};
|
||||
|
@ -32,6 +34,19 @@ pub struct Table {
|
|||
#[serde(skip_serializing)]
|
||||
style: Arc<ComputedValues>,
|
||||
|
||||
/// The column groups for this table.
|
||||
pub column_groups: Vec<TableTrackGroup>,
|
||||
|
||||
/// The columns of this tabled defined by `<colgroup> | display: table-column-group`
|
||||
/// and `<col> | display: table-column` elements as well as `display: table-column`.
|
||||
pub columns: Vec<TableTrack>,
|
||||
|
||||
/// The rows groups for this table deinfed by `<tbody>`, `<thead>`, and `<tfoot>`.
|
||||
pub row_groups: Vec<TableTrackGroup>,
|
||||
|
||||
/// The rows of this tabled defined by `<tr>` or `display: table-row` elements.
|
||||
pub rows: Vec<TableTrack>,
|
||||
|
||||
/// The content of the slots of this table.
|
||||
pub slots: Vec<Vec<TableSlot>>,
|
||||
|
||||
|
@ -46,6 +61,10 @@ impl Table {
|
|||
pub(crate) fn new(style: Arc<ComputedValues>) -> Self {
|
||||
Self {
|
||||
style,
|
||||
column_groups: Vec::new(),
|
||||
columns: Vec::new(),
|
||||
row_groups: Vec::new(),
|
||||
rows: Vec::new(),
|
||||
slots: Vec::new(),
|
||||
size: TableSize::zero(),
|
||||
anonymous: false,
|
||||
|
@ -167,3 +186,52 @@ impl TableSlot {
|
|||
Self::Spanned(vec![offset])
|
||||
}
|
||||
}
|
||||
|
||||
/// A row or column of a table.
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
pub struct TableTrack {
|
||||
/// The [`BaseFragmentInfo`] of this cell.
|
||||
base_fragment_info: BaseFragmentInfo,
|
||||
|
||||
/// The style of this table column.
|
||||
#[serde(skip_serializing)]
|
||||
style: Arc<ComputedValues>,
|
||||
|
||||
/// The index of the table row or column group parent in the table's list of row or column
|
||||
/// groups.
|
||||
group_index: Option<usize>,
|
||||
|
||||
/// Whether or not this [`TableTrack`] was anonymous, for instance created due to
|
||||
/// a `span` attribute set on a parent `<colgroup>`.
|
||||
is_anonymous: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize)]
|
||||
pub enum TableTrackGroupType {
|
||||
HeaderGroup,
|
||||
FooterGroup,
|
||||
RowGroup,
|
||||
ColumnGroup,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct TableTrackGroup {
|
||||
/// The [`BaseFragmentInfo`] of this [`TableTrackGroup`].
|
||||
base_fragment_info: BaseFragmentInfo,
|
||||
|
||||
/// The style of this [`TableTrackGroup`].
|
||||
#[serde(skip_serializing)]
|
||||
style: Arc<ComputedValues>,
|
||||
|
||||
/// The type of this [`TableTrackGroup`].
|
||||
group_type: TableTrackGroupType,
|
||||
|
||||
/// The range of tracks in this [`TableTrackGroup`].
|
||||
track_range: Range<usize>,
|
||||
}
|
||||
|
||||
impl TableTrackGroup {
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.track_range.is_empty()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue