diff --git a/components/gfx/display_list/mod.rs b/components/gfx/display_list/mod.rs index c14f91308d1..75f6cde1e54 100644 --- a/components/gfx/display_list/mod.rs +++ b/components/gfx/display_list/mod.rs @@ -171,13 +171,27 @@ impl DisplayList { let doit = |items: &Vec| { for item in items.iter() { match *item { - DisplayItem::SolidColorClass(ref solid_color) => println!("{} SolidColor. {:?}", indentation, solid_color.base.bounds), - DisplayItem::TextClass(ref text) => println!("{:?} Text. {:?}", indentation, text.base.bounds), - DisplayItem::ImageClass(ref image) => println!("{:?} Image. {:?}", indentation, image.base.bounds), - DisplayItem::BorderClass(ref border) => println!("{:?} Border. {:?}", indentation, border.base.bounds), - DisplayItem::GradientClass(ref gradient) => println!("{:?} Gradient. {:?}", indentation, gradient.base.bounds), - DisplayItem::LineClass(ref line) => println!("{:?} Line. {:?}", indentation, line.base.bounds), - DisplayItem::BoxShadowClass(ref box_shadow) => println!("{:?} Box_shadow. {:?}", indentation, box_shadow.base.bounds), + DisplayItem::SolidColorClass(ref solid_color) => { + println!("{:?} SolidColor. {:?}", indentation, solid_color.base.bounds) + } + DisplayItem::TextClass(ref text) => { + println!("{:?} Text. {:?}", indentation, text.base.bounds) + } + DisplayItem::ImageClass(ref image) => { + println!("{:?} Image. {:?}", indentation, image.base.bounds) + } + DisplayItem::BorderClass(ref border) => { + println!("{:?} Border. {:?}", indentation, border.base.bounds) + } + DisplayItem::GradientClass(ref gradient) => { + println!("{:?} Gradient. {:?}", indentation, gradient.base.bounds) + } + DisplayItem::LineClass(ref line) => { + println!("{:?} Line. {:?}", indentation, line.base.bounds) + } + DisplayItem::BoxShadowClass(ref box_shadow) => { + println!("{:?} Box_shadow. {:?}", indentation, box_shadow.base.bounds) + } } } println!("\n"); diff --git a/components/gfx/paint_context.rs b/components/gfx/paint_context.rs index 1af8b8f695d..2db375e2cd9 100644 --- a/components/gfx/paint_context.rs +++ b/components/gfx/paint_context.rs @@ -290,11 +290,7 @@ impl<'a> PaintContext<'a> { radii: &BorderRadii, color: Color) { let mut path_builder = self.draw_target.create_path_builder(); - self.create_border_path_segment(&mut path_builder, - bounds, - direction, - border, - radii); + self.create_border_path_segment(&mut path_builder, bounds, direction, border, radii); let draw_options = DrawOptions::new(1.0, 0); self.draw_target.fill(&path_builder.finish(), &ColorPattern::new(color), &draw_options); } @@ -693,7 +689,8 @@ impl<'a> PaintContext<'a> { let scaled_left_top = left_top + Point2D(scaled_border.left, scaled_border.top); return Rect(scaled_left_top, - Size2D(rect.size.width - 2.0 * scaled_border.right, rect.size.height - 2.0 * scaled_border.bottom)); + Size2D(rect.size.width - 2.0 * scaled_border.right, + rect.size.height - 2.0 * scaled_border.bottom)); } fn scale_color(&self, color: Color, scale_factor: f32) -> Color { @@ -1129,6 +1126,7 @@ impl ToAzureRect for Rect { fn to_azure_rect(&self) -> Rect { Rect(self.origin.to_azure_point(), Size2D(self.size.width.to_nearest_px() as AzFloat, self.size.height.to_nearest_px() as AzFloat)) + } fn to_subpx_azure_rect(&self) -> Rect { Rect(self.origin.to_subpx_azure_point(), Size2D(self.size.width.to_subpx() as AzFloat, diff --git a/components/layout/block.rs b/components/layout/block.rs index e616a952643..15d6e2e1684 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -29,7 +29,8 @@ use context::LayoutContext; use css::node_style::StyledNode; -use display_list_builder::{BlockFlowDisplayListBuilding, FragmentDisplayListBuilding}; +use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode}; +use display_list_builder::{FragmentDisplayListBuilding}; use floats::{ClearType, FloatKind, Floats, PlacementInfo}; use flow::{self, AbsolutePositionInfo, BaseFlow, ForceNonfloatedFlag, FlowClass, Flow}; use flow::{ImmutableFlowUtils, MutableFlowUtils, PreorderFlowTraversal}; @@ -44,7 +45,6 @@ use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; use layout_debug; use model::{IntrinsicISizes, MarginCollapseInfo}; use model::{MaybeAuto, CollapsibleMargins, specified, specified_or_none}; -use table; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect, Size2D}; @@ -54,12 +54,13 @@ use rustc_serialize::{Encoder, Encodable}; use std::cmp::{max, min}; use std::fmt; use std::sync::Arc; -use style::computed_values::{overflow_x, overflow_y, position, box_sizing, display, float}; +use style::computed_values::{border_collapse, box_sizing, display, float, overflow_x, overflow_y}; +use style::computed_values::{position}; use style::properties::ComputedValues; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; use style::values::computed::{LengthOrPercentageOrNone}; use util::geometry::{Au, MAX_AU}; -use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize}; +use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; use util::opts; /// Information specific to floated blocks. @@ -621,44 +622,51 @@ impl BlockFlow { /// Compute the actual inline size and position for this block. pub fn compute_used_inline_size(&mut self, layout_context: &LayoutContext, - containing_block_inline_size: Au) { + containing_block_inline_size: Au, + border_collapse: border_collapse::T) { let block_type = self.block_type(); match block_type { BlockType::AbsoluteReplaced => { let inline_size_computer = AbsoluteReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } BlockType::AbsoluteNonReplaced => { let inline_size_computer = AbsoluteNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } BlockType::FloatReplaced => { let inline_size_computer = FloatReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } BlockType::FloatNonReplaced => { let inline_size_computer = FloatNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } BlockType::Replaced => { let inline_size_computer = BlockReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } BlockType::NonReplaced => { let inline_size_computer = BlockNonReplaced; inline_size_computer.compute_used_inline_size(self, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); } } } @@ -1193,27 +1201,37 @@ impl BlockFlow { /// Compute inline size based using the `block_container_inline_size` set by the parent flow. /// /// This is run in the `AssignISizes` traversal. - pub fn propagate_and_compute_used_inline_size(&mut self, layout_context: &LayoutContext) { + fn propagate_and_compute_used_inline_size(&mut self, + layout_context: &LayoutContext, + border_collapse: border_collapse::T) { let containing_block_inline_size = self.base.block_container_inline_size; - self.compute_used_inline_size(layout_context, containing_block_inline_size); + self.compute_used_inline_size(layout_context, + containing_block_inline_size, + border_collapse); if self.base.flags.is_float() { - self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size; + self.float.as_mut().unwrap().containing_inline_size = containing_block_inline_size } } /// Assigns the computed inline-start content edge and inline-size to all the children of this - /// block flow. Also computes whether each child will be impacted by floats. + /// block flow. Also computes whether each child will be impacted by floats. The given + /// `callback`, if supplied, will be called once per child; it is currently used to push down + /// column sizes for tables. /// /// `#[inline(always)]` because this is called only from block or table inline-size assignment /// and the code for block layout is significantly simpler. #[inline(always)] - pub fn propagate_assigned_inline_size_to_children( - &mut self, - layout_context: &LayoutContext, - inline_start_content_edge: Au, - inline_end_content_edge: Au, - content_inline_size: Au, - table_info: Option) { + pub fn propagate_assigned_inline_size_to_children(&mut self, + layout_context: &LayoutContext, + inline_start_content_edge: Au, + inline_end_content_edge: Au, + content_inline_size: Au, + mut callback: F) + where F: FnMut(&mut Flow, + usize, + Au, + WritingMode, + &mut Au) { // Keep track of whether floats could impact each child. let mut inline_start_floats_impact_child = self.base.flags.contains(IMPACTED_BY_LEFT_FLOATS); @@ -1277,13 +1295,10 @@ impl BlockFlow { let containing_block_mode = self.base.writing_mode; // This value is used only for table cells. - let mut inline_start_margin_edge = if table_info.is_some() { - inline_start_content_edge - } else { - Au(0) - }; + let mut inline_start_margin_edge = inline_start_content_edge; - for (i, kid) in self.base.child_iter().enumerate() { + let mut iterator = self.base.child_iter().enumerate().peekable(); + while let Some((i, kid)) = iterator.next() { { let kid_base = flow::mut_base(kid); kid_base.block_container_explicit_block_size = explicit_content_size; @@ -1350,14 +1365,13 @@ impl BlockFlow { inline_size_of_preceding_right_floats; } - // Handle tables. - if let Some(ref table_info) = table_info { - table_info.propagate_to_child(kid, - i, - content_inline_size, - containing_block_mode, - &mut inline_start_margin_edge); - } + // Call the callback to propagate extra inline size information down to the child. This + // is currently used for tables. + callback(kid, + i, + content_inline_size, + containing_block_mode, + &mut inline_start_margin_edge); // Per CSS 2.1 § 16.3.1, text alignment propagates to all children in flow. // @@ -1575,7 +1589,7 @@ impl Flow for BlockFlow { // Our inline-size was set to the inline-size of the containing block by the flow's parent. // Now compute the real value. - self.propagate_and_compute_used_inline_size(layout_context); + self.propagate_and_compute_used_inline_size(layout_context, border_collapse::T::separate); // Formatting contexts are never impacted by floats. match self.formatting_context_type() { @@ -1617,7 +1631,7 @@ impl Flow for BlockFlow { inline_start_content_edge, inline_end_content_edge, content_inline_size, - None); + |_, _, _, _, _| {}); } fn place_float_if_applicable<'a>(&mut self, _: &'a LayoutContext<'a>) { @@ -1906,7 +1920,9 @@ impl Flow for BlockFlow { } fn build_display_list(&mut self, layout_context: &LayoutContext) { - self.build_display_list_for_block(box DisplayList::new(), layout_context); + self.build_display_list_for_block(box DisplayList::new(), + layout_context, + BorderPaintingMode::Separate); if opts::get().validate_display_list_geometry { self.base.validate_display_list_geometry(); } @@ -2031,14 +2047,15 @@ pub trait ISizeAndMarginsComputer { fn compute_inline_size_constraint_inputs(&self, block: &mut BlockFlow, parent_flow_inline_size: Au, - layout_context: &LayoutContext) + layout_context: &LayoutContext, + border_collapse: border_collapse::T) -> ISizeConstraintInput { let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, layout_context); block.fragment.compute_block_direction_margins(containing_block_inline_size); block.fragment.compute_inline_direction_margins(containing_block_inline_size); - block.fragment.compute_border_and_padding(containing_block_inline_size); + block.fragment.compute_border_and_padding(containing_block_inline_size, border_collapse); let mut computed_inline_size = self.initial_computed_inline_size(block, parent_flow_inline_size, @@ -2165,10 +2182,12 @@ pub trait ISizeAndMarginsComputer { fn compute_used_inline_size(&self, block: &mut BlockFlow, layout_context: &LayoutContext, - parent_flow_inline_size: Au) { + parent_flow_inline_size: Au, + border_collapse: border_collapse::T) { let mut input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, - layout_context); + layout_context, + border_collapse); let containing_block_inline_size = self.containing_block_inline_size(block, parent_flow_inline_size, layout_context); @@ -2617,7 +2636,9 @@ impl ISizeAndMarginsComputer for BlockReplaced { -> ISizeConstraintSolution { match input.computed_inline_size { MaybeAuto::Specified(_) => {}, - MaybeAuto::Auto => panic!("BlockReplaced: inline_size should have been computed by now") + MaybeAuto::Auto => { + panic!("BlockReplaced: inline_size should have been computed by now") + } }; self.solve_block_inline_size_constraints(block, input) } diff --git a/components/layout/display_list_builder.rs b/components/layout/display_list_builder.rs index 153afb61ee0..9061b845c9a 100644 --- a/components/layout/display_list_builder.rs +++ b/components/layout/display_list_builder.rs @@ -20,6 +20,7 @@ use fragment::{ScannedTextFragmentInfo, SpecificFragmentInfo}; use inline::InlineFlow; use list_item::ListItemFlow; use model::{self, MaybeAuto, ToGfxMatrix}; +use table_cell::CollapsedBordersForCell; use geom::{Matrix2D, Point2D, Rect, Size2D, SideOffsets2D}; use gfx::color; @@ -31,31 +32,31 @@ use gfx::display_list::{GradientStop, ImageDisplayItem, LineDisplayItem}; use gfx::display_list::{OpaqueNode, SolidColorDisplayItem}; use gfx::display_list::{StackingContext, TextDisplayItem, TextOrientation}; use gfx::paint_task::{PaintLayer, THREAD_TINT_COLORS}; -use png::{self, PixelsByColorType}; use msg::compositor_msg::ScrollPolicy; -use msg::constellation_msg::Msg as ConstellationMsg; use msg::constellation_msg::ConstellationChan; -use util::cursor::Cursor; -use util::geometry::{self, Au, ZERO_POINT, to_px, to_frac_px}; -use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; -use util::opts; +use msg::constellation_msg::Msg as ConstellationMsg; +use png::{self, PixelsByColorType}; use std::cmp; use std::default::Default; use std::iter::repeat; use std::num::Float; -use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection}; -use style::values::computed::{Image, LinearGradient, LengthOrPercentage, LengthOrPercentageOrAuto}; -use style::values::RGBA; +use std::num::ToPrimitive; +use std::sync::Arc; +use std::sync::mpsc::channel; use style::computed_values::filter::Filter; use style::computed_values::transform::ComputedMatrix; use style::computed_values::{background_attachment, background_repeat, background_size}; use style::computed_values::{border_style, image_rendering, overflow_x, position, visibility}; -use style::properties::style_structs::Border; use style::properties::ComputedValues; -use std::num::ToPrimitive; -use std::sync::Arc; -use std::sync::mpsc::channel; +use style::properties::style_structs::Border; +use style::values::RGBA; +use style::values::computed::{Image, LinearGradient, LengthOrPercentage, LengthOrPercentageOrAuto}; +use style::values::specified::{AngleOrCorner, HorizontalDirection, VerticalDirection}; use url::Url; +use util::cursor::Cursor; +use util::geometry::{self, Au, ZERO_POINT, to_px, to_frac_px}; +use util::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode}; +use util::opts; /// The results of display list building for a single flow. pub enum DisplayListBuildingResult { @@ -123,12 +124,14 @@ pub trait FragmentDisplayListBuilding { /// Adds the display items necessary to paint the borders of this fragment to a display list if /// necessary. - fn build_display_list_for_borders_if_applicable(&self, - style: &ComputedValues, - display_list: &mut DisplayList, - abs_bounds: &Rect, - level: StackingLevel, - clip: &ClippingRegion); + fn build_display_list_for_borders_if_applicable( + &self, + style: &ComputedValues, + border_painting_mode: BorderPaintingMode, + display_list: &mut DisplayList, + bounds: &Rect, + level: StackingLevel, + clip: &ClippingRegion); /// Adds the display items necessary to paint the outline of this fragment to the display list /// if necessary. @@ -181,6 +184,7 @@ pub trait FragmentDisplayListBuilding { stacking_relative_flow_origin: &Point2D, relative_containing_block_size: &LogicalSize, relative_containing_block_mode: WritingMode, + border_painting_mode: BorderPaintingMode, background_and_border_level: BackgroundAndBorderLevel, clip: &ClippingRegion); @@ -631,37 +635,67 @@ impl FragmentDisplayListBuilding for Fragment { } } - fn build_display_list_for_borders_if_applicable(&self, - style: &ComputedValues, - display_list: &mut DisplayList, - abs_bounds: &Rect, - level: StackingLevel, - clip: &ClippingRegion) { - let border = style.logical_border_width(); + fn build_display_list_for_borders_if_applicable( + &self, + style: &ComputedValues, + border_painting_mode: BorderPaintingMode, + display_list: &mut DisplayList, + bounds: &Rect, + level: StackingLevel, + clip: &ClippingRegion) { + let mut border = style.logical_border_width(); + + match border_painting_mode { + BorderPaintingMode::Separate => {} + BorderPaintingMode::Collapse(collapsed_borders) => { + collapsed_borders.adjust_border_widths_for_painting(&mut border) + } + BorderPaintingMode::Hidden => return, + } if border.is_zero() { return } - let top_color = style.resolve_color(style.get_border().border_top_color); - let right_color = style.resolve_color(style.get_border().border_right_color); - let bottom_color = style.resolve_color(style.get_border().border_bottom_color); - let left_color = style.resolve_color(style.get_border().border_left_color); + let border_style_struct = style.get_border(); + let mut colors = SideOffsets2D::new(border_style_struct.border_top_color, + border_style_struct.border_right_color, + border_style_struct.border_bottom_color, + border_style_struct.border_left_color); + let mut border_style = SideOffsets2D::new(border_style_struct.border_top_style, + border_style_struct.border_right_style, + border_style_struct.border_bottom_style, + border_style_struct.border_left_style); + if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { + collapsed_borders.adjust_border_colors_and_styles_for_painting(&mut colors, + &mut border_style, + style.writing_mode); + } + + let colors = SideOffsets2D::new(style.resolve_color(colors.top), + style.resolve_color(colors.right), + style.resolve_color(colors.bottom), + style.resolve_color(colors.left)); + + // If this border collapses, then we draw outside the boundaries we were given. + let mut bounds = *bounds; + if let BorderPaintingMode::Collapse(collapsed_borders) = border_painting_mode { + collapsed_borders.adjust_border_bounds_for_painting(&mut bounds, style.writing_mode) + } // Append the border to the display list. display_list.push(DisplayItem::BorderClass(box BorderDisplayItem { - base: BaseDisplayItem::new(*abs_bounds, - DisplayItemMetadata::new(self.node, style, Cursor::DefaultCursor), + base: BaseDisplayItem::new(bounds, + DisplayItemMetadata::new(self.node, + style, + Cursor::DefaultCursor), (*clip).clone()), border_widths: border.to_physical(style.writing_mode), - color: SideOffsets2D::new(top_color.to_gfx_color(), - right_color.to_gfx_color(), - bottom_color.to_gfx_color(), - left_color.to_gfx_color()), - style: SideOffsets2D::new(style.get_border().border_top_style, - style.get_border().border_right_style, - style.get_border().border_bottom_style, - style.get_border().border_left_style), - radius: build_border_radius(abs_bounds, style.get_border()), + color: SideOffsets2D::new(colors.top.to_gfx_color(), + colors.right.to_gfx_color(), + colors.bottom.to_gfx_color(), + colors.left.to_gfx_color()), + style: border_style, + radius: build_border_radius(&bounds, border_style_struct), }), level); } @@ -693,7 +727,9 @@ impl FragmentDisplayListBuilding for Fragment { let color = style.resolve_color(style.get_outline().outline_color).to_gfx_color(); display_list.outlines.push_back(DisplayItem::BorderClass(box BorderDisplayItem { base: BaseDisplayItem::new(bounds, - DisplayItemMetadata::new(self.node, style, Cursor::DefaultCursor), + DisplayItemMetadata::new(self.node, + style, + Cursor::DefaultCursor), (*clip).clone()), border_widths: SideOffsets2D::new_all_same(width), color: SideOffsets2D::new_all_same(color), @@ -715,7 +751,9 @@ impl FragmentDisplayListBuilding for Fragment { // Compute the text fragment bounds and draw a border surrounding them. display_list.content.push_back(DisplayItem::BorderClass(box BorderDisplayItem { base: BaseDisplayItem::new(*stacking_relative_border_box, - DisplayItemMetadata::new(self.node, style, Cursor::DefaultCursor), + DisplayItemMetadata::new(self.node, + style, + Cursor::DefaultCursor), (*clip).clone()), border_widths: SideOffsets2D::new_all_same(Au::from_px(1)), color: SideOffsets2D::new_all_same(color::rgb(0, 0, 200)), @@ -785,6 +823,7 @@ impl FragmentDisplayListBuilding for Fragment { stacking_relative_flow_origin: &Point2D, relative_containing_block_size: &LogicalSize, relative_containing_block_mode: WritingMode, + border_painting_mode: BorderPaintingMode, background_and_border_level: BackgroundAndBorderLevel, clip: &ClippingRegion) { if self.style().get_inheritedbox().visibility != visibility::T::visible { @@ -845,6 +884,7 @@ impl FragmentDisplayListBuilding for Fragment { &clip); self.build_display_list_for_borders_if_applicable( &**style, + border_painting_mode, display_list, &stacking_relative_border_box, level, @@ -870,6 +910,7 @@ impl FragmentDisplayListBuilding for Fragment { &stacking_relative_border_box, &clip); self.build_display_list_for_borders_if_applicable(&*self.style, + border_painting_mode, display_list, &stacking_relative_border_box, level, @@ -1262,26 +1303,33 @@ pub trait BlockFlowDisplayListBuilding { fn build_display_list_for_block_base(&mut self, display_list: &mut DisplayList, layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode, background_border_level: BackgroundAndBorderLevel); fn build_display_list_for_static_block(&mut self, display_list: Box, layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode, background_border_level: BackgroundAndBorderLevel); - fn build_display_list_for_absolutely_positioned_block(&mut self, - display_list: Box, - layout_context: &LayoutContext); + fn build_display_list_for_absolutely_positioned_block( + &mut self, + display_list: Box, + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode); fn build_display_list_for_floating_block(&mut self, display_list: Box, - layout_context: &LayoutContext); + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode); fn build_display_list_for_block(&mut self, display_list: Box, - layout_context: &LayoutContext); + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode); } impl BlockFlowDisplayListBuilding for BlockFlow { fn build_display_list_for_block_base(&mut self, display_list: &mut DisplayList, layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode, background_border_level: BackgroundAndBorderLevel) { // Add the box that starts the block context. let clip = if self.fragment.establishes_stacking_context() { @@ -1289,17 +1337,15 @@ impl BlockFlowDisplayListBuilding for BlockFlow { } else { self.base.clip.clone() }; - self.fragment.build_display_list(display_list, - layout_context, - &self.base.stacking_relative_position, - &self.base - .absolute_position_info - .relative_containing_block_size, - self.base - .absolute_position_info - .relative_containing_block_mode, - background_border_level, - &clip); + self.fragment + .build_display_list(display_list, + layout_context, + &self.base.stacking_relative_position, + &self.base.absolute_position_info.relative_containing_block_size, + self.base.absolute_position_info.relative_containing_block_mode, + border_painting_mode, + background_border_level, + &clip); // Add children. for kid in self.base.children.iter_mut() { @@ -1312,9 +1358,11 @@ impl BlockFlowDisplayListBuilding for BlockFlow { fn build_display_list_for_static_block(&mut self, mut display_list: Box, layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode, background_border_level: BackgroundAndBorderLevel) { self.build_display_list_for_block_base(&mut *display_list, layout_context, + border_painting_mode, background_border_level); self.base.display_list_building_result = if self.fragment.establishes_stacking_context() { @@ -1324,11 +1372,14 @@ impl BlockFlowDisplayListBuilding for BlockFlow { } } - fn build_display_list_for_absolutely_positioned_block(&mut self, - mut display_list: Box, - layout_context: &LayoutContext) { + fn build_display_list_for_absolutely_positioned_block( + &mut self, + mut display_list: Box, + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode) { self.build_display_list_for_block_base(&mut *display_list, layout_context, + border_painting_mode, BackgroundAndBorderLevel::RootOfStackingContext); if !self.base.absolute_position_info.layers_needed_for_positioned_flows && @@ -1359,9 +1410,11 @@ impl BlockFlowDisplayListBuilding for BlockFlow { fn build_display_list_for_floating_block(&mut self, mut display_list: Box, - layout_context: &LayoutContext) { + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode) { self.build_display_list_for_block_base(&mut *display_list, layout_context, + border_painting_mode, BackgroundAndBorderLevel::RootOfStackingContext); display_list.form_float_pseudo_stacking_context(); @@ -1375,16 +1428,22 @@ impl BlockFlowDisplayListBuilding for BlockFlow { fn build_display_list_for_block(&mut self, display_list: Box, - layout_context: &LayoutContext) { + layout_context: &LayoutContext, + border_painting_mode: BorderPaintingMode) { if self.base.flags.is_float() { // TODO(#2009, pcwalton): This is a pseudo-stacking context. We need to merge `z-index: // auto` kids into the parent stacking context, when that is supported. - self.build_display_list_for_floating_block(display_list, layout_context); + self.build_display_list_for_floating_block(display_list, + layout_context, + border_painting_mode); } else if self.base.flags.contains(IS_ABSOLUTELY_POSITIONED) { - self.build_display_list_for_absolutely_positioned_block(display_list, layout_context); + self.build_display_list_for_absolutely_positioned_block(display_list, + layout_context, + border_painting_mode); } else { self.build_display_list_for_static_block(display_list, layout_context, + border_painting_mode, BackgroundAndBorderLevel::Block); } } @@ -1412,6 +1471,7 @@ impl InlineFlowDisplayListBuilding for InlineFlow { self.base .absolute_position_info .relative_containing_block_mode, + BorderPaintingMode::Separate, BackgroundAndBorderLevel::Content, &self.base.clip); @@ -1474,12 +1534,15 @@ impl ListItemFlowDisplayListBuilding for ListItemFlow { .base .absolute_position_info .relative_containing_block_mode, + BorderPaintingMode::Separate, BackgroundAndBorderLevel::Content, &self.block_flow.base.clip); } // Draw the rest of the block. - self.block_flow.build_display_list_for_block(display_list, layout_context) + self.block_flow.build_display_list_for_block(display_list, + layout_context, + BorderPaintingMode::Separate) } } @@ -1539,7 +1602,9 @@ fn fmin(a: f32, b: f32) -> f32 { fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32 { match position { - LengthOrPercentage::Length(Au(length)) => fmin(1.0, (length as f32) / (total_length as f32)), + LengthOrPercentage::Length(Au(length)) => { + fmin(1.0, (length as f32) / (total_length as f32)) + } LengthOrPercentage::Percentage(percentage) => percentage as f32, } } @@ -1610,3 +1675,15 @@ impl ToGfxColor for RGBA { color::rgba(self.red, self.green, self.blue, self.alpha) } } + +/// Describes how to paint the borders. +#[derive(Copy, Clone)] +pub enum BorderPaintingMode<'a> { + /// Paint borders separately (`border-collapse: separate`). + Separate, + /// Paint collapsed borders. + Collapse(&'a CollapsedBordersForCell), + /// Paint no borders. + Hidden, +} + diff --git a/components/layout/flow.rs b/components/layout/flow.rs index cb0b73b68e7..6ebf8530c90 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -48,14 +48,12 @@ use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect, Size2D}; use gfx::display_list::ClippingRegion; -use rustc_serialize::{Encoder, Encodable}; -use msg::constellation_msg::ConstellationChan; use msg::compositor_msg::LayerId; -use util::geometry::{Au, ZERO_RECT}; -use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; -use std::mem; +use msg::constellation_msg::ConstellationChan; +use rustc_serialize::{Encoder, Encodable}; use std::fmt; use std::iter::Zip; +use std::mem; use std::num::FromPrimitive; use std::raw; use std::slice::IterMut; @@ -64,6 +62,8 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use style::computed_values::{clear, empty_cells, float, position, text_align}; use style::properties::ComputedValues; use style::values::computed::LengthOrPercentageOrAuto; +use util::geometry::{Au, ZERO_RECT}; +use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; /// Virtual methods that make up a float context. /// @@ -358,7 +358,7 @@ pub fn child_iter<'a>(flow: &'a mut Flow) -> MutFlowListIterator<'a> { pub trait ImmutableFlowUtils { // Convenience functions - /// Returns true if this flow is a block or a float flow. + /// Returns true if this flow is a block flow or subclass thereof. fn is_block_like(self) -> bool; /// Returns true if this flow is a table flow. diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index 69fc424d827..16ff6a6d307 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -40,8 +40,8 @@ use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; use string_cache::Atom; use style::computed_values::content::ContentItem; -use style::computed_values::{clear, mix_blend_mode, overflow_wrap, position, text_align}; -use style::computed_values::{text_decoration, white_space, word_break}; +use style::computed_values::{border_collapse, clear, mix_blend_mode, overflow_wrap, position}; +use style::computed_values::{text_align, text_decoration, white_space, word_break}; use style::node::{TElement, TNode}; use style::properties::{ComputedValues, cascade_anonymous, make_border}; use style::values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto}; @@ -93,6 +93,7 @@ pub struct Fragment { /// border, but not margin. /// /// NB: This does not account for relative positioning. + /// NB: Collapsed borders are not included in this. pub border_box: LogicalRect, /// The sum of border and padding; i.e. the distance from the edge of the border box to the @@ -387,8 +388,16 @@ impl ReplacedImageFragmentInfo { for_node: untrusted_node, computed_inline_size: None, computed_block_size: None, - dom_inline_size: if is_vertical { dom_height } else { dom_width }, - dom_block_size: if is_vertical { dom_width } else { dom_height }, + dom_inline_size: if is_vertical { + dom_height + } else { + dom_width + }, + dom_block_size: if is_vertical { + dom_width + } else { + dom_height + }, writing_mode_is_vertical: is_vertical, } } @@ -410,16 +419,10 @@ impl ReplacedImageFragmentInfo { pub fn style_length(style_length: LengthOrPercentageOrAuto, dom_length: Option, container_inline_size: Au) -> MaybeAuto { - match (MaybeAuto::from_style(style_length,container_inline_size),dom_length) { - (MaybeAuto::Specified(length),_) => { - MaybeAuto::Specified(length) - }, - (MaybeAuto::Auto,Some(length)) => { - MaybeAuto::Specified(length) - }, - (MaybeAuto::Auto,None) => { - MaybeAuto::Auto - } + match (MaybeAuto::from_style(style_length,container_inline_size), dom_length) { + (MaybeAuto::Specified(length), _) => MaybeAuto::Specified(length), + (MaybeAuto::Auto, Some(length)) => MaybeAuto::Specified(length), + (MaybeAuto::Auto, None) => MaybeAuto::Auto, } } @@ -520,8 +523,8 @@ impl ReplacedImageFragmentInfo { } } -/// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the size -/// of this iframe can be communicated via the constellation to the iframe's own layout task. +/// A fragment that represents an inline frame (iframe). This stores the pipeline ID so that the +/// size of this iframe can be communicated via the constellation to the iframe's own layout task. #[derive(Clone)] pub struct IframeFragmentInfo { /// The pipeline ID of this iframe. @@ -878,20 +881,34 @@ impl Fragment { SpecificFragmentInfo::InlineBlock(_) => { QuantitiesIncludedInIntrinsicInlineSizes::all() } - SpecificFragmentInfo::Table | - SpecificFragmentInfo::TableCell => { - INTRINSIC_INLINE_SIZE_INCLUDES_PADDING | - INTRINSIC_INLINE_SIZE_INCLUDES_BORDER | - INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED + SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell => { + let base_quantities = INTRINSIC_INLINE_SIZE_INCLUDES_PADDING | + INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; + if self.style.get_inheritedtable().border_collapse == + border_collapse::T::separate { + base_quantities | INTRINSIC_INLINE_SIZE_INCLUDES_BORDER + } else { + base_quantities + } } SpecificFragmentInfo::TableWrapper => { - INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS | - INTRINSIC_INLINE_SIZE_INCLUDES_BORDER | - INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED + let base_quantities = INTRINSIC_INLINE_SIZE_INCLUDES_MARGINS | + INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; + if self.style.get_inheritedtable().border_collapse == + border_collapse::T::separate { + base_quantities | INTRINSIC_INLINE_SIZE_INCLUDES_BORDER + } else { + base_quantities + } } SpecificFragmentInfo::TableRow => { - INTRINSIC_INLINE_SIZE_INCLUDES_BORDER | - INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED + let base_quantities = INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED; + if self.style.get_inheritedtable().border_collapse == + border_collapse::T::separate { + base_quantities | INTRINSIC_INLINE_SIZE_INCLUDES_BORDER + } else { + base_quantities + } } SpecificFragmentInfo::ScannedText(_) | SpecificFragmentInfo::TableColumn(_) | @@ -946,12 +963,13 @@ impl Fragment { fn style_specified_intrinsic_inline_size(&self) -> IntrinsicISizesContribution { let flags = self.quantities_included_in_intrinsic_inline_size(); let style = self.style(); - let (min_inline_size, specified) = if flags.contains(INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED) { - (model::specified(style.min_inline_size(), Au(0)), - MaybeAuto::from_style(style.content_inline_size(), Au(0)).specified_or_zero()) - } else { - (Au(0), Au(0)) - }; + let (min_inline_size, specified) = + if flags.contains(INTRINSIC_INLINE_SIZE_INCLUDES_SPECIFIED) { + (model::specified(style.min_inline_size(), Au(0)), + MaybeAuto::from_style(style.content_inline_size(), Au(0)).specified_or_zero()) + } else { + (Au(0), Au(0)) + }; // FIXME(#2261, pcwalton): This won't work well for inlines: is this OK? let surrounding_inline_size = self.surrounding_intrinsic_inline_size(); @@ -974,7 +992,7 @@ impl Fragment { /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this /// can be expensive to compute, so if possible use the `border_padding` field instead. #[inline] - pub fn border_width(&self) -> LogicalMargin { + fn border_width(&self) -> LogicalMargin { let style_border_width = match self.specific { SpecificFragmentInfo::ScannedText(_) => LogicalMargin::zero(self.style.writing_mode), _ => self.style().logical_border_width(), @@ -983,8 +1001,10 @@ impl Fragment { match self.inline_context { None => style_border_width, Some(ref inline_fragment_context) => { - inline_fragment_context.styles.iter().fold(style_border_width, - |acc, style| acc + style.logical_border_width()) + inline_fragment_context.styles + .iter() + .fold(style_border_width, + |acc, style| acc + style.logical_border_width()) } } } @@ -996,7 +1016,10 @@ impl Fragment { /// (for example, via constraint solving for blocks). pub fn compute_inline_direction_margins(&mut self, containing_block_inline_size: Au) { match self.specific { - SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableColumn(_) => { + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableColumn(_) => { self.margin.inline_start = Au(0); self.margin.inline_end = Au(0) } @@ -1019,7 +1042,10 @@ impl Fragment { /// (for example, via constraint solving for absolutely-positioned flows). pub fn compute_block_direction_margins(&mut self, containing_block_inline_size: Au) { match self.specific { - SpecificFragmentInfo::Table | SpecificFragmentInfo::TableCell | SpecificFragmentInfo::TableRow | SpecificFragmentInfo::TableColumn(_) => { + SpecificFragmentInfo::Table | + SpecificFragmentInfo::TableCell | + SpecificFragmentInfo::TableRow | + SpecificFragmentInfo::TableColumn(_) => { self.margin.block_start = Au(0); self.margin.block_end = Au(0) } @@ -1040,9 +1066,17 @@ impl Fragment { /// Computes the border and padding in both inline and block directions from the containing /// block inline-size and the style. After this call, the `border_padding` field will be /// correct. - pub fn compute_border_and_padding(&mut self, containing_block_inline_size: Au) { + /// + /// TODO(pcwalton): Remove `border_collapse`; we can figure it out from our style and specific + /// fragment info. + pub fn compute_border_and_padding(&mut self, + containing_block_inline_size: Au, + border_collapse: border_collapse::T) { // Compute border. - let border = self.border_width(); + let border = match border_collapse { + border_collapse::T::separate => self.border_width(), + border_collapse::T::collapse => LogicalMargin::zero(self.style.writing_mode), + }; // Compute padding. let padding = match self.specific { @@ -1050,21 +1084,27 @@ impl Fragment { SpecificFragmentInfo::TableWrapper => LogicalMargin::zero(self.style.writing_mode), _ => { let style_padding = match self.specific { - SpecificFragmentInfo::ScannedText(_) => LogicalMargin::zero(self.style.writing_mode), + SpecificFragmentInfo::ScannedText(_) => { + LogicalMargin::zero(self.style.writing_mode) + } _ => model::padding_from_style(self.style(), containing_block_inline_size), }; match self.inline_context { None => style_padding, Some(ref inline_fragment_context) => { - inline_fragment_context.styles.iter().fold(style_padding, - |acc, style| acc + model::padding_from_style(&**style, Au(0))) + inline_fragment_context.styles + .iter() + .fold(style_padding, |acc, style| { + acc + model::padding_from_style(&**style, + Au(0)) + }) } } } }; - self.border_padding = border + padding; + self.border_padding = border + padding } // Return offset from original position because of `position: relative`. @@ -1073,14 +1113,18 @@ impl Fragment { -> LogicalSize { let offsets = style.logical_position(); let offset_i = if offsets.inline_start != LengthOrPercentageOrAuto::Auto { - MaybeAuto::from_style(offsets.inline_start, container_size.inline).specified_or_zero() + MaybeAuto::from_style(offsets.inline_start, + container_size.inline).specified_or_zero() } else { - -MaybeAuto::from_style(offsets.inline_end, container_size.inline).specified_or_zero() + -MaybeAuto::from_style(offsets.inline_end, + container_size.inline).specified_or_zero() }; let offset_b = if offsets.block_start != LengthOrPercentageOrAuto::Auto { - MaybeAuto::from_style(offsets.block_start, container_size.inline).specified_or_zero() + MaybeAuto::from_style(offsets.block_start, + container_size.inline).specified_or_zero() } else { - -MaybeAuto::from_style(offsets.block_end, container_size.inline).specified_or_zero() + -MaybeAuto::from_style(offsets.block_end, + container_size.inline).specified_or_zero() }; LogicalSize::new(style.writing_mode, offset_i, offset_b) } @@ -1254,7 +1298,7 @@ impl Fragment { /// TODO: What exactly does this function return? Why is it Au(0) for - /// SpecificFragmentInfo::Generic? + /// `SpecificFragmentInfo::Generic`? pub fn content_inline_size(&self) -> Au { match self.specific { SpecificFragmentInfo::Generic | diff --git a/components/layout/inline.rs b/components/layout/inline.rs index 316022eec6c..e4a91bec006 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -15,7 +15,7 @@ use layout_debug; use model::IntrinsicISizesContribution; use text; -use collections::{VecDeque}; +use collections::VecDeque; use geom::{Point2D, Rect}; use gfx::font::FontMetrics; use gfx::font_context::FontContext; @@ -28,8 +28,8 @@ use std::num::ToPrimitive; use std::ops::{Add, Sub, Mul, Div, Rem, Neg, Shl, Shr, Not, BitOr, BitAnd, BitXor}; use std::sync::Arc; use std::u16; -use style::computed_values::{display, overflow_x, text_align, text_justify, text_overflow}; -use style::computed_values::{vertical_align, white_space}; +use style::computed_values::{border_collapse, display, overflow_x, text_align, text_justify}; +use style::computed_values::{text_overflow, vertical_align, white_space}; use style::properties::ComputedValues; use util::geometry::{Au, MAX_AU, ZERO_RECT}; use util::logical_geometry::{LogicalRect, LogicalSize, WritingMode}; @@ -1197,7 +1197,7 @@ impl Flow for InlineFlow { { let this = &mut *self; for fragment in this.fragments.fragments.iter_mut() { - fragment.compute_border_and_padding(inline_size); + fragment.compute_border_and_padding(inline_size, border_collapse::T::separate); fragment.compute_block_direction_margins(inline_size); fragment.compute_inline_direction_margins(inline_size); fragment.assign_replaced_inline_size_if_necessary(inline_size); diff --git a/components/layout/table.rs b/components/layout/table.rs index 02cae3c81c9..3731f4c524d 100644 --- a/components/layout/table.rs +++ b/components/layout/table.rs @@ -9,6 +9,7 @@ use block::{self, BlockFlow, CandidateBSizeIterator, ISizeAndMarginsComputer}; use block::{ISizeConstraintInput, ISizeConstraintSolution}; use context::LayoutContext; +use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode}; use floats::FloatKind; use flow::{self, Flow, FlowClass, IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; use flow::{ImmutableFlowUtils}; @@ -16,20 +17,23 @@ use fragment::{Fragment, FragmentBorderBoxIterator}; use incremental::{REFLOW, REFLOW_OUT_OF_FLOW}; use layout_debug; use model::{IntrinsicISizes, IntrinsicISizesContribution, MaybeAuto}; -use table_row::CellIntrinsicInlineSize; +use table_row::{self, CellIntrinsicInlineSize, CollapsedBorder, CollapsedBorderProvenance}; +use table_row::{TableRowFlow}; use table_wrapper::TableLayout; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect}; -use std::cmp::max; +use gfx::display_list::DisplayList; +use std::cmp; use std::fmt; +use std::iter; use std::sync::Arc; use style::computed_values::{border_collapse, border_spacing, table_layout}; use style::properties::ComputedValues; use style::values::CSSFloat; use style::values::computed::LengthOrPercentageOrAuto; use util::geometry::Au; -use util::logical_geometry::{LogicalRect, WritingMode}; +use util::logical_geometry::LogicalRect; /// 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, @@ -42,10 +46,18 @@ pub struct TableFlow { /// intrinsic inline-size bubbling. pub column_intrinsic_inline_sizes: Vec, - /// Information about the actual inline-sizes of each column, computed top-down during actual + /// Information about the actual inline sizes of each column, computed top-down during actual /// inline-size bubbling. pub column_computed_inline_sizes: Vec, + /// 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, + + /// 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, + /// Table-layout property pub table_layout: TableLayout, } @@ -65,6 +77,8 @@ impl 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 } } @@ -84,6 +98,8 @@ impl 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 } } @@ -109,10 +125,10 @@ impl TableFlow { } else { let column_size = &child_cell_inline_size.column_size; *parent_sizes = ColumnIntrinsicInlineSize { - minimum_length: max(parent_sizes.minimum_length, - column_size.minimum_length), + minimum_length: cmp::max(parent_sizes.minimum_length, + column_size.minimum_length), percentage: parent_sizes.greatest_percentage(column_size), - preferred: max(parent_sizes.preferred, column_size.preferred), + preferred: cmp::max(parent_sizes.preferred, column_size.preferred), constrained: parent_sizes.constrained || column_size.constrained, } } @@ -146,7 +162,7 @@ impl TableFlow { fn update_column_inline_sizes_for_row(child: &mut Flow, column_inline_sizes: &mut Vec, computation: &mut IntrinsicISizesContribution, - did_first_row: &mut bool, + first_row: bool, table_layout: TableLayout) { // Read column inline-sizes from the table-row, and assign inline-size=0 for the columns // not defined in the column group. @@ -159,8 +175,7 @@ impl TableFlow { // Fixed table layout only looks at the first row. // // FIXME(pcwalton): This is really inefficient. We should stop after the first row! - if !*did_first_row { - *did_first_row = true; + if first_row { for cell_inline_size in row.cell_intrinsic_inline_sizes.iter() { column_inline_sizes.push(cell_inline_size.column_size); } @@ -175,7 +190,7 @@ impl TableFlow { } /// Returns the effective spacing per cell, taking the value of `border-collapse` into account. - fn spacing(&self) -> border_spacing::T { + 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, @@ -206,6 +221,10 @@ impl Flow for TableFlow { &mut self.block_flow } + fn as_immutable_block(&self) -> &BlockFlow { + &self.block_flow + } + fn column_intrinsic_inline_sizes<'a>(&'a mut self) -> &'a mut Vec { &mut self.column_intrinsic_inline_sizes } @@ -224,50 +243,165 @@ impl Flow for TableFlow { // Don't use `compute_intrinsic_inline_sizes` here because that will count padding as // part of the table, which we don't want to do—it belongs to the table wrapper instead. + + 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 did_first_row = false; - for kid in self.block_flow.base.child_iter() { - debug_assert!(kid.is_proper_table_child()); - if kid.is_table_colgroup() { - for specified_inline_size in kid.as_table_colgroup().inline_sizes.iter() { - self.column_intrinsic_inline_sizes.push(ColumnIntrinsicInlineSize { - minimum_length: match *specified_inline_size { - LengthOrPercentageOrAuto::Auto | LengthOrPercentageOrAuto::Percentage(_) => Au(0), - LengthOrPercentageOrAuto::Length(length) => length, - }, - percentage: match *specified_inline_size { - LengthOrPercentageOrAuto::Auto | LengthOrPercentageOrAuto::Length(_) => 0.0, - LengthOrPercentageOrAuto::Percentage(percentage) => percentage, - }, - preferred: Au(0), - constrained: false, - }) - } - } else if kid.is_table_rowgroup() { - for grandkid in flow::mut_base(kid).child_iter() { + let mut previous_collapsed_block_end_borders = if collapsing_borders { + PreviousBlockCollapsedBorders::FromTable(CollapsedBorder::block_start( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable)) + } else { + PreviousBlockCollapsedBorders::NotCollapsingBorders + }; + let mut first_row = true; + + { + let mut iterator = self.block_flow.base.child_iter().peekable(); + while let Some(kid) = iterator.next() { + let next_index_and_sibling = iterator.peek(); + let next_collapsed_borders_in_block_direction = if collapsing_borders { + match next_index_and_sibling { + Some(next_sibling) => { + if next_sibling.is_table_rowgroup() { + NextBlockCollapsedBorders::FromNextRow( + &next_sibling.as_immutable_table_rowgroup() + .preliminary_collapsed_borders + .block_start + .as_slice()) + } else { + NextBlockCollapsedBorders::FromNextRow( + &next_sibling.as_immutable_table_row() + .preliminary_collapsed_borders + .block_start + .as_slice()) + } + } + None => { + NextBlockCollapsedBorders::FromTable( + CollapsedBorder::block_end(&*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable)) + } + } + } else { + NextBlockCollapsedBorders::NotCollapsingBorders + }; + + if kid.is_table_colgroup() { + for specified_inline_size in kid.as_table_colgroup().inline_sizes.iter() { + self.column_intrinsic_inline_sizes.push(ColumnIntrinsicInlineSize { + minimum_length: match *specified_inline_size { + LengthOrPercentageOrAuto::Auto | + LengthOrPercentageOrAuto::Percentage(_) => Au(0), + LengthOrPercentageOrAuto::Length(length) => length, + }, + percentage: match *specified_inline_size { + LengthOrPercentageOrAuto::Auto | + LengthOrPercentageOrAuto::Length(_) => 0.0, + LengthOrPercentageOrAuto::Percentage(percentage) => percentage, + }, + preferred: Au(0), + constrained: false, + }) + } + } else if kid.is_table_row() { TableFlow::update_column_inline_sizes_for_row( - grandkid, - &mut self.column_intrinsic_inline_sizes, - &mut computation, - &mut did_first_row, - self.table_layout) + kid, + &mut self.column_intrinsic_inline_sizes, + &mut computation, + first_row, + self.table_layout); + if collapsing_borders { + perform_border_collapse_for_row( + kid.as_table_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( + kid.as_table_row().final_collapsed_borders.block_end.clone()) + } + first_row = false + } else if kid.is_table_rowgroup() { + let mut iterator = flow::mut_base(kid).child_iter().peekable(); + while let Some(grandkid) = iterator.next() { + let grandkid_next_sibling = iterator.peek(); + let next_collapsed_borders_in_block_direction = if collapsing_borders { + match grandkid_next_sibling { + Some(grandkid_next_sibling) => { + if grandkid_next_sibling.is_table_rowgroup() { + NextBlockCollapsedBorders::FromNextRow( + &grandkid_next_sibling.as_immutable_table_rowgroup() + .preliminary_collapsed_borders + .block_start + .as_slice()) + } else { + NextBlockCollapsedBorders::FromNextRow( + &grandkid_next_sibling.as_immutable_table_row() + .preliminary_collapsed_borders + .block_start + .as_slice()) + } + } + None => { + NextBlockCollapsedBorders::FromTable( + CollapsedBorder::block_end( + &*self.block_flow.fragment.style, + CollapsedBorderProvenance::FromTable)) + } + } + } else { + NextBlockCollapsedBorders::NotCollapsingBorders + }; + + TableFlow::update_column_inline_sizes_for_row( + grandkid, + &mut self.column_intrinsic_inline_sizes, + &mut computation, + first_row, + self.table_layout); + if collapsing_borders { + perform_border_collapse_for_row( + grandkid.as_table_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( + grandkid.as_table_row() + .final_collapsed_borders + .block_end + .clone()) + } + first_row = false + } } - } else if kid.is_table_row() { - TableFlow::update_column_inline_sizes_for_row( - kid, - &mut self.column_intrinsic_inline_sizes, - &mut computation, - &mut did_first_row, - self.table_layout) } } - let spacing = self.block_flow - .fragment - .style() - .get_inheritedtable() - .border_spacing - .horizontal * (self.column_intrinsic_inline_sizes.len() as i32 + 1); + let spacing = self.spacing().horizontal * + (self.column_intrinsic_inline_sizes.len() as i32 + 1); computation.surrounding_size = computation.surrounding_size + spacing; self.block_flow.base.intrinsic_inline_sizes = computation.finish() @@ -295,9 +429,11 @@ impl Flow for TableFlow { } let inline_size_computer = InternalTable; + let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse; inline_size_computer.compute_used_inline_size(&mut self.block_flow, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); 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; @@ -347,15 +483,41 @@ impl Flow for TableFlow { self.block_flow.base.flags.remove(IMPACTED_BY_LEFT_FLOATS); self.block_flow.base.flags.remove(IMPACTED_BY_RIGHT_FLOATS); - let info = ChildInlineSizeInfo { - column_computed_inline_sizes: &self.column_computed_inline_sizes, - spacing: spacing_per_cell, - }; + let column_computed_inline_sizes = self.column_computed_inline_sizes.as_slice(); + let collapsed_inline_direction_border_widths_for_table = + self.collapsed_inline_direction_border_widths_for_table.as_slice(); + let mut collapsed_block_direction_border_widths_for_table = + self.collapsed_block_direction_border_widths_for_table.iter().peekable(); self.block_flow.propagate_assigned_inline_size_to_children(layout_context, inline_start_content_edge, inline_end_content_edge, content_inline_size, - Some(info)); + |child_flow, + child_index, + content_inline_size, + writing_mode, + inline_start_margin_edge| { + table_row::propagate_column_inline_sizes_to_child( + child_flow, + child_index, + content_inline_size, + writing_mode, + column_computed_inline_sizes, + &spacing_per_cell, + &None, + inline_start_margin_edge); + if child_flow.is_table_row() { + let child_table_row = child_flow.as_table_row(); + child_table_row.populate_collapsed_border_spacing( + collapsed_inline_direction_border_widths_for_table.as_slice(), + &mut collapsed_block_direction_border_widths_for_table); + } else if child_flow.is_table_rowgroup() { + let child_table_rowgroup = child_flow.as_table_rowgroup(); + child_table_rowgroup.populate_collapsed_border_spacing( + collapsed_inline_direction_border_widths_for_table.as_slice(), + &mut collapsed_block_direction_border_widths_for_table); + } + }) } fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { @@ -381,7 +543,18 @@ impl Flow for TableFlow { } fn build_display_list(&mut self, layout_context: &LayoutContext) { - self.block_flow.build_display_list(layout_context); + 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(box DisplayList::new(), + layout_context, + border_painting_mode); } fn repair_style(&mut self, new_style: &Arc) { @@ -420,11 +593,13 @@ impl ISizeAndMarginsComputer for InternalTable { /// CSS Section 10.4: Minimum and Maximum inline-sizes fn compute_used_inline_size(&self, block: &mut BlockFlow, - ctx: &LayoutContext, - parent_flow_inline_size: Au) { + layout_context: &LayoutContext, + parent_flow_inline_size: Au, + border_collapse: border_collapse::T) { let input = self.compute_inline_size_constraint_inputs(block, parent_flow_inline_size, - ctx); + layout_context, + border_collapse); let solution = self.solve_inline_size_constraints(block, &input); self.set_inline_size_constraint_solutions(block, solution); @@ -437,87 +612,6 @@ impl ISizeAndMarginsComputer for InternalTable { } } -/// 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<'a>(&mut self, - layout_context: &'a LayoutContext<'a>, - block_direction_spacing: Au); -} - -impl TableLikeFlow for BlockFlow { - fn assign_block_size_for_table_like_flow<'a>(&mut self, - _: &'a LayoutContext<'a>, - block_direction_spacing: Au) { - if self.base.restyle_damage.contains(REFLOW) { - // 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; - - // At this point, `current_block_offset` is at the content edge of our box. Now iterate - // over children. - let mut layers_needed_for_descendants = false; - for kid in self.base.child_iter() { - // Mark flows for layerization if necessary to handle painting order correctly. - block::propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); - - // Account for spacing. - if kid.is_table_row() { - current_block_offset = current_block_offset + block_direction_spacing; - } - - // At this point, `current_block_offset` is at the border edge of the child. - flow::mut_base(kid).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 = flow::mut_base(kid); - 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); - loop { - match candidate_block_size_iterator.next() { - Some(candidate_block_size) => { - candidate_block_size_iterator.candidate_value = - match candidate_block_size { - MaybeAuto::Auto => block_size, - MaybeAuto::Specified(value) => value - } - } - None => break, - } - } - - // 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 + - block_direction_spacing; - 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; - } - - self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); - } -} - /// 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 @@ -556,7 +650,7 @@ impl ColumnIntrinsicInlineSize { /// Beware that this is generally only correct for fixed table layout. (Compare CSS 2.1 § /// 17.5.2.1 with the algorithm in INTRINSIC § 4.) pub fn minimum(&self, containing_block_inline_size: Au) -> Au { - max(self.minimum_length, containing_block_inline_size.scale_by(self.percentage)) + cmp::max(self.minimum_length, containing_block_inline_size.scale_by(self.percentage)) } /// Returns the higher of the two percentages specified in `self` and `other`. @@ -579,63 +673,206 @@ pub struct ColumnComputedInlineSize { pub size: Au, } -/// Inline-size information that we need to push down to table children. -pub struct ChildInlineSizeInfo<'a> { - /// The spacing of the table. - pub spacing: border_spacing::T, - /// The computed inline sizes for each column. - pub column_computed_inline_sizes: &'a [ColumnComputedInlineSize], +pub trait VecExt { + fn push_or_set(&mut self, index: usize, value: T); + fn push_or_mutate(&mut self, index: usize, zero: T) -> &mut T; } -impl<'a> ChildInlineSizeInfo<'a> { - /// Propagates information computed during inline size assignment to a child of a table, and - /// lays out that child in the inline direction. - pub fn propagate_to_child(&self, - kid: &mut Flow, - child_index: usize, - content_inline_size: Au, - writing_mode: WritingMode, - inline_start_margin_edge: &mut Au) { - // If the child is a table or a row, copy computed inline size information from its parent. - // - // FIXME(pcwalton): This seems inefficient. Reference count it instead? - let inline_size; - if kid.is_table() { - let table_kid = kid.as_table(); - table_kid.column_computed_inline_sizes = self.column_computed_inline_sizes.to_vec(); - inline_size = content_inline_size - } else if kid.is_table_rowgroup() { - let table_rowgroup_kid = kid.as_table_rowgroup(); - table_rowgroup_kid.column_computed_inline_sizes = - self.column_computed_inline_sizes.to_vec(); - table_rowgroup_kid.spacing = self.spacing; - inline_size = content_inline_size - } else if kid.is_table_row() { - let table_row_kid = kid.as_table_row(); - table_row_kid.column_computed_inline_sizes = - self.column_computed_inline_sizes.to_vec(); - table_row_kid.spacing = self.spacing; - inline_size = content_inline_size - } else if kid.is_table_cell() { - // Take spacing into account. - *inline_start_margin_edge = *inline_start_margin_edge + self.spacing.horizontal; - inline_size = self.column_computed_inline_sizes[child_index].size; +impl VecExt for Vec { + fn push_or_set(&mut self, index: usize, value: T) { + if index < self.len() { + self[index] = value } else { - // ISize of kid flow is our content inline-size. - inline_size = content_inline_size + debug_assert!(index == self.len()); + self.push(value) } + } - { - let kid_base = flow::mut_base(kid); - kid_base.position.start.i = *inline_start_margin_edge; - kid_base.block_container_inline_size = inline_size; - kid_base.block_container_writing_mode = writing_mode - } - - // Move over for the next table cell. - if kid.is_table_cell() { - *inline_start_margin_edge = *inline_start_margin_edge + inline_size + fn push_or_mutate(&mut self, index: usize, zero: T) -> &mut T { + if index >= self.len() { + debug_assert!(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, + block_spacing: &mut Vec) { + // Compute interior inline borders. + for (i, this_inline_border) in child_table_row.preliminary_collapsed_borders + .inline + .iter() + .enumerate() { + child_table_row.final_collapsed_borders.inline.push_or_set(i, *this_inline_border); + + let inline_spacing = inline_spacing.push_or_mutate(i, Au(0)); + *inline_spacing = cmp::max(*inline_spacing, this_inline_border.width) + } + + // Collapse edge interior borders with the table. + if let Some(ref mut first_inline_borders) = child_table_row.final_collapsed_borders + .inline + .get_mut(0) { + first_inline_borders.combine(&table_inline_borders.start) + } + if let Some(ref mut last_inline_borders) = child_table_row.final_collapsed_borders + .inline + .last_mut() { + last_inline_borders.combine(&table_inline_borders.end) + } + + // Compute block-start borders. + match previous_block_borders { + PreviousBlockCollapsedBorders::FromPreviousRow(previous_block_borders) => { + child_table_row.final_collapsed_borders.block_start = previous_block_borders + } + PreviousBlockCollapsedBorders::FromTable(collapsed_border) => { + child_table_row.final_collapsed_borders.block_start = + iter::repeat(collapsed_border).take(child_table_row.block_flow.base.children.len()) + .collect() + } + PreviousBlockCollapsedBorders::NotCollapsingBorders => {} + } + + // 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_mutate(i, *this_block_border); + match next_block_borders { + NextBlockCollapsedBorders::FromNextRow(next_block_borders) => { + next_block.combine(&next_block_borders[i]); + } + NextBlockCollapsedBorders::FromTable(ref next_block_borders) => { + next_block.combine(next_block_borders); + } + NextBlockCollapsedBorders::NotCollapsingBorders => {} + } + *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<'a>(&mut self, + layout_context: &'a LayoutContext<'a>, + block_direction_spacing: Au); +} + +impl TableLikeFlow for BlockFlow { + fn assign_block_size_for_table_like_flow<'a>(&mut self, + _: &'a LayoutContext<'a>, + block_direction_spacing: Au) { + debug_assert!(self.fragment.style.get_inheritedtable().border_collapse == + border_collapse::T::separate || block_direction_spacing == Au(0)); + + if self.base.restyle_damage.contains(REFLOW) { + // 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; + + // At this point, `current_block_offset` is at the content edge of our box. Now iterate + // over children. + let mut layers_needed_for_descendants = false; + for kid in self.base.child_iter() { + // Mark flows for layerization if necessary to handle painting order correctly. + block::propagate_layer_flag_from_child(&mut layers_needed_for_descendants, kid); + + // Account for spacing or collapsed borders. + if kid.is_table_row() { + let child_table_row = kid.as_table_row(); + current_block_offset = current_block_offset + + match self.fragment.style.get_inheritedtable().border_collapse { + border_collapse::T::separate => block_direction_spacing, + border_collapse::T::collapse => { + child_table_row.collapsed_border_spacing.block_start + } + } + } + + // At this point, `current_block_offset` is at the border edge of the child. + flow::mut_base(kid).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 = flow::mut_base(kid); + 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); + loop { + match candidate_block_size_iterator.next() { + Some(candidate_block_size) => { + candidate_block_size_iterator.candidate_value = + match candidate_block_size { + MaybeAuto::Auto => block_size, + MaybeAuto::Specified(value) => value + } + } + None => break, + } + } + + // 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 + + block_direction_spacing; + 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; + } + + self.base.restyle_damage.remove(REFLOW_OUT_OF_FLOW | REFLOW); + } +} + +/// Inline collapsed borders for the table itself. +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), + FromTable(CollapsedBorder), + NotCollapsingBorders, +} + +enum NextBlockCollapsedBorders<'a> { + FromNextRow(&'a [CollapsedBorder]), + FromTable(CollapsedBorder), + NotCollapsingBorders, +} + diff --git a/components/layout/table_caption.rs b/components/layout/table_caption.rs index 06617e8c198..26e63c9d1b4 100644 --- a/components/layout/table_caption.rs +++ b/components/layout/table_caption.rs @@ -46,6 +46,10 @@ impl Flow for TableCaptionFlow { &mut self.block_flow } + fn as_immutable_block(&self) -> &BlockFlow { + &self.block_flow + } + fn bubble_inline_sizes(&mut self) { self.block_flow.bubble_inline_sizes(); } @@ -105,3 +109,4 @@ impl fmt::Debug for TableCaptionFlow { write!(f, "TableCaptionFlow: {:?}", self.block_flow) } } + diff --git a/components/layout/table_cell.rs b/components/layout/table_cell.rs index 1de720b2516..92e503d3019 100644 --- a/components/layout/table_cell.rs +++ b/components/layout/table_cell.rs @@ -8,28 +8,39 @@ use block::{BlockFlow, ISizeAndMarginsComputer, MarginsMayCollapseFlag}; use context::LayoutContext; +use css::node_style::StyledNode; +use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode}; use flow::{Flow, FlowClass}; use fragment::{Fragment, FragmentBorderBoxIterator}; -use model::{MaybeAuto}; +use model::MaybeAuto; use layout_debug; use table::InternalTable; +use table_row::{CollapsedBorder, CollapsedBorderProvenance}; use wrapper::ThreadSafeLayoutNode; -use geom::{Point2D, Rect}; -use util::geometry::Au; -use util::logical_geometry::LogicalRect; +use cssparser::Color; +use geom::{Point2D, Rect, SideOffsets2D, Size2D}; +use gfx::display_list::DisplayList; use std::fmt; -use style::properties::ComputedValues; -use style::legacy::UnsignedIntegerAttribute; use std::sync::Arc; +use style::computed_values::{border_collapse, border_top_style}; +use style::legacy::UnsignedIntegerAttribute; +use style::properties::ComputedValues; +use util::geometry::Au; +use util::logical_geometry::{LogicalMargin, LogicalRect, WritingMode}; /// A table formatting context. #[derive(RustcEncodable)] pub struct TableCellFlow { /// Data common to all block flows. pub block_flow: BlockFlow, + + /// Border collapse information for the cell. + pub collapsed_borders: CollapsedBordersForCell, + /// The column span of this cell. pub column_span: u32, + /// Whether this cell is visible. If false, the value of `empty-cells` means that we must not /// display this cell. pub visible: bool, @@ -42,6 +53,7 @@ impl TableCellFlow { -> TableCellFlow { TableCellFlow { block_flow: BlockFlow::from_node_and_fragment(node, fragment), + collapsed_borders: CollapsedBordersForCell::new(), column_span: node.get_unsigned_integer_attribute(UnsignedIntegerAttribute::ColSpan) .unwrap_or(1), visible: visible, @@ -85,6 +97,10 @@ impl Flow for TableCellFlow { &mut self.block_flow } + fn as_immutable_block(&self) -> &BlockFlow { + &self.block_flow + } + /// Minimum/preferred inline-sizes set by this function are used in automatic table layout /// calculation. fn bubble_inline_sizes(&mut self) { @@ -120,10 +136,11 @@ impl Flow for TableCellFlow { let containing_block_inline_size = self.block_flow.base.block_container_inline_size; let inline_size_computer = InternalTable; - + let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse; inline_size_computer.compute_used_inline_size(&mut self.block_flow, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); let inline_start_content_edge = self.block_flow.fragment.border_box.start.i + @@ -140,7 +157,7 @@ impl Flow for TableCellFlow { inline_start_content_edge, inline_end_content_edge, content_inline_size, - None); + |_, _, _, _, _| {}); } fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { @@ -161,9 +178,22 @@ impl Flow for TableCellFlow { } fn build_display_list(&mut self, layout_context: &LayoutContext) { - if self.visible { - self.block_flow.build_display_list(layout_context) + if !self.visible { + return } + + 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::Collapse(&self.collapsed_borders), + }; + + self.block_flow.build_display_list_for_block(box DisplayList::new(), + layout_context, + border_painting_mode) } fn repair_style(&mut self, new_style: &Arc) { @@ -194,3 +224,134 @@ impl fmt::Debug for TableCellFlow { write!(f, "TableCellFlow: {:?}", self.block_flow) } } + +#[derive(Copy, Clone, Debug, RustcEncodable)] +pub struct CollapsedBordersForCell { + pub inline_start_border: CollapsedBorder, + pub inline_end_border: CollapsedBorder, + pub block_start_border: CollapsedBorder, + pub block_end_border: CollapsedBorder, + pub inline_start_width: Au, + pub inline_end_width: Au, + pub block_start_width: Au, + pub block_end_width: Au, +} + +impl CollapsedBordersForCell { + fn new() -> CollapsedBordersForCell { + CollapsedBordersForCell { + inline_start_border: CollapsedBorder::new(), + inline_end_border: CollapsedBorder::new(), + block_start_border: CollapsedBorder::new(), + block_end_border: CollapsedBorder::new(), + inline_start_width: Au(0), + inline_end_width: Au(0), + block_start_width: Au(0), + block_end_width: Au(0), + } + } + + fn should_paint_inline_start_border(&self) -> bool { + self.inline_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell + } + + fn should_paint_inline_end_border(&self) -> bool { + self.inline_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell + } + + fn should_paint_block_start_border(&self) -> bool { + self.block_start_border.provenance != CollapsedBorderProvenance::FromPreviousTableCell + } + + fn should_paint_block_end_border(&self) -> bool { + self.block_end_border.provenance != CollapsedBorderProvenance::FromNextTableCell + } + + pub fn adjust_border_widths_for_painting(&self, border_widths: &mut LogicalMargin) { + border_widths.inline_start = if !self.should_paint_inline_start_border() { + Au(0) + } else { + self.inline_start_border.width + }; + border_widths.inline_end = if !self.should_paint_inline_end_border() { + Au(0) + } else { + self.inline_end_border.width + }; + border_widths.block_start = if !self.should_paint_block_start_border() { + Au(0) + } else { + self.block_start_border.width + }; + border_widths.block_end = if !self.should_paint_block_end_border() { + Au(0) + } else { + self.block_end_border.width + } + } + + pub fn adjust_border_bounds_for_painting(&self, + border_bounds: &mut Rect, + writing_mode: WritingMode) { + let inline_start_divisor = if self.should_paint_inline_start_border() { + 2 + } else { + -2 + }; + let inline_start_offset = self.inline_start_width / 2 + self.inline_start_border.width / + inline_start_divisor; + let inline_end_divisor = if self.should_paint_inline_end_border() { + 2 + } else { + -2 + }; + let inline_end_offset = self.inline_end_width / 2 + self.inline_end_border.width / + inline_end_divisor; + let block_start_divisor = if self.should_paint_block_start_border() { + 2 + } else { + -2 + }; + let block_start_offset = self.block_start_width / 2 + self.block_start_border.width / + block_start_divisor; + let block_end_divisor = if self.should_paint_block_end_border() { + 2 + } else { + -2 + }; + let block_end_offset = self.block_end_width / 2 + self.block_end_border.width / + block_end_divisor; + + // FIXME(pcwalton): Get the real container size. + let mut logical_bounds = + LogicalRect::from_physical(writing_mode, *border_bounds, Size2D(Au(0), Au(0))); + logical_bounds.start.i = logical_bounds.start.i - inline_start_offset; + logical_bounds.start.b = logical_bounds.start.b - block_start_offset; + logical_bounds.size.inline = logical_bounds.size.inline + inline_start_offset + + inline_end_offset; + logical_bounds.size.block = logical_bounds.size.block + block_start_offset + + block_end_offset; + *border_bounds = logical_bounds.to_physical(writing_mode, Size2D(Au(0), Au(0))) + } + + pub fn adjust_border_colors_and_styles_for_painting( + &self, + border_colors: &mut SideOffsets2D, + border_styles: &mut SideOffsets2D, + writing_mode: WritingMode) { + let logical_border_colors = LogicalMargin::new(writing_mode, + self.block_start_border.color, + self.inline_end_border.color, + self.block_end_border.color, + self.inline_start_border.color); + *border_colors = logical_border_colors.to_physical(writing_mode); + + let logical_border_styles = LogicalMargin::new(writing_mode, + self.block_start_border.style, + self.inline_end_border.style, + self.block_end_border.style, + self.inline_start_border.style); + *border_styles = logical_border_styles.to_physical(writing_mode); + } +} + diff --git a/components/layout/table_row.rs b/components/layout/table_row.rs index 00958547f20..36afbabcd0c 100644 --- a/components/layout/table_row.rs +++ b/components/layout/table_row.rs @@ -6,30 +6,35 @@ #![deny(unsafe_code)] -use block::BlockFlow; -use block::ISizeAndMarginsComputer; +use block::{BlockFlow, ISizeAndMarginsComputer}; use context::LayoutContext; -use flow::{self, FlowClass, Flow, ImmutableFlowUtils}; +use display_list_builder::{BlockFlowDisplayListBuilding, BorderPaintingMode}; +use flow::{self, BaseFlow, FlowClass, Flow, ImmutableFlowUtils}; +use flow_list::MutFlowListIterator; use fragment::{Fragment, FragmentBorderBoxIterator}; use layout_debug; -use table::{ChildInlineSizeInfo, ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; -use table::{InternalTable}; +use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, VecExt}; +use table_cell::{CollapsedBordersForCell, TableCellFlow}; use model::MaybeAuto; use wrapper::ThreadSafeLayoutNode; +use cssparser::{Color, RGBA}; use geom::{Point2D, Rect}; -use util::geometry::Au; -use util::logical_geometry::LogicalRect; +use gfx::display_list::DisplayList; +use rustc_serialize::{Encoder, Encodable}; use std::cmp::max; use std::fmt; +use std::iter::{Enumerate, IntoIterator, Peekable}; use std::sync::Arc; -use style::computed_values::border_spacing; +use style::computed_values::{border_collapse, border_spacing, border_top_style}; use style::properties::ComputedValues; use style::values::computed::LengthOrPercentageOrAuto; +use util::geometry::Au; +use util::logical_geometry::{LogicalRect, WritingMode}; /// A single row of a table. -#[derive(RustcEncodable)] pub struct TableRowFlow { + /// Fields common to all block flows. pub block_flow: BlockFlow, /// Information about the intrinsic inline-sizes of each cell. @@ -41,6 +46,23 @@ pub struct TableRowFlow { /// The spacing for this row, propagated down from the table during the inline-size assignment /// phase. pub spacing: border_spacing::T, + + /// Information about the borders for each cell that we bubble up to our parent. This is only + /// computed if `border-collapse` is `collapse`. + pub preliminary_collapsed_borders: CollapsedBordersForRow, + + /// Information about the borders for each cell, post-collapse. This is only computed if + /// `border-collapse` is `collapse`. + pub final_collapsed_borders: CollapsedBordersForRow, + + /// The computed cell spacing widths post-collapse. + pub collapsed_border_spacing: CollapsedBorderSpacingForRow, +} + +impl Encodable for TableRowFlow { + fn encode(&self, e: &mut S) -> Result<(), S::Error> { + self.block_flow.encode(e) + } } /// Information about the column inline size and span for each cell. @@ -52,6 +74,7 @@ pub struct CellIntrinsicInlineSize { pub column_span: u32, } + impl TableRowFlow { pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> TableRowFlow { @@ -63,6 +86,9 @@ impl TableRowFlow { horizontal: Au(0), vertical: Au(0), }, + preliminary_collapsed_borders: CollapsedBordersForRow::new(), + final_collapsed_borders: CollapsedBordersForRow::new(), + collapsed_border_spacing: CollapsedBorderSpacingForRow::new(), } } @@ -70,12 +96,6 @@ impl TableRowFlow { &self.block_flow.fragment } - fn initialize_offsets(&mut self) -> (Au, Au, Au) { - // TODO: If border-collapse: collapse, block_start_offset, block_end_offset, and - // inline_start_offset should be updated. Currently, they are set as Au(0). - (Au(0), Au(0), Au(0)) - } - /// Assign block-size for table-row flow. /// /// TODO(pcwalton): This doesn't handle floats and positioned elements right. @@ -84,8 +104,6 @@ impl TableRowFlow { /// methods #[inline(always)] fn assign_block_size_table_row_base<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { - let (block_start_offset, _, _) = self.initialize_offsets(); - // Per CSS 2.1 § 17.5.3, find max_y = max(computed `block-size`, minimum block-size of all // cells). let mut max_block_size = Au(0); @@ -108,7 +126,7 @@ impl TableRowFlow { child_fragment.border_padding.block_start_end()); } let child_node = flow::mut_base(kid); - child_node.position.start.b = block_start_offset; + child_node.position.start.b = Au(0); max_block_size = max(max_block_size, child_node.position.size.block); } @@ -120,7 +138,7 @@ impl TableRowFlow { .content_block_size(), Au(0)) { MaybeAuto::Auto => block_size, - MaybeAuto::Specified(value) => max(value, block_size) + MaybeAuto::Specified(value) => max(value, block_size), }; // Assign the block-size of own fragment @@ -131,14 +149,38 @@ impl TableRowFlow { // Assign the block-size of kid fragments, which is the same value as own block-size. for kid in self.block_flow.base.child_iter() { + let child_table_cell = kid.as_table_cell(); { - let kid_fragment = kid.as_table_cell().mut_fragment(); + let kid_fragment = child_table_cell.mut_fragment(); let mut position = kid_fragment.border_box; position.size.block = block_size; kid_fragment.border_box = position; } - let child_node = flow::mut_base(kid); - child_node.position.size.block = block_size; + + // Assign the child's block size. + child_table_cell.block_flow.base.position.size.block = block_size + } + } + + pub fn populate_collapsed_border_spacing<'a,I>( + &mut self, + collapsed_inline_direction_border_widths_for_table: &[Au], + collapsed_block_direction_border_widths_for_table: &mut Peekable) + where I: Iterator { + self.collapsed_border_spacing.inline.clear(); + self.collapsed_border_spacing + .inline + .extend(collapsed_inline_direction_border_widths_for_table.into_iter().map(|x| *x)); + + if let Some(collapsed_block_direction_border_width_for_table) = + collapsed_block_direction_border_widths_for_table.next() { + self.collapsed_border_spacing.block_start = + *collapsed_block_direction_border_width_for_table + } + if let Some(collapsed_block_direction_border_width_for_table) = + collapsed_block_direction_border_widths_for_table.peek() { + self.collapsed_border_spacing.block_end = + **collapsed_block_direction_border_width_for_table } } } @@ -160,6 +202,10 @@ impl Flow for TableRowFlow { &mut self.block_flow } + fn as_immutable_block(&self) -> &BlockFlow { + &self.block_flow + } + fn column_intrinsic_inline_sizes<'a>(&'a mut self) -> &'a mut Vec { panic!("can't call column_intrinsic_inline_sizes() on table row") } @@ -181,48 +227,73 @@ impl Flow for TableRowFlow { // Bubble up the specified inline-sizes from child table cells. let (mut min_inline_size, mut pref_inline_size) = (Au(0), Au(0)); - for kid in self.block_flow.base.child_iter() { - assert!(kid.is_table_cell()); + let collapsing_borders = self.block_flow + .fragment + .style() + .get_inheritedtable() + .border_collapse == border_collapse::T::collapse; + // FIXME(pcwalton): Shouldn't use `CollapsedBorder::new()` here. + self.preliminary_collapsed_borders.reset(CollapsedBorder::new()); - // Collect the specified column inline-size of the cell. This is used in both fixed and - // automatic table layout calculation. - let child_specified_inline_size; - let child_column_span; - { - let child_table_cell = kid.as_table_cell(); - child_specified_inline_size = child_table_cell.fragment() - .style() - .content_inline_size(); - child_column_span = child_table_cell.column_span - } + { + let mut iterator = self.block_flow.base.child_iter().enumerate().peekable(); + while let Some((i, kid)) = iterator.next() { + assert!(kid.is_table_cell()); - // Collect minimum and preferred inline-sizes of the cell for automatic table layout - // calculation. - let child_base = flow::mut_base(kid); - let child_column_inline_size = ColumnIntrinsicInlineSize { - minimum_length: match child_specified_inline_size { - LengthOrPercentageOrAuto::Auto | LengthOrPercentageOrAuto::Percentage(_) => { - child_base.intrinsic_inline_sizes.minimum_inline_size + // Collect the specified column inline-size of the cell. This is used in both + // fixed and automatic table layout calculation. + let child_specified_inline_size; + let child_column_span; + { + let child_table_cell = kid.as_table_cell(); + child_specified_inline_size = child_table_cell.block_flow + .fragment + .style + .content_inline_size(); + child_column_span = child_table_cell.column_span; + + // Perform border collapse if necessary. + if collapsing_borders { + perform_inline_direction_border_collapse_for_row( + i, + child_table_cell, + &mut iterator, + &mut self.preliminary_collapsed_borders) } - LengthOrPercentageOrAuto::Length(length) => length, - }, - percentage: match child_specified_inline_size { - LengthOrPercentageOrAuto::Auto | LengthOrPercentageOrAuto::Length(_) => 0.0, - LengthOrPercentageOrAuto::Percentage(percentage) => percentage, - }, - preferred: child_base.intrinsic_inline_sizes.preferred_inline_size, - constrained: match child_specified_inline_size { - LengthOrPercentageOrAuto::Length(_) => true, - LengthOrPercentageOrAuto::Auto | LengthOrPercentageOrAuto::Percentage(_) => false, - }, - }; - min_inline_size = min_inline_size + child_column_inline_size.minimum_length; - pref_inline_size = pref_inline_size + child_column_inline_size.preferred; - self.cell_intrinsic_inline_sizes.push(CellIntrinsicInlineSize { - column_size: child_column_inline_size, - column_span: child_column_span, - }); + } + + // Collect minimum and preferred inline-sizes of the cell for automatic table layout + // calculation. + let child_base = flow::mut_base(kid); + let child_column_inline_size = ColumnIntrinsicInlineSize { + minimum_length: match child_specified_inline_size { + LengthOrPercentageOrAuto::Auto | + LengthOrPercentageOrAuto::Percentage(_) => { + child_base.intrinsic_inline_sizes.minimum_inline_size + } + LengthOrPercentageOrAuto::Length(length) => length, + }, + percentage: match child_specified_inline_size { + LengthOrPercentageOrAuto::Auto | + LengthOrPercentageOrAuto::Length(_) => 0.0, + LengthOrPercentageOrAuto::Percentage(percentage) => percentage, + }, + preferred: child_base.intrinsic_inline_sizes.preferred_inline_size, + constrained: match child_specified_inline_size { + LengthOrPercentageOrAuto::Length(_) => true, + LengthOrPercentageOrAuto::Auto | + LengthOrPercentageOrAuto::Percentage(_) => false, + }, + }; + min_inline_size = min_inline_size + child_column_inline_size.minimum_length; + pref_inline_size = pref_inline_size + child_column_inline_size.preferred; + self.cell_intrinsic_inline_sizes.push(CellIntrinsicInlineSize { + column_size: child_column_inline_size, + column_span: child_column_span, + }); + } } + self.block_flow.base.intrinsic_inline_sizes.minimum_inline_size = min_inline_size; self.block_flow.base.intrinsic_inline_sizes.preferred_inline_size = max(min_inline_size, pref_inline_size); @@ -241,9 +312,11 @@ impl Flow for TableRowFlow { let inline_end_content_edge = Au(0); let inline_size_computer = InternalTable; + let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse; inline_size_computer.compute_used_inline_size(&mut self.block_flow, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); // Spread out the completed inline sizes among columns with spans > 1. let mut computed_inline_size_for_cells = Vec::new(); @@ -279,21 +352,44 @@ impl Flow for TableRowFlow { computed_inline_size_for_cells.push(column_computed_inline_size) } + // Set up border collapse info. + let border_collapse_info = + match self.block_flow.fragment.style().get_inheritedtable().border_collapse { + border_collapse::T::collapse => { + Some(BorderCollapseInfoForChildTableCell { + collapsed_borders_for_row: &self.final_collapsed_borders, + collapsed_border_spacing_for_row: &self.collapsed_border_spacing, + }) + } + border_collapse::T::separate => None, + }; + // Push those inline sizes down to the cells. - let info = ChildInlineSizeInfo { - column_computed_inline_sizes: &computed_inline_size_for_cells, - spacing: self.spacing, - }; + let spacing = self.spacing; self.block_flow.propagate_assigned_inline_size_to_children(layout_context, inline_start_content_edge, inline_end_content_edge, containing_block_inline_size, - Some(info)); + |child_flow, + child_index, + content_inline_size, + writing_mode, + inline_start_margin_edge| { + propagate_column_inline_sizes_to_child( + child_flow, + child_index, + content_inline_size, + writing_mode, + computed_inline_size_for_cells.as_slice(), + &spacing, + &border_collapse_info, + inline_start_margin_edge) + }) } - fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { debug!("assign_block_size: assigning block_size for table_row"); - self.assign_block_size_table_row_base(ctx); + self.assign_block_size_table_row_base(layout_context); } fn compute_absolute_position(&mut self) { @@ -309,7 +405,18 @@ impl Flow for TableRowFlow { } fn build_display_list(&mut self, layout_context: &LayoutContext) { - self.block_flow.build_display_list(layout_context) + 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(box DisplayList::new(), + layout_context, + border_painting_mode); } fn repair_style(&mut self, new_style: &Arc) { @@ -340,3 +447,375 @@ impl fmt::Debug for TableRowFlow { write!(f, "TableRowFlow: {:?}", self.block_flow.fragment) } } + +#[derive(Clone, Debug)] +pub struct CollapsedBordersForRow { + /// The size of this vector should be equal to the number of cells plus one. + pub inline: Vec, + /// The size of this vector should be equal to the number of cells. + pub block_start: Vec, + /// The size of this vector should be equal to the number of cells. + pub block_end: Vec, +} + +impl CollapsedBordersForRow { + pub fn new() -> CollapsedBordersForRow { + CollapsedBordersForRow { + inline: Vec::new(), + block_start: Vec::new(), + block_end: Vec::new(), + } + } + + pub fn reset(&mut self, first_inline_border: CollapsedBorder) { + self.inline.clear(); + self.inline.push(first_inline_border); + self.block_start.clear(); + self.block_end.clear() + } +} + +#[derive(Clone, Debug)] +pub struct CollapsedBorderSpacingForRow { + /// The spacing in between each column. + inline: Vec, + /// The spacing above this row. + pub block_start: Au, + /// The spacing below this row. + block_end: Au, +} + +impl CollapsedBorderSpacingForRow { + fn new() -> CollapsedBorderSpacingForRow { + CollapsedBorderSpacingForRow { + inline: Vec::new(), + block_start: Au(0), + block_end: Au(0), + } + } +} + +/// All aspects of a border that can collapse with adjacent borders. See CSS 2.1 § 17.6.2.1. +#[derive(Copy, Clone, Debug)] +pub struct CollapsedBorder { + /// The style of the border. + pub style: border_top_style::T, + /// The width of the border. + pub width: Au, + /// The color of the border. + pub color: Color, + /// The type of item that this border comes from. + pub provenance: CollapsedBorderProvenance, +} + +impl Encodable for CollapsedBorder { + fn encode(&self, _: &mut S) -> Result<(), S::Error> { + Ok(()) + } +} + +/// Where a border style comes from. +/// +/// The integer values here correspond to the border conflict resolution rules in CSS 2.1 § +/// 17.6.2.1. Higher values override lower values. +#[derive(Copy, Clone, Debug, PartialEq, RustcEncodable)] +pub enum CollapsedBorderProvenance { + FromPreviousTableCell = 6, + FromNextTableCell = 5, + FromTableRow = 4, + FromTableRowGroup = 3, + FromTableColumn = 2, + FromTableColumnGroup = 1, + FromTable = 0, +} + +impl CollapsedBorder { + /// Creates a collapsible border style for no border. + pub fn new() -> CollapsedBorder { + CollapsedBorder { + style: border_top_style::T::none, + width: Au(0), + color: Color::RGBA(RGBA { + red: 0.0, + green: 0.0, + blue: 0.0, + alpha: 0.0, + }), + provenance: CollapsedBorderProvenance::FromTable, + } + } + + /// Creates a collapsed border from the block-start border described in the given CSS style + /// object. + fn top(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + CollapsedBorder { + style: css_style.get_border().border_top_style, + width: css_style.get_border().border_top_width, + color: css_style.get_border().border_top_color, + provenance: provenance, + } + } + + /// Creates a collapsed border style from the right border described in the given CSS style + /// object. + fn right(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + CollapsedBorder { + style: css_style.get_border().border_right_style, + width: css_style.get_border().border_right_width, + color: css_style.get_border().border_right_color, + provenance: provenance, + } + } + + /// Creates a collapsed border style from the bottom border described in the given CSS style + /// object. + fn bottom(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + CollapsedBorder { + style: css_style.get_border().border_bottom_style, + width: css_style.get_border().border_bottom_width, + color: css_style.get_border().border_bottom_color, + provenance: provenance, + } + } + + /// Creates a collapsed border style from the left border described in the given CSS style + /// object. + fn left(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + CollapsedBorder { + style: css_style.get_border().border_left_style, + width: css_style.get_border().border_left_width, + color: css_style.get_border().border_left_color, + provenance: provenance, + } + } + + /// Creates a collapsed border style from the inline-start border described in the given CSS + /// style object. + pub fn inline_start(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + let writing_mode = css_style.writing_mode; + match (writing_mode.is_vertical(), + writing_mode.is_inline_tb(), + writing_mode.is_bidi_ltr()) { + (false, _, true) => CollapsedBorder::left(css_style, provenance), + (false, _, false) => CollapsedBorder::right(css_style, provenance), + (true, true, _) => CollapsedBorder::top(css_style, provenance), + (true, false, _) => CollapsedBorder::bottom(css_style, provenance), + } + } + + /// Creates a collapsed border style from the inline-start border described in the given CSS + /// style object. + pub fn inline_end(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + let writing_mode = css_style.writing_mode; + match (writing_mode.is_vertical(), + writing_mode.is_inline_tb(), + writing_mode.is_bidi_ltr()) { + (false, _, true) => CollapsedBorder::right(css_style, provenance), + (false, _, false) => CollapsedBorder::left(css_style, provenance), + (true, true, _) => CollapsedBorder::bottom(css_style, provenance), + (true, false, _) => CollapsedBorder::top(css_style, provenance), + } + } + + /// Creates a collapsed border style from the block-start border described in the given CSS + /// style object. + pub fn block_start(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + let writing_mode = css_style.writing_mode; + match (writing_mode.is_vertical(), writing_mode.is_vertical_lr()) { + (false, _) => CollapsedBorder::top(css_style, provenance), + (true, true) => CollapsedBorder::left(css_style, provenance), + (true, false) => CollapsedBorder::right(css_style, provenance), + } + } + + /// Creates a collapsed border style from the block-end border described in the given CSS style + /// object. + pub fn block_end(css_style: &ComputedValues, provenance: CollapsedBorderProvenance) + -> CollapsedBorder { + let writing_mode = css_style.writing_mode; + match (writing_mode.is_vertical(), writing_mode.is_vertical_lr()) { + (false, _) => CollapsedBorder::bottom(css_style, provenance), + (true, true) => CollapsedBorder::right(css_style, provenance), + (true, false) => CollapsedBorder::left(css_style, provenance), + } + } + + /// If `other` has a higher priority per CSS 2.1 § 17.6.2.1, replaces `self` with it. + pub fn combine(&mut self, other: &CollapsedBorder) { + match (self.style, other.style) { + // Step 1. + (border_top_style::T::hidden, _) => {} + (_, border_top_style::T::hidden) => *self = *other, + // Step 2. + (border_top_style::T::none, _) => *self = *other, + (_, border_top_style::T::none) => {} + // Step 3. + _ if self.width > other.width => {} + _ if self.width < other.width => *self = *other, + (this_style, other_style) if (this_style as i8) > other_style as i8 => {} + (this_style, other_style) if (this_style as i8) < other_style as i8 => *self = *other, + // Step 4. + _ if (self.provenance as i8) >= other.provenance as i8 => {} + _ => *self = *other, + } + } +} + +/// Pushes column inline size and border collapse info down to a child. +pub fn propagate_column_inline_sizes_to_child( + child_flow: &mut Flow, + child_index: usize, + content_inline_size: Au, + writing_mode: WritingMode, + column_computed_inline_sizes: &[ColumnComputedInlineSize], + border_spacing: &border_spacing::T, + border_collapse_info: &Option, + inline_start_margin_edge: &mut Au) { + // If the child is a row group or a row, the column inline-size info should be copied from its + // parent. + // + // FIXME(pcwalton): This seems inefficient. Reference count it instead? + let inline_size = match child_flow.class() { + FlowClass::Table => { + let child_table_flow = child_flow.as_table(); + child_table_flow.column_computed_inline_sizes = column_computed_inline_sizes.to_vec(); + content_inline_size + } + FlowClass::TableRowGroup => { + let child_table_rowgroup_flow = child_flow.as_table_rowgroup(); + child_table_rowgroup_flow.column_computed_inline_sizes = + column_computed_inline_sizes.to_vec(); + child_table_rowgroup_flow.spacing = *border_spacing; + content_inline_size + } + FlowClass::TableRow => { + let child_table_row_flow = child_flow.as_table_row(); + child_table_row_flow.column_computed_inline_sizes = + column_computed_inline_sizes.to_vec(); + child_table_row_flow.spacing = *border_spacing; + content_inline_size + } + FlowClass::TableCell => column_computed_inline_sizes[child_index].size, + _ => content_inline_size, + }; + + if !child_flow.is_table_cell() { + set_inline_position_of_child_flow(flow::mut_base(child_flow), + inline_start_margin_edge, + inline_size, + writing_mode); + return + } + + // Handle border collapsing, if necessary. + let child_table_cell = child_flow.as_table_cell(); + match *border_collapse_info { + Some(ref border_collapse_info) => { + // Write in the child's border collapse state. + child_table_cell.collapsed_borders = CollapsedBordersForCell { + inline_start_border: border_collapse_info.collapsed_borders_for_row + .inline + .get(child_index) + .map(|x| *x) + .unwrap_or(CollapsedBorder::new()), + inline_end_border: border_collapse_info.collapsed_borders_for_row + .inline + .get(child_index + 1) + .map(|x| *x) + .unwrap_or(CollapsedBorder::new()), + block_start_border: border_collapse_info.collapsed_borders_for_row + .block_start + .get(child_index) + .map(|x| *x) + .unwrap_or(CollapsedBorder::new()), + block_end_border: border_collapse_info.collapsed_borders_for_row + .block_end + .get(child_index) + .map(|x| *x) + .unwrap_or(CollapsedBorder::new()), + inline_start_width: border_collapse_info.collapsed_border_spacing_for_row + .inline + .get(child_index) + .map(|x| *x) + .unwrap_or(Au(0)), + inline_end_width: border_collapse_info.collapsed_border_spacing_for_row + .inline + .get(child_index + 1) + .map(|x| *x) + .unwrap_or(Au(0)), + block_start_width: border_collapse_info.collapsed_border_spacing_for_row + .block_start, + block_end_width: border_collapse_info.collapsed_border_spacing_for_row.block_end, + }; + + // Move over past the collapsed border. + *inline_start_margin_edge = *inline_start_margin_edge + + child_table_cell.collapsed_borders.inline_start_width + } + None => { + // Take spacing into account. + *inline_start_margin_edge = *inline_start_margin_edge + border_spacing.horizontal + } + } + + set_inline_position_of_child_flow(&mut child_table_cell.block_flow.base, + inline_start_margin_edge, + inline_size, + writing_mode); + + *inline_start_margin_edge = *inline_start_margin_edge + inline_size +} + +#[derive(Copy, Clone)] +pub struct BorderCollapseInfoForChildTableCell<'a> { + collapsed_borders_for_row: &'a CollapsedBordersForRow, + collapsed_border_spacing_for_row: &'a CollapsedBorderSpacingForRow, +} + +fn set_inline_position_of_child_flow(child_flow: &mut BaseFlow, + inline_start_margin_edge: &mut Au, + inline_size: Au, + writing_mode: WritingMode) { + child_flow.position.start.i = *inline_start_margin_edge; + child_flow.block_container_inline_size = inline_size; + child_flow.block_container_writing_mode = writing_mode; +} + +/// Performs border-collapse in the inline direction for all the cells' inside borders in the +/// inline-direction cells and propagates the outside borders (the far left and right) up to the +/// table row. This is done eagerly here so that at least the inline inside border collapse +/// computations can be parallelized across all the rows of the table. +fn perform_inline_direction_border_collapse_for_row( + child_index: usize, + child_table_cell: &mut TableCellFlow, + iterator: &mut Peekable>, + preliminary_collapsed_borders: &mut CollapsedBordersForRow) { + let inline_collapsed_border = preliminary_collapsed_borders.inline.push_or_mutate( + child_index + 1, + CollapsedBorder::inline_end(&*child_table_cell.block_flow.fragment.style, + CollapsedBorderProvenance::FromPreviousTableCell)); + + if let Some(&(_, ref next_child_flow)) = iterator.peek() { + let next_child_flow = next_child_flow.as_immutable_block(); + inline_collapsed_border.combine( + &CollapsedBorder::inline_start(&*next_child_flow.fragment.style, + CollapsedBorderProvenance::FromNextTableCell)) + }; + + let block_start_border = + CollapsedBorder::block_start(&*child_table_cell.block_flow.fragment.style, + CollapsedBorderProvenance::FromNextTableCell); + preliminary_collapsed_borders.block_start.push_or_mutate(child_index, block_start_border); + let block_end_border = + CollapsedBorder::block_end(&*child_table_cell.block_flow.fragment.style, + CollapsedBorderProvenance::FromPreviousTableCell); + preliminary_collapsed_borders.block_end.push_or_mutate(child_index, block_end_border); +} + diff --git a/components/layout/table_rowgroup.rs b/components/layout/table_rowgroup.rs index 85f095b3076..1a2ea96d0d3 100644 --- a/components/layout/table_rowgroup.rs +++ b/components/layout/table_rowgroup.rs @@ -11,20 +11,21 @@ use context::LayoutContext; use flow::{FlowClass, Flow}; use fragment::{Fragment, FragmentBorderBoxIterator}; use layout_debug; -use style::computed_values::border_spacing; -use table::{ChildInlineSizeInfo, ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; -use table::{InternalTable, TableLikeFlow}; +use style::computed_values::{border_collapse, border_spacing}; +use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize, InternalTable, TableLikeFlow}; +use table_row::{self, CollapsedBordersForRow}; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect}; +use rustc_serialize::{Encoder, Encodable}; +use std::fmt; +use std::iter::{IntoIterator, Iterator, Peekable}; +use std::sync::Arc; +use style::properties::ComputedValues; use util::geometry::Au; use util::logical_geometry::LogicalRect; -use std::fmt; -use style::properties::ComputedValues; -use std::sync::Arc; /// A table formatting context. -#[derive(RustcEncodable)] pub struct TableRowGroupFlow { /// Fields common to all block flows. pub block_flow: BlockFlow, @@ -37,6 +38,24 @@ pub struct TableRowGroupFlow { /// The spacing for this rowgroup. pub spacing: border_spacing::T, + + /// Information about the borders for each cell that we bubble up to our parent. This is only + /// computed if `border-collapse` is `collapse`. + pub preliminary_collapsed_borders: CollapsedBordersForRow, + + /// 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, + + /// 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, +} + +impl Encodable for TableRowGroupFlow { + fn encode(&self, e: &mut S) -> Result<(), S::Error> { + self.block_flow.encode(e) + } } impl TableRowGroupFlow { @@ -50,12 +69,38 @@ impl TableRowGroupFlow { horizontal: Au(0), vertical: Au(0), }, + preliminary_collapsed_borders: CollapsedBordersForRow::new(), + collapsed_inline_direction_border_widths_for_table: Vec::new(), + collapsed_block_direction_border_widths_for_table: Vec::new(), } } pub fn fragment<'a>(&'a mut self) -> &'a Fragment { &self.block_flow.fragment } + + pub fn populate_collapsed_border_spacing<'a,I>( + &mut self, + collapsed_inline_direction_border_widths_for_table: &[Au], + collapsed_block_direction_border_widths_for_table: &mut Peekable) + where I: Iterator { + self.collapsed_inline_direction_border_widths_for_table.clear(); + self.collapsed_inline_direction_border_widths_for_table + .extend(collapsed_inline_direction_border_widths_for_table.into_iter().map(|x| *x)); + + for _ in range(0, self.block_flow.base.children.len()) { + if let Some(collapsed_block_direction_border_width_for_table) = + collapsed_block_direction_border_widths_for_table.next() { + self.collapsed_block_direction_border_widths_for_table + .push(*collapsed_block_direction_border_width_for_table) + } + } + if let Some(collapsed_block_direction_border_width_for_table) = + collapsed_block_direction_border_widths_for_table.peek() { + self.collapsed_block_direction_border_widths_for_table + .push(**collapsed_block_direction_border_width_for_table) + } + } } impl Flow for TableRowGroupFlow { @@ -75,6 +120,10 @@ impl Flow for TableRowGroupFlow { &mut self.block_flow } + fn as_immutable_block(&self) -> &BlockFlow { + &self.block_flow + } + fn column_intrinsic_inline_sizes<'a>(&'a mut self) -> &'a mut Vec { &mut self.column_intrinsic_inline_sizes } @@ -99,26 +148,48 @@ impl Flow for TableRowGroupFlow { // The position was set to the containing block by the flow's parent. let containing_block_inline_size = self.block_flow.base.block_container_inline_size; - // FIXME: In case of border-collapse: collapse, inline-start_content_edge should be - // the border width on the inline-start side. - let inline_start_content_edge = Au::new(0); - let inline_end_content_edge = Au::new(0); + let (inline_start_content_edge, inline_end_content_edge) = (Au(0), Au(0)); let content_inline_size = containing_block_inline_size; let inline_size_computer = InternalTable; + let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse; inline_size_computer.compute_used_inline_size(&mut self.block_flow, layout_context, - containing_block_inline_size); + containing_block_inline_size, + border_collapse); - let info = ChildInlineSizeInfo { - column_computed_inline_sizes: &self.column_computed_inline_sizes, - spacing: self.spacing, - }; + let column_computed_inline_sizes = self.column_computed_inline_sizes.as_slice(); + let border_spacing = self.spacing; + let collapsed_inline_direction_border_widths_for_table = + self.collapsed_inline_direction_border_widths_for_table.as_slice(); + let mut collapsed_block_direction_border_widths_for_table = + self.collapsed_block_direction_border_widths_for_table.iter().peekable(); self.block_flow.propagate_assigned_inline_size_to_children(layout_context, inline_start_content_edge, inline_end_content_edge, content_inline_size, - Some(info)); + |child_flow, + child_index, + content_inline_size, + writing_mode, + inline_start_margin_edge| { + table_row::propagate_column_inline_sizes_to_child( + child_flow, + child_index, + content_inline_size, + writing_mode, + column_computed_inline_sizes, + &border_spacing, + &None, + inline_start_margin_edge); + + if border_collapse == border_collapse::T::collapse { + let child_table_row = child_flow.as_table_row(); + child_table_row.populate_collapsed_border_spacing( + collapsed_inline_direction_border_widths_for_table.as_slice(), + &mut collapsed_block_direction_border_widths_for_table); + } + }); } fn assign_block_size<'a>(&mut self, layout_context: &'a LayoutContext<'a>) { diff --git a/components/layout/table_wrapper.rs b/components/layout/table_wrapper.rs index c603ff47476..6eb0839925e 100644 --- a/components/layout/table_wrapper.rs +++ b/components/layout/table_wrapper.rs @@ -20,11 +20,13 @@ use floats::FloatKind; use flow::{FlowClass, Flow, ImmutableFlowUtils}; use flow::{IMPACTED_BY_LEFT_FLOATS, IMPACTED_BY_RIGHT_FLOATS}; use fragment::{Fragment, FragmentBorderBoxIterator}; -use table::{ChildInlineSizeInfo, ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; +use table::{ColumnComputedInlineSize, ColumnIntrinsicInlineSize}; +use table_row; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect}; use util::geometry::Au; +use util::logical_geometry::LogicalRect; use std::cmp::{max, min}; use std::fmt; use std::ops::Add; @@ -98,25 +100,32 @@ impl TableWrapperFlow { // when normally the child computes it itself. But it has to be this way because the // padding will affect where we place the child. This is an odd artifact of the way that // tables are separated into table flows and table wrapper flows. + // + // FIXME(pcwalton): Handle `border-collapse` correctly. let mut available_inline_size = self.block_flow.fragment.border_box.size.inline; let (mut table_border_padding, mut spacing) = (Au(0), Au(0)); for kid in self.block_flow.base.child_iter() { - if kid.is_table() { - let kid_block = kid.as_block(); - let spacing_per_cell = kid_block.fragment - .style() - .get_inheritedtable() - .border_spacing - .horizontal; - spacing = spacing_per_cell * (self.column_intrinsic_inline_sizes.len() as i32 + 1); - available_inline_size = self.block_flow.fragment.border_box.size.inline; - - kid_block.fragment.compute_border_and_padding(available_inline_size); - kid_block.fragment.compute_block_direction_margins(available_inline_size); - kid_block.fragment.compute_inline_direction_margins(available_inline_size); - table_border_padding = kid_block.fragment.border_padding.inline_start_end(); - break + if !kid.is_table() { + continue } + + let kid_table = kid.as_table(); + let spacing_per_cell = kid_table.spacing().horizontal; + spacing = spacing_per_cell * (self.column_intrinsic_inline_sizes.len() as i32 + 1); + available_inline_size = self.block_flow.fragment.border_box.size.inline; + + let kid_block_flow = &mut kid_table.block_flow; + kid_block_flow.fragment + .compute_border_and_padding(available_inline_size, + self.block_flow + .fragment + .style + .get_inheritedtable() + .border_collapse); + kid_block_flow.fragment.compute_block_direction_margins(available_inline_size); + kid_block_flow.fragment.compute_inline_direction_margins(available_inline_size); + table_border_padding = kid_block_flow.fragment.border_padding.inline_start_end(); + break } // FIXME(pcwalton, spec): INTRINSIC § 8 does not properly define how to compute this, but @@ -196,14 +205,17 @@ impl TableWrapperFlow { layout_context: &LayoutContext, parent_flow_inline_size: Au) { // Delegate to the appropriate inline size computer to find the constraint inputs. + let border_collapse = self.block_flow.fragment.style.get_inheritedtable().border_collapse; let input = if self.block_flow.base.flags.is_float() { FloatNonReplaced.compute_inline_size_constraint_inputs(&mut self.block_flow, parent_flow_inline_size, - layout_context) + layout_context, + border_collapse) } else { BlockNonReplaced.compute_inline_size_constraint_inputs(&mut self.block_flow, parent_flow_inline_size, - layout_context) + layout_context, + border_collapse) }; // Delegate to the appropriate inline size computer to write the constraint solutions in. @@ -313,26 +325,37 @@ impl Flow for TableWrapperFlow { } }; + let border_spacing = self.block_flow.fragment.style().get_inheritedtable().border_spacing; match assigned_column_inline_sizes { None => { - self.block_flow.propagate_assigned_inline_size_to_children( - layout_context, - inline_start_content_edge, - inline_end_content_edge, - content_inline_size, - None) - } - Some(ref assigned_column_inline_sizes) => { - let info = ChildInlineSizeInfo { - column_computed_inline_sizes: &assigned_column_inline_sizes, - spacing: self.block_flow.fragment.style().get_inheritedtable().border_spacing, - }; self.block_flow .propagate_assigned_inline_size_to_children(layout_context, inline_start_content_edge, inline_end_content_edge, content_inline_size, - Some(info)); + |_, _, _, _, _| {}) + } + Some(ref assigned_column_inline_sizes) => { + self.block_flow + .propagate_assigned_inline_size_to_children(layout_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| { + table_row::propagate_column_inline_sizes_to_child( + child_flow, + child_index, + content_inline_size, + writing_mode, + assigned_column_inline_sizes.as_slice(), + &border_spacing, + &None, + inline_start_margin_edge) + }) } } @@ -369,6 +392,10 @@ impl Flow for TableWrapperFlow { self.block_flow.update_late_computed_block_position_if_necessary(block_position) } + fn generated_containing_block_rect(&self) -> LogicalRect { + self.block_flow.generated_containing_block_rect() + } + fn build_display_list(&mut self, layout_context: &LayoutContext) { self.block_flow.build_display_list(layout_context) } diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index 79e70a83d4e..e937f4c41b2 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -41,7 +41,6 @@ partial interface CSSStyleDeclaration { [TreatNullAs=EmptyString] attribute DOMString backgroundSize; [TreatNullAs=EmptyString] attribute DOMString border; - [TreatNullAs=EmptyString] attribute DOMString borderCollapse; [TreatNullAs=EmptyString] attribute DOMString borderColor; [TreatNullAs=EmptyString] attribute DOMString borderRadius; [TreatNullAs=EmptyString] attribute DOMString borderSpacing; @@ -119,6 +118,7 @@ partial interface CSSStyleDeclaration { [TreatNullAs=EmptyString] attribute DOMString overflowWrap; [TreatNullAs=EmptyString] attribute DOMString tableLayout; + [TreatNullAs=EmptyString] attribute DOMString borderCollapse; [TreatNullAs=EmptyString] attribute DOMString emptyCells; [TreatNullAs=EmptyString] attribute DOMString captionSide; diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index cfacc639054..b6e08f7bd09 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -2020,12 +2020,12 @@ pub mod longhands { ${new_style_struct("InheritedTable", is_inherited=True)} + ${single_keyword("border-collapse", "separate collapse")} + ${single_keyword("empty-cells", "show hide")} ${single_keyword("caption-side", "top bottom")} - ${single_keyword("border-collapse", "separate collapse", experimental=True)} - <%self:longhand name="border-spacing"> use values::computed::{Context, ToComputedValue}; diff --git a/components/style/values.rs b/components/style/values.rs index 3d724829be0..267ed05a023 100644 --- a/components/style/values.rs +++ b/components/style/values.rs @@ -6,14 +6,13 @@ pub use cssparser::RGBA; - macro_rules! define_css_keyword_enum { ($name: ident: $( $css: expr => $variant: ident ),+,) => { define_css_keyword_enum!($name: $( $css => $variant ),+); }; ($name: ident: $( $css: expr => $variant: ident ),+) => { #[allow(non_camel_case_types)] - #[derive(Clone, Eq, PartialEq, FromPrimitive, Copy, Hash)] + #[derive(Clone, Eq, PartialEq, FromPrimitive, Copy, Hash, RustcEncodable)] pub enum $name { $( $variant ),+ } @@ -46,6 +45,45 @@ macro_rules! define_css_keyword_enum { } } +macro_rules! define_numbered_css_keyword_enum { + ($name: ident: $( $css: expr => $variant: ident = $value: expr ),+,) => { + define_numbered_css_keyword_enum!($name: $( $css => $variant = $value ),+); + }; + ($name: ident: $( $css: expr => $variant: ident = $value: expr ),+) => { + #[allow(non_camel_case_types)] + #[derive(Clone, Eq, PartialEq, FromPrimitive, Copy, RustcEncodable)] + pub enum $name { + $( $variant = $value ),+ + } + + impl $name { + pub fn parse(input: &mut ::cssparser::Parser) -> Result<$name, ()> { + match_ignore_ascii_case! { try!(input.expect_ident()), + $( $css => Ok($name::$variant) ),+ + _ => Err(()) + } + } + } + + impl ::std::fmt::Debug for $name { + #[inline] + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + use cssparser::ToCss; + self.fmt_to_css(f) + } + } + + impl ::cssparser::ToCss for $name { + fn to_css(&self, dest: &mut W) -> ::text_writer::Result + where W: ::text_writer::TextWriter { + match self { + $( &$name::$variant => dest.write_str($css) ),+ + } + } + } + } +} + pub type CSSFloat = f64; @@ -850,17 +888,19 @@ pub mod specified { }) } - define_css_keyword_enum! { BorderStyle: - "none" => none, - "solid" => solid, - "double" => double, - "dotted" => dotted, - "dashed" => dashed, - "hidden" => hidden, - "groove" => groove, - "ridge" => ridge, - "inset" => inset, - "outset" => outset, + // The integer values here correspond to the border conflict resolution rules in CSS 2.1 § + // 17.6.2.1. Higher values override lower values. + define_numbered_css_keyword_enum! { BorderStyle: + "none" => none = -1, + "solid" => solid = 6, + "double" => double = 7, + "dotted" => dotted = 4, + "dashed" => dashed = 5, + "hidden" => hidden = -2, + "groove" => groove = 1, + "ridge" => ridge = 3, + "inset" => inset = 0, + "outset" => outset = 2, } /// A time in seconds according to CSS-VALUES § 6.2. diff --git a/components/util/geometry.rs b/components/util/geometry.rs index 05b1469a1b1..b1c1aa6c4dc 100644 --- a/components/util/geometry.rs +++ b/components/util/geometry.rs @@ -245,6 +245,20 @@ impl Au { NumCast::from(px.get() * 60f32).unwrap() } + /// Rounds this app unit down to the previous (left or top) pixel and returns it. + #[inline] + pub fn to_prev_px(&self) -> isize { + let Au(s) = *self; + ((s as f64) / 60f64).floor() as isize + } + + /// Rounds this app unit up to the next (right or bottom) pixel and returns it. + #[inline] + pub fn to_next_px(&self) -> isize { + let Au(s) = *self; + ((s as f64) / 60f64).ceil() as isize + } + #[inline] pub fn to_nearest_px(&self) -> isize { let Au(s) = *self; diff --git a/tests/html/border_collapse_test.html b/tests/html/border_collapse_test.html new file mode 100644 index 00000000000..1771b3c6d36 --- /dev/null +++ b/tests/html/border_collapse_test.html @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + +
1 + 2 + 3 +
4 + 5 + 6 +
7 + 8 + 9 +
10 + 11 + 12 +
13 + 14 + 15 +
+ + + + diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 1e9a887fcb1..e762048e1ca 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -81,6 +81,7 @@ flaky_cpu == append_style_a.html append_style_b.html != border_black_ridge.html border_black_groove.html != border_black_ridge.html border_black_solid.html == border_code_tag.html border_code_tag_ref.html +== border_collapse_simple_a.html border_collapse_simple_ref.html == border_radius_clip_a.html border_radius_clip_ref.html == border_radius_overlapping_a.html border_radius_overlapping_ref.html == border_spacing_a.html border_spacing_ref.html diff --git a/tests/ref/border_collapse_simple_a.html b/tests/ref/border_collapse_simple_a.html new file mode 100644 index 00000000000..292b9d6435e --- /dev/null +++ b/tests/ref/border_collapse_simple_a.html @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + + diff --git a/tests/ref/border_collapse_simple_ref.html b/tests/ref/border_collapse_simple_ref.html new file mode 100644 index 00000000000..98dc28d1094 --- /dev/null +++ b/tests/ref/border_collapse_simple_ref.html @@ -0,0 +1,136 @@ + + + + + + +
+
+
+
+
+ + + diff --git a/tests/ref/inline_block_img_a.html b/tests/ref/inline_block_img_a.html index a0022111715..06858c339e1 100644 --- a/tests/ref/inline_block_img_a.html +++ b/tests/ref/inline_block_img_a.html @@ -32,5 +32,9 @@ } -
XX
+ +
+ XX +
+