From f30cd4f3770bc0d65a01b66d6ce92b31b364a50b Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Fri, 20 Feb 2015 18:58:41 +0100 Subject: [PATCH 1/3] Add column-width, column-count, columns and column-gap properties in the style system. --- .../dom/webidls/CSSStyleDeclaration.webidl | 5 + components/style/properties.mako.rs | 214 +++++++++++++++++- 2 files changed, 216 insertions(+), 3 deletions(-) diff --git a/components/script/dom/webidls/CSSStyleDeclaration.webidl b/components/script/dom/webidls/CSSStyleDeclaration.webidl index e937f4c41b2..8ed0805f432 100644 --- a/components/script/dom/webidls/CSSStyleDeclaration.webidl +++ b/components/script/dom/webidls/CSSStyleDeclaration.webidl @@ -187,6 +187,11 @@ partial interface CSSStyleDeclaration { [TreatNullAs=EmptyString] attribute DOMString imageRendering; + [TreatNullAs=EmptyString] attribute DOMString columnCount; + [TreatNullAs=EmptyString] attribute DOMString columnWidth; + [TreatNullAs=EmptyString] attribute DOMString columns; + [TreatNullAs=EmptyString] attribute DOMString columnGap; + [TreatNullAs=EmptyString] attribute DOMString transition; [TreatNullAs=EmptyString] attribute DOMString transitionDuration; [TreatNullAs=EmptyString] attribute DOMString transitionTimingFunction; diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index b6e08f7bd09..c67b3c5c887 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -60,11 +60,12 @@ class Longhand(object): self.derived_from = [ to_rust_ident(name) for name in derived_from ] class Shorthand(object): - def __init__(self, name, sub_properties): + def __init__(self, name, sub_properties, experimental=False): self.name = name self.ident = to_rust_ident(name) self.camel_case = to_camel_case(self.ident) self.derived_from = None + self.experimental = experimental self.sub_properties = [LONGHANDS_BY_NAME[s] for s in sub_properties] class StyleStruct(object): @@ -2177,6 +2178,167 @@ pub mod longhands { // TODO(pcwalton): SVG-only values. ${single_keyword("pointer-events", "auto none")} + + ${new_style_struct("Column", is_inherited=False)} + + <%self:longhand name="column-width" experimental="True"> + use values::computed::{ToComputedValue, Context}; + use cssparser::ToCss; + use text_writer::{self, TextWriter}; + + #[derive(Clone, Copy, PartialEq)] + pub enum SpecifiedValue { + Auto, + Specified(specified::Length), + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + SpecifiedValue::Auto => dest.write_str("auto"), + SpecifiedValue::Specified(l) => l.to_css(dest), + } + } + } + + pub mod computed_value { + use util::geometry::Au; + pub type T = Option; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + None + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + match *self { + SpecifiedValue::Auto => None, + SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + } + } + } + + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("auto")).is_ok() { + Ok(SpecifiedValue::Auto) + } else { + specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified) + } + } + + + <%self:longhand name="column-count" experimental="True"> + use values::computed::{ToComputedValue, Context}; + use cssparser::ToCss; + use text_writer::{self, TextWriter}; + + #[derive(Clone, Copy, PartialEq)] + pub enum SpecifiedValue { + Auto, + Specified(u32), + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + SpecifiedValue::Auto => dest.write_str("auto"), + SpecifiedValue::Specified(count) => write!(dest, "{}", count), + } + } + } + + pub mod computed_value { + pub type T = Option; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + None + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, _context: &Context) -> computed_value::T { + match *self { + SpecifiedValue::Auto => None, + SpecifiedValue::Specified(count) => Some(count) + } + } + } + + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("auto")).is_ok() { + Ok(SpecifiedValue::Auto) + } else { + use std::u32; + let count = try!(input.expect_integer()); + // Zero is invalid + if count <= 0 || count > (u32::MAX as i64) { + return Err(()) + } + Ok(SpecifiedValue::Specified(count as u32)) + } + } + + + <%self:longhand name="column-gap" experimental="True"> + use values::computed::{ToComputedValue, Context}; + use cssparser::ToCss; + use text_writer::{self, TextWriter}; + + #[derive(Clone, Copy, PartialEq)] + pub enum SpecifiedValue { + Normal, + Specified(specified::Length), + } + + impl ToCss for SpecifiedValue { + fn to_css(&self, dest: &mut W) -> text_writer::Result where W: TextWriter { + match *self { + SpecifiedValue::Normal => dest.write_str("normal"), + SpecifiedValue::Specified(l) => l.to_css(dest), + } + } + } + + pub mod computed_value { + use util::geometry::Au; + pub type T = Option; + } + + #[inline] + pub fn get_initial_value() -> computed_value::T { + None + } + + impl ToComputedValue for SpecifiedValue { + type ComputedValue = computed_value::T; + + #[inline] + fn to_computed_value(&self, context: &Context) -> computed_value::T { + match *self { + SpecifiedValue::Normal => None, + SpecifiedValue::Specified(l) => Some(l.to_computed_value(context)) + } + } + } + + pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result { + if input.try(|input| input.expect_ident_matching("normal")).is_ok() { + Ok(SpecifiedValue::Normal) + } else { + specified::Length::parse_non_negative(input).map(SpecifiedValue::Specified) + } + } + + // Box-shadow, etc. ${new_style_struct("Effects", is_inherited=False)} @@ -3952,9 +4114,9 @@ pub mod shorthands { use parser::ParserContext; use values::specified; - <%def name="shorthand(name, sub_properties)"> + <%def name="shorthand(name, sub_properties, experimental=False)"> <% - shorthand = Shorthand(name, sub_properties.split()) + shorthand = Shorthand(name, sub_properties.split(), experimental=experimental) SHORTHANDS.append(shorthand) %> pub mod ${shorthand.ident} { @@ -4439,6 +4601,47 @@ pub mod shorthands { } + <%self:shorthand name="columns" sub_properties="column-count column-width" experimental="True"> + use properties::longhands::{column_count, column_width}; + let mut column_count = None; + let mut column_width = None; + let mut autos = 0; + + loop { + if input.try(|input| input.expect_ident_matching("auto")).is_ok() { + // Leave the options to None, 'auto' is the initial value. + autos += 1; + continue + } + + if column_count.is_none() { + if let Ok(value) = input.try(|input| column_count::parse(context, input)) { + column_count = Some(value); + continue + } + } + + if column_width.is_none() { + if let Ok(value) = input.try(|input| column_width::parse(context, input)) { + column_width = Some(value); + continue + } + } + + break + } + + let values = autos + column_count.iter().len() + column_width.iter().len(); + if values == 0 || values > 2 { + Err(()) + } else { + Ok(Longhands { + column_count: column_count, + column_width: column_width, + }) + } + + <%self:shorthand name="overflow" sub_properties="overflow-x overflow-y"> use properties::longhands::{overflow_x, overflow_y}; @@ -4802,6 +5005,11 @@ impl PropertyDeclaration { % endfor % for shorthand in SHORTHANDS: "${shorthand.name}" => { + % if shorthand.experimental: + if !::util::opts::experimental_enabled() { + return PropertyDeclarationParseResult::ExperimentalProperty + } + % endif match input.try(CSSWideKeyword::parse) { Ok(CSSWideKeyword::InheritKeyword) => { % for sub_property in shorthand.sub_properties: From cc4749373a421c6323dd5c9e5037899775ba015d Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Sat, 7 Mar 2015 17:15:31 +0100 Subject: [PATCH 2/3] Add MulticolFlow and use it for multicol elements. It currently "inherits" from BlockFlow and does not override anything. --- components/layout/block.rs | 3 +- components/layout/construct.rs | 9 ++- components/layout/flow.rs | 7 ++ components/layout/lib.rs | 1 + components/layout/multicol.rs | 107 ++++++++++++++++++++++++++++ components/style/properties.mako.rs | 6 ++ 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 components/layout/multicol.rs diff --git a/components/layout/block.rs b/components/layout/block.rs index 10bb2a055aa..39a0fe715f7 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -1394,7 +1394,8 @@ impl BlockFlow { FormattingContextType::Other } _ if style.get_box().overflow_x != overflow_x::T::visible || - style.get_box().overflow_y != overflow_y::T(overflow_x::T::visible) => { + style.get_box().overflow_y != overflow_y::T(overflow_x::T::visible) || + style.is_multicol() => { FormattingContextType::Block } _ => FormattingContextType::None, diff --git a/components/layout/construct.rs b/components/layout/construct.rs index f945e8bb526..12f9900b088 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -33,6 +33,7 @@ use fragment::{InlineBlockFragmentInfo, SpecificFragmentInfo}; use incremental::{RECONSTRUCT_FLOW, RestyleDamage}; use inline::InlineFlow; use list_item::{ListItemFlow, ListStyleTypeContent}; +use multicol::MulticolFlow; use opaque_node::OpaqueNodeMethods; use parallel; use table::TableFlow; @@ -655,8 +656,12 @@ impl<'a> FlowConstructor<'a> { /// to happen. fn build_flow_for_nonfloated_block(&mut self, node: &ThreadSafeLayoutNode) -> ConstructionResult { - let flow = box BlockFlow::from_node_and_fragment(node, self.build_fragment_for_block(node)) - as Box; + let fragment = self.build_fragment_for_block(node); + let flow = if node.style().is_multicol() { + box MulticolFlow::from_node_and_fragment(node, fragment) as Box + } else { + box BlockFlow::from_node_and_fragment(node, fragment) as Box + }; self.build_flow_for_block(FlowRef::new(flow), node) } diff --git a/components/layout/flow.rs b/components/layout/flow.rs index 6ebf8530c90..647916f33bc 100644 --- a/components/layout/flow.rs +++ b/components/layout/flow.rs @@ -44,6 +44,7 @@ use table_colgroup::TableColGroupFlow; use table_row::TableRowFlow; use table_rowgroup::TableRowGroupFlow; use table_wrapper::TableWrapperFlow; +use multicol::MulticolFlow; use wrapper::ThreadSafeLayoutNode; use geom::{Point2D, Rect, Size2D}; @@ -158,6 +159,11 @@ pub trait Flow: fmt::Debug + Sync { panic!("called as_table_cell() on a non-tablecell flow") } + /// If this is a multicol flow, returns the underlying object. Fails otherwise. + fn as_multicol<'a>(&'a mut self) -> &'a mut MulticolFlow { + panic!("called as_multicol() on a non-multicol flow") + } + /// If this is a table cell flow, returns the underlying object, borrowed immutably. Fails /// otherwise. fn as_immutable_table_cell<'a>(&'a self) -> &'a TableCellFlow { @@ -451,6 +457,7 @@ pub enum FlowClass { TableRow, TableCaption, TableCell, + Multicol, } /// A top-down traversal. diff --git a/components/layout/lib.rs b/components/layout/lib.rs index 7fbbb2d3576..844689f0534 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -78,6 +78,7 @@ pub mod incremental; pub mod inline; pub mod list_item; pub mod model; +pub mod multicol; pub mod opaque_node; pub mod parallel; pub mod sequential; diff --git a/components/layout/multicol.rs b/components/layout/multicol.rs new file mode 100644 index 00000000000..b23ccf65c1c --- /dev/null +++ b/components/layout/multicol.rs @@ -0,0 +1,107 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +//! CSS Multi-column layout http://dev.w3.org/csswg/css-multicol/ + +#![deny(unsafe_blocks)] + +use block::BlockFlow; +use context::LayoutContext; +use flow::{FlowClass, Flow}; +use fragment::{Fragment, FragmentBorderBoxIterator}; +use wrapper::ThreadSafeLayoutNode; + +use geom::{Point2D, Rect}; +use util::geometry::Au; +use util::logical_geometry::LogicalRect; +use std::fmt; +use style::properties::ComputedValues; +use std::sync::Arc; + +pub struct MulticolFlow { + pub block_flow: BlockFlow, +} + +impl MulticolFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) + -> MulticolFlow { + MulticolFlow { + block_flow: BlockFlow::from_node_and_fragment(node, fragment) + } + } +} + +impl Flow for MulticolFlow { + fn class(&self) -> FlowClass { + FlowClass::Multicol + } + + fn as_multicol<'a>(&'a mut self) -> &'a mut MulticolFlow { + self + } + + fn as_block<'a>(&'a mut self) -> &'a mut BlockFlow { + &mut self.block_flow + } + + fn bubble_inline_sizes(&mut self) { + // FIXME(SimonSapin) http://dev.w3.org/csswg/css-sizing/#multicol-intrinsic + self.block_flow.bubble_inline_sizes(); + } + + fn assign_inline_sizes(&mut self, ctx: &LayoutContext) { + debug!("assign_inline_sizes({}): assigning inline_size for flow", "multicol"); + self.block_flow.assign_inline_sizes(ctx); + } + + fn assign_block_size<'a>(&mut self, ctx: &'a LayoutContext<'a>) { + debug!("assign_block_size: assigning block_size for multicol"); + self.block_flow.assign_block_size(ctx); + } + + fn compute_absolute_position(&mut self) { + self.block_flow.compute_absolute_position() + } + + fn update_late_computed_inline_position_if_necessary(&mut self, inline_position: Au) { + self.block_flow.update_late_computed_inline_position_if_necessary(inline_position) + } + + fn update_late_computed_block_position_if_necessary(&mut self, block_position: Au) { + self.block_flow.update_late_computed_block_position_if_necessary(block_position) + } + + fn build_display_list(&mut self, layout_context: &LayoutContext) { + debug!("build_display_list_multicol: same process as block flow"); + self.block_flow.build_display_list(layout_context) + } + + fn repair_style(&mut self, new_style: &Arc) { + self.block_flow.repair_style(new_style) + } + + fn compute_overflow(&self) -> Rect { + self.block_flow.compute_overflow() + } + + fn generated_containing_block_rect(&self) -> LogicalRect { + self.block_flow.generated_containing_block_rect() + } + + fn iterate_through_fragment_border_boxes(&self, + iterator: &mut FragmentBorderBoxIterator, + stacking_context_position: &Point2D) { + self.block_flow.iterate_through_fragment_border_boxes(iterator, stacking_context_position) + } + + fn mutate_fragments(&mut self, mutator: &mut FnMut(&mut Fragment)) { + self.block_flow.mutate_fragments(mutator) + } +} + +impl fmt::Debug for MulticolFlow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MulticolFlow: {:?}", self.block_flow) + } +} diff --git a/components/style/properties.mako.rs b/components/style/properties.mako.rs index c67b3c5c887..38f5821ceb1 100644 --- a/components/style/properties.mako.rs +++ b/components/style/properties.mako.rs @@ -5194,6 +5194,12 @@ impl ComputedValues { )) } + #[inline] + pub fn is_multicol(&self) -> bool { + let style = self.get_column(); + style.column_count.is_some() || style.column_width.is_some() + } + #[inline] pub fn get_font_arc(&self) -> Arc { self.font.clone() From 544a02a25068a92dfd9e950eb3609713ad9599c7 Mon Sep 17 00:00:00 2001 From: Simon Sapin Date: Thu, 16 Apr 2015 16:51:27 +0200 Subject: [PATCH 3/3] Refactor flow construction to make `float` less of a special case. --- components/layout/block.rs | 31 +++++---------- components/layout/construct.rs | 62 +++++++++-------------------- components/layout/floats.rs | 8 ++-- components/layout/list_item.rs | 6 +-- components/layout/multicol.rs | 9 +++-- components/layout/table.rs | 24 +---------- components/layout/table_caption.rs | 2 +- components/layout/table_cell.rs | 2 +- components/layout/table_row.rs | 2 +- components/layout/table_rowgroup.rs | 2 +- components/layout/table_wrapper.rs | 25 ++---------- 11 files changed, 48 insertions(+), 125 deletions(-) diff --git a/components/layout/block.rs b/components/layout/block.rs index 39a0fe715f7..6396558aaeb 100644 --- a/components/layout/block.rs +++ b/components/layout/block.rs @@ -562,33 +562,20 @@ impl Encodable for BlockFlowFlags { } impl BlockFlow { - pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, fragment: Fragment) -> BlockFlow { + pub fn from_node_and_fragment(node: &ThreadSafeLayoutNode, + fragment: Fragment, + float_kind: Option) + -> BlockFlow { let writing_mode = node.style().writing_mode; BlockFlow { - base: BaseFlow::new(Some((*node).clone()), - writing_mode, - ForceNonfloatedFlag::ForceNonfloated), + base: BaseFlow::new(Some((*node).clone()), writing_mode, match float_kind { + Some(_) => ForceNonfloatedFlag::FloatIfNecessary, + None => ForceNonfloatedFlag::ForceNonfloated, + }), fragment: fragment, inline_size_of_preceding_left_floats: Au(0), inline_size_of_preceding_right_floats: Au(0), - float: None, - flags: BlockFlowFlags::empty(), - } - } - - pub fn float_from_node_and_fragment(node: &ThreadSafeLayoutNode, - fragment: Fragment, - float_kind: FloatKind) - -> BlockFlow { - let writing_mode = node.style().writing_mode; - BlockFlow { - base: BaseFlow::new(Some((*node).clone()), - writing_mode, - ForceNonfloatedFlag::FloatIfNecessary), - fragment: fragment, - inline_size_of_preceding_left_floats: Au(0), - inline_size_of_preceding_right_floats: Au(0), - float: Some(box FloatedBlockInfo::new(float_kind)), + float: float_kind.map(|kind| box FloatedBlockInfo::new(kind)), flags: BlockFlowFlags::empty(), } } diff --git a/components/layout/construct.rs b/components/layout/construct.rs index 12f9900b088..6c4537e8b26 100644 --- a/components/layout/construct.rs +++ b/components/layout/construct.rs @@ -331,7 +331,7 @@ impl<'a> FlowConstructor<'a> { if child.is_table() { let fragment = Fragment::new(child_node, SpecificFragmentInfo::TableWrapper); let mut new_child = - FlowRef::new(box TableWrapperFlow::from_node_and_fragment(child_node, fragment)); + FlowRef::new(box TableWrapperFlow::from_node_and_fragment(child_node, fragment, None)); new_child.add_new_child(child.clone()); child.finish(); *child = new_child @@ -602,7 +602,7 @@ impl<'a> FlowConstructor<'a> { /// /// FIXME(pcwalton): It is not clear to me that there isn't a cleaner way to handle /// `