layout: Implement border-spacing per CSS 2.1 § 17.6.1 and the legacy

`cellspacing` attribute per HTML5 § 14.3.9.

Table layout code has been refactored to push the spacing down to
rowgroups and rows; this will aid the implementation of
`border-collapse` as well.

This commit also fixes two nasty issues in table layout:

* In fixed layout, extra space would not be divided among columns that
  had auto width but had nonzero minimum width.

* In automatic layout, extra space would be distributed to constrained
  columns as well even if unconstrained columns with percentage equal to
  zero were present.
This commit is contained in:
Patrick Walton 2014-12-17 16:44:37 -08:00
parent e8f1a046c6
commit 586c12ccc4
26 changed files with 690 additions and 208 deletions

View file

@ -36,6 +36,7 @@ git = "https://github.com/servo/string-cache"
[dependencies]
text_writer = "0.1.1"
encoding = "0.2"
rustc-serialize = "0.2"
matches = "0.1"
url = "0.2.16"
mod_path = "0.1"

View file

@ -14,7 +14,7 @@ use values::specified::CSSColor;
use values::{CSSFloat, specified};
use properties::DeclaredValue::SpecifiedValue;
use properties::PropertyDeclaration;
use properties::longhands;
use properties::longhands::{self, border_spacing};
use selector_matching::Stylist;
use cssparser::Color;
@ -43,6 +43,8 @@ pub enum IntegerAttribute {
pub enum UnsignedIntegerAttribute {
/// `<td border>`
Border,
/// `<table cellspacing>`
CellSpacing,
/// `<td colspan>`
ColSpan,
}
@ -143,6 +145,21 @@ impl PresentationalHintSynthesis for Stylist {
element,
matching_rules_list,
shareable);
match element.get_unsigned_integer_attribute(
UnsignedIntegerAttribute::CellSpacing) {
None => {}
Some(length) => {
let width_value = specified::Length::Absolute(Au::from_px(length as int));
matching_rules_list.vec_push(from_declaration(
PropertyDeclaration::BorderSpacing(
SpecifiedValue(
border_spacing::SpecifiedValue {
horizontal: width_value,
vertical: width_value,
}))));
*shareable = false
}
}
}
name if *name == atom!("body") || *name == atom!("tr") || *name == atom!("thead") ||
*name == atom!("tbody") || *name == atom!("tfoot") => {

View file

@ -28,6 +28,7 @@ extern crate cssparser;
extern crate matches;
extern crate encoding;
extern crate "rustc-serialize" as rustc_serialize;
extern crate string_cache;
extern crate selectors;

View file

@ -2020,6 +2020,89 @@ pub mod longhands {
${single_keyword("caption-side", "top bottom")}
${single_keyword("border-collapse", "separate collapse", experimental=True)}
<%self:longhand name="border-spacing">
use values::computed::{Context, ToComputedValue};
use cssparser::ToCss;
use text_writer::{self, TextWriter};
use util::geometry::Au;
pub mod computed_value {
use util::geometry::Au;
#[derive(Clone, Copy, Debug, PartialEq, RustcEncodable)]
pub struct T {
pub horizontal: Au,
pub vertical: Au,
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SpecifiedValue {
pub horizontal: specified::Length,
pub vertical: specified::Length,
}
#[inline]
pub fn get_initial_value() -> computed_value::T {
computed_value::T {
horizontal: Au(0),
vertical: Au(0),
}
}
impl ToCss for SpecifiedValue {
fn to_css<W>(&self, dest: &mut W) -> text_writer::Result where W: TextWriter {
try!(self.horizontal.to_css(dest));
try!(dest.write_str(" "));
self.vertical.to_css(dest)
}
}
impl ToComputedValue for SpecifiedValue {
type ComputedValue = computed_value::T;
#[inline]
fn to_computed_value(&self, context: &Context) -> computed_value::T {
computed_value::T {
horizontal: self.horizontal.to_computed_value(context),
vertical: self.vertical.to_computed_value(context),
}
}
}
pub fn parse(_: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
let mut lengths = [ None, None ];
for i in range(0, 2) {
match specified::Length::parse_non_negative(input) {
Err(()) => break,
Ok(length) => lengths[i] = Some(length),
}
}
if input.next().is_ok() {
return Err(())
}
match (lengths[0], lengths[1]) {
(None, None) => Err(()),
(Some(length), None) => {
Ok(SpecifiedValue {
horizontal: length,
vertical: length,
})
}
(Some(horizontal), Some(vertical)) => {
Ok(SpecifiedValue {
horizontal: horizontal,
vertical: vertical,
})
}
(None, Some(_)) => panic!("shouldn't happen"),
}
}
</%self:longhand>
// CSS 2.1, Section 18 - User interface

View file

@ -193,16 +193,19 @@ impl Stylist {
let mut shareable = true;
// Step 1: Virtual rules that are synthesized from legacy HTML attributes.
self.synthesize_presentational_hints_for_legacy_attributes(element,
applicable_declarations,
&mut shareable);
// Step 2: Normal rules.
// Step 1: Normal user-agent rules.
map.user_agent.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
// Step 2: Presentational hints.
self.synthesize_presentational_hints_for_legacy_attributes(element,
applicable_declarations,
&mut shareable);
// Step 3: User and author normal rules.
map.user.normal.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
@ -212,27 +215,27 @@ impl Stylist {
applicable_declarations,
&mut shareable);
// Step 3: Normal style attributes.
// Step 4: Normal style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(
GenericDeclarationBlock::from_declarations(sa.normal.clone()))
});
// Step 4: Author-supplied `!important` rules.
// Step 5: Author-supplied `!important` rules.
map.author.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
&mut shareable);
// Step 5: `!important` style attributes.
// Step 6: `!important` style attributes.
style_attribute.map(|sa| {
shareable = false;
applicable_declarations.vec_push(
GenericDeclarationBlock::from_declarations(sa.important.clone()))
});
// Step 6: User and UA `!important` rules.
// Step 7: User and UA `!important` rules.
map.user.important.get_all_matching_rules(element,
parent_bf,
applicable_declarations,
@ -277,3 +280,4 @@ impl PerPseudoElementSelectorMap {
}
}
}