Auto merge of #7496 - servo:calc_, r=SimonSapin

Implement CSS3 Calc

This is #7185 with one commit added to make it build merged with master, which got support for the `ch` unit in the meantime.

<!-- Reviewable:start -->
[<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/7496)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2015-09-02 02:33:18 -06:00
commit a547ae6826
21 changed files with 873 additions and 63 deletions

View file

@ -327,7 +327,12 @@ impl CandidateBSizeIterator {
(LengthOrPercentageOrAuto::Percentage(percent), Some(block_container_block_size)) => {
MaybeAuto::Specified(block_container_block_size.scale_by(percent))
}
(LengthOrPercentageOrAuto::Percentage(_), None) | (LengthOrPercentageOrAuto::Auto, _) => MaybeAuto::Auto,
(LengthOrPercentageOrAuto::Calc(calc), Some(block_container_block_size)) => {
MaybeAuto::Specified(calc.length() + block_container_block_size.scale_by(calc.percentage()))
}
(LengthOrPercentageOrAuto::Percentage(_), None) |
(LengthOrPercentageOrAuto::Auto, _) |
(LengthOrPercentageOrAuto::Calc(_), _) => MaybeAuto::Auto,
(LengthOrPercentageOrAuto::Length(length), _) => MaybeAuto::Specified(length),
};
let max_block_size = match (fragment.style.max_block_size(), block_container_block_size) {
@ -342,6 +347,10 @@ impl CandidateBSizeIterator {
(LengthOrPercentage::Percentage(percent), Some(block_container_block_size)) => {
block_container_block_size.scale_by(percent)
}
(LengthOrPercentage::Calc(calc), Some(block_container_block_size)) => {
calc.length() + block_container_block_size.scale_by(calc.percentage())
}
(LengthOrPercentage::Calc(calc), None) => calc.length(),
(LengthOrPercentage::Percentage(_), None) => Au(0),
(LengthOrPercentage::Length(length), _) => length,
};
@ -1128,13 +1137,15 @@ impl BlockFlow {
let content_block_size = self.fragment.style().content_block_size();
match (content_block_size, containing_block_size) {
(LengthOrPercentageOrAuto::Calc(calc), Some(container_size)) => {
Some(container_size.scale_by(calc.percentage()) + calc.length())
}
(LengthOrPercentageOrAuto::Length(length), _) => Some(length),
(LengthOrPercentageOrAuto::Percentage(percent), Some(container_size)) => {
Some(container_size.scale_by(percent))
}
(LengthOrPercentageOrAuto::Percentage(_), None) => {
None
}
(LengthOrPercentageOrAuto::Percentage(_), None) |
(LengthOrPercentageOrAuto::Calc(_), None) |
(LengthOrPercentageOrAuto::Auto, None) => {
None
}

View file

@ -1959,6 +1959,8 @@ fn position_to_offset(position: LengthOrPercentage, Au(total_length): Au) -> f32
fmin(1.0, (length as f32) / (total_length as f32))
}
LengthOrPercentage::Percentage(percentage) => percentage as f32,
LengthOrPercentage::Calc(calc) =>
fmin(1.0, calc.percentage() + (calc.length().0 as f32) / (total_length as f32)),
}
}

View file

@ -167,6 +167,7 @@ impl FlexFlow {
}
(LengthOrPercentageOrAuto::Percentage(_), None) |
(LengthOrPercentageOrAuto::Auto, _) => None,
(LengthOrPercentageOrAuto::Calc(_), _) => None,
(LengthOrPercentageOrAuto::Length(length), _) => Some(length),
};

View file

@ -463,6 +463,10 @@ impl ReplacedImageFragmentInfo {
MaybeAuto::Specified(container_size.scale_by(pc))
}
(LengthOrPercentageOrAuto::Percentage(_), _, None) => MaybeAuto::Auto,
(LengthOrPercentageOrAuto::Calc(calc), _, Some(container_size)) => {
MaybeAuto::Specified(calc.length() + container_size.scale_by(calc.percentage()))
}
(LengthOrPercentageOrAuto::Calc(_), _, None) => MaybeAuto::Auto,
(LengthOrPercentageOrAuto::Auto, Some(dom_length), _) => MaybeAuto::Specified(dom_length),
(LengthOrPercentageOrAuto::Auto, None, _) => MaybeAuto::Auto,
}
@ -616,6 +620,10 @@ impl IframeFragmentInfo {
let computed_size = match (content_size, containing_size) {
(LengthOrPercentageOrAuto::Length(length), _) => length,
(LengthOrPercentageOrAuto::Percentage(pc), Some(container_size)) => container_size.scale_by(pc),
(LengthOrPercentageOrAuto::Calc(calc), Some(container_size)) => {
container_size.scale_by(calc.percentage()) + calc.length()
},
(LengthOrPercentageOrAuto::Calc(calc), None) => calc.length(),
(LengthOrPercentageOrAuto::Percentage(_), None) => default_size,
(LengthOrPercentageOrAuto::Auto, _) => default_size,
};
@ -1285,6 +1293,7 @@ impl Fragment {
}
(Some(dom_inline_size), _) => dom_inline_size,
(None, LengthOrPercentageOrAuto::Length(length)) => length,
(None, LengthOrPercentageOrAuto::Calc(calc)) => calc.length(),
};
result.union_block(&IntrinsicISizes {
minimum_inline_size: image_inline_size,

View file

@ -988,6 +988,11 @@ impl InlineFlow {
let percent_offset = line_height.scale_by(p);
offset_from_baseline = offset_from_baseline - percent_offset
}
vertical_align::T::Calc(calc) => {
let line_height = fragment.calculate_line_height(layout_context);
let percent_offset = line_height.scale_by(calc.percentage());
offset_from_baseline = offset_from_baseline - percent_offset - calc.length()
}
}
}
(offset_from_baseline - ascent, largest_size_updated)

View file

@ -379,6 +379,9 @@ impl MaybeAuto {
LengthOrPercentageOrAuto::Percentage(percent) => {
MaybeAuto::Specified(containing_length.scale_by(percent))
}
LengthOrPercentageOrAuto::Calc(calc) => {
MaybeAuto::Specified(calc.length() + containing_length.scale_by(calc.percentage()))
}
LengthOrPercentageOrAuto::Length(length) => MaybeAuto::Specified(length)
}
}
@ -416,7 +419,9 @@ pub fn specified_or_none(length: LengthOrPercentageOrNone, containing_length: Au
pub fn specified(length: LengthOrPercentage, containing_length: Au) -> Au {
match length {
LengthOrPercentage::Length(length) => length,
LengthOrPercentage::Percentage(p) => containing_length.scale_by(p)
LengthOrPercentage::Percentage(p) => containing_length.scale_by(p),
LengthOrPercentage::Calc(calc) =>
containing_length.scale_by(calc.percentage()) + calc.length(),
}
}

View file

@ -267,11 +267,13 @@ impl Flow for TableFlow {
self.column_intrinsic_inline_sizes.push(ColumnIntrinsicInlineSize {
minimum_length: match *specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Percentage(_) => Au(0),
LengthOrPercentageOrAuto::Length(length) => length,
},
percentage: match *specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Length(_) => 0.0,
LengthOrPercentageOrAuto::Percentage(percentage) => percentage,
},

View file

@ -280,6 +280,7 @@ impl Flow for TableRowFlow {
let child_column_inline_size = ColumnIntrinsicInlineSize {
minimum_length: match child_specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Percentage(_) => {
child_base.intrinsic_inline_sizes.minimum_inline_size
}
@ -287,6 +288,7 @@ impl Flow for TableRowFlow {
},
percentage: match child_specified_inline_size {
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Length(_) => 0.0,
LengthOrPercentageOrAuto::Percentage(percentage) => percentage,
},
@ -294,6 +296,7 @@ impl Flow for TableRowFlow {
constrained: match child_specified_inline_size {
LengthOrPercentageOrAuto::Length(_) => true,
LengthOrPercentageOrAuto::Auto |
LengthOrPercentageOrAuto::Calc(_) |
LengthOrPercentageOrAuto::Percentage(_) => false,
},
};

View file

@ -365,7 +365,8 @@ impl RawLayoutElementHelpers for Element {
match width {
LengthOrPercentageOrAuto::Auto => {}
LengthOrPercentageOrAuto::Percentage(percentage) => {
let width_value = specified::LengthOrPercentageOrAuto::Percentage(percentage);
let width_value =
specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage));
hints.push(from_declaration(
PropertyDeclaration::Width(SpecifiedValue(width_value))));
}
@ -388,7 +389,8 @@ impl RawLayoutElementHelpers for Element {
match height {
LengthOrPercentageOrAuto::Auto => {}
LengthOrPercentageOrAuto::Percentage(percentage) => {
let height_value = specified::LengthOrPercentageOrAuto::Percentage(percentage);
let height_value =
specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage));
hints.push(from_declaration(
PropertyDeclaration::Height(SpecifiedValue(height_value))));
}

View file

@ -4,6 +4,7 @@
#![feature(arc_unique)]
#![feature(box_syntax)]
#![feature(box_patterns)]
#![feature(core_intrinsics)]
#![feature(custom_attribute)]
#![feature(custom_derive)]

View file

@ -760,7 +760,7 @@ pub mod longhands {
pub mod computed_value {
use std::fmt;
use util::geometry::Au;
use values::CSSFloat;
use values::{CSSFloat, computed};
#[allow(non_camel_case_types)]
#[derive(PartialEq, Copy, Clone, HeapSizeOf)]
pub enum T {
@ -769,6 +769,7 @@ pub mod longhands {
% endfor
Length(Au),
Percentage(CSSFloat),
Calc(computed::Calc),
}
impl fmt::Debug for T {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -778,6 +779,7 @@ pub mod longhands {
% endfor
&T::Length(length) => write!(f, "{:?}", length),
&T::Percentage(number) => write!(f, "{}%", number),
&T::Calc(calc) => write!(f, "{:?}", calc)
}
}
}
@ -789,6 +791,7 @@ pub mod longhands {
% endfor
T::Length(value) => value.to_css(dest),
T::Percentage(percentage) => write!(dest, "{}%", percentage * 100.),
T::Calc(calc) => calc.to_css(dest),
}
}
}
@ -809,12 +812,12 @@ pub mod longhands {
% endfor
SpecifiedValue::LengthOrPercentage(value) => {
match value.to_computed_value(context) {
computed::LengthOrPercentage::Length(value) => {
computed_value::T::Length(value)
}
computed::LengthOrPercentage::Percentage(value) => {
computed_value::T::Percentage(value)
}
computed::LengthOrPercentage::Length(value) =>
computed_value::T::Length(value),
computed::LengthOrPercentage::Percentage(value) =>
computed_value::T::Percentage(value),
computed::LengthOrPercentage::Calc(value) =>
computed_value::T::Calc(value),
}
}
}
@ -1910,10 +1913,12 @@ pub mod longhands {
/// <length> | <percentage> | <absolute-size> | <relative-size>
pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
input.try(specified::LengthOrPercentage::parse_non_negative)
.map(|value| match value {
specified::LengthOrPercentage::Length(value) => value,
.and_then(|value| match value {
specified::LengthOrPercentage::Length(value) => Ok(value),
specified::LengthOrPercentage::Percentage(value) =>
specified::Length::FontRelative(specified::FontRelativeLength::Em(value))
Ok(specified::Length::FontRelative(specified::FontRelativeLength::Em(value.0))),
// FIXME(dzbarsky) handle calc for font-size
specified::LengthOrPercentage::Calc(_) => Err(())
})
.or_else(|()| {
match_ignore_ascii_case! { try!(input.expect_ident()),
@ -3984,6 +3989,7 @@ pub mod longhands {
}
pub fn parse_origin(_: &ParserContext, input: &mut Parser) -> Result<OriginParseResult,()> {
use values::specified::{LengthOrPercentage, Percentage};
let (mut horizontal, mut vertical, mut depth) = (None, None, None);
loop {
if let Err(_) = input.try(|input| {
@ -3992,37 +3998,37 @@ pub mod longhands {
token,
"left" => {
if horizontal.is_none() {
horizontal = Some(specified::LengthOrPercentage::Percentage(0.0))
horizontal = Some(LengthOrPercentage::Percentage(Percentage(0.0)))
} else {
return Err(())
}
},
"center" => {
if horizontal.is_none() {
horizontal = Some(specified::LengthOrPercentage::Percentage(0.5))
horizontal = Some(LengthOrPercentage::Percentage(Percentage(0.5)))
} else if vertical.is_none() {
vertical = Some(specified::LengthOrPercentage::Percentage(0.5))
vertical = Some(LengthOrPercentage::Percentage(Percentage(0.5)))
} else {
return Err(())
}
},
"right" => {
if horizontal.is_none() {
horizontal = Some(specified::LengthOrPercentage::Percentage(1.0))
horizontal = Some(LengthOrPercentage::Percentage(Percentage(1.0)))
} else {
return Err(())
}
},
"top" => {
if vertical.is_none() {
vertical = Some(specified::LengthOrPercentage::Percentage(0.0))
vertical = Some(LengthOrPercentage::Percentage(Percentage(0.0)))
} else {
return Err(())
}
},
"bottom" => {
if vertical.is_none() {
vertical = Some(specified::LengthOrPercentage::Percentage(1.0))
vertical = Some(LengthOrPercentage::Percentage(Percentage(1.0)))
} else {
return Err(())
}
@ -4031,13 +4037,13 @@ pub mod longhands {
}
Ok(())
}) {
match specified::LengthOrPercentage::parse(input) {
match LengthOrPercentage::parse(input) {
Ok(value) => {
if horizontal.is_none() {
horizontal = Some(value);
} else if vertical.is_none() {
vertical = Some(value);
} else if let specified::LengthOrPercentage::Length(length) = value {
} else if let LengthOrPercentage::Length(length) = value {
depth = Some(length);
} else {
break;
@ -4065,7 +4071,7 @@ pub mod longhands {
<%self:longhand name="transform-origin">
use values::computed::Context;
use values::specified::{Length, LengthOrPercentage};
use values::specified::{Length, LengthOrPercentage, Percentage};
use cssparser::ToCss;
use std::fmt;
@ -4121,8 +4127,8 @@ pub mod longhands {
pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue,()> {
let result = try!(super::parse_origin(context, input));
Ok(SpecifiedValue {
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(0.5)),
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(0.5)),
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
depth: result.depth.unwrap_or(Length::Absolute(Au(0))),
})
}
@ -4147,7 +4153,7 @@ pub mod longhands {
<%self:longhand name="perspective-origin">
use values::computed::Context;
use values::specified::LengthOrPercentage;
use values::specified::{LengthOrPercentage, Percentage};
use cssparser::ToCss;
use std::fmt;
@ -4197,8 +4203,8 @@ pub mod longhands {
match result.depth {
Some(_) => Err(()),
None => Ok(SpecifiedValue {
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(0.5)),
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(0.5)),
horizontal: result.horizontal.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
vertical: result.vertical.unwrap_or(LengthOrPercentage::Percentage(Percentage(0.5))),
})
}
}

View file

@ -358,19 +358,359 @@ pub mod specified {
}
}
#[derive(Clone, Debug)]
struct CalcSumNode {
products: Vec<CalcProductNode>,
}
#[derive(Clone, Debug)]
struct CalcProductNode {
values: Vec<CalcValueNode>
}
#[derive(Clone, Debug)]
enum CalcValueNode {
Length(Length),
Percentage(CSSFloat),
Number(CSSFloat),
Sum(Box<CalcSumNode>),
}
#[derive(Clone, Debug)]
struct SimplifiedSumNode {
values: Vec<SimplifiedValueNode>,
}
impl<'a> Mul<CSSFloat> for &'a SimplifiedSumNode {
type Output = SimplifiedSumNode;
#[inline]
fn mul(self, scalar: CSSFloat) -> SimplifiedSumNode {
SimplifiedSumNode {
values: self.values.iter().map(|p| p * scalar).collect()
}
}
}
#[derive(Clone, Debug)]
enum SimplifiedValueNode {
Length(Length),
Percentage(CSSFloat),
Number(CSSFloat),
Sum(Box<SimplifiedSumNode>),
}
impl<'a> Mul<CSSFloat> for &'a SimplifiedValueNode {
type Output = SimplifiedValueNode;
#[inline]
fn mul(self, scalar: CSSFloat) -> SimplifiedValueNode {
match self {
&SimplifiedValueNode::Length(l) => SimplifiedValueNode::Length(l * scalar),
&SimplifiedValueNode::Percentage(p) => SimplifiedValueNode::Percentage(p * scalar),
&SimplifiedValueNode::Number(n) => SimplifiedValueNode::Number(n * scalar),
&SimplifiedValueNode::Sum(box ref s) => {
let sum = s * scalar;
SimplifiedValueNode::Sum(box sum)
}
}
}
}
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub struct Calc {
pub absolute: Option<Au>,
pub vw: Option<ViewportPercentageLength>,
pub vh: Option<ViewportPercentageLength>,
pub vmin: Option<ViewportPercentageLength>,
pub vmax: Option<ViewportPercentageLength>,
pub em: Option<FontRelativeLength>,
pub ex: Option<FontRelativeLength>,
pub ch: Option<FontRelativeLength>,
pub rem: Option<FontRelativeLength>,
pub percentage: Option<Percentage>,
}
impl Calc {
fn parse_sum(input: &mut Parser) -> Result<CalcSumNode, ()> {
let mut products = Vec::new();
products.push(try!(Calc::parse_product(input)));
loop {
match input.next() {
Ok(Token::Delim('+')) => {
products.push(try!(Calc::parse_product(input)));
}
Ok(Token::Delim('-')) => {
let mut right = try!(Calc::parse_product(input));
right.values.push(CalcValueNode::Number(-1.));
products.push(right);
}
Ok(_) => return Err(()),
_ => break
}
}
Ok(CalcSumNode { products: products })
}
fn parse_product(input: &mut Parser) -> Result<CalcProductNode, ()> {
let mut values = Vec::new();
values.push(try!(Calc::parse_value(input)));
loop {
let position = input.position();
match input.next() {
Ok(Token::Delim('*')) => {
values.push(try!(Calc::parse_value(input)));
}
Ok(Token::Delim('/')) => {
if let Ok(Token::Number(ref value)) = input.next() {
if value.value == 0. {
return Err(());
}
values.push(CalcValueNode::Number(1. / value.value));
} else {
return Err(());
}
}
_ => {
input.reset(position);
break
}
}
}
Ok(CalcProductNode { values: values })
}
fn parse_value(input: &mut Parser) -> Result<CalcValueNode, ()> {
match input.next() {
Ok(Token::Number(ref value)) => Ok(CalcValueNode::Number(value.value)),
Ok(Token::Dimension(ref value, ref unit)) =>
Length::parse_dimension(value.value, unit).map(CalcValueNode::Length),
Ok(Token::Percentage(ref value)) =>
Ok(CalcValueNode::Percentage(value.unit_value)),
Ok(Token::ParenthesisBlock) => {
let result = try!(input.parse_nested_block(Calc::parse_sum));
Ok(CalcValueNode::Sum(box result))
},
_ => Err(())
}
}
fn simplify_value_to_number(node: &CalcValueNode) -> Option<CSSFloat> {
match node {
&CalcValueNode::Number(number) => Some(number),
&CalcValueNode::Sum(box ref sum) => Calc::simplify_sum_to_number(sum),
_ => None
}
}
fn simplify_sum_to_number(node: &CalcSumNode) -> Option<CSSFloat> {
let mut sum = 0.;
for ref product in &node.products {
match Calc::simplify_product_to_number(product) {
Some(number) => sum += number,
_ => return None
}
}
Some(sum)
}
fn simplify_product_to_number(node: &CalcProductNode) -> Option<CSSFloat> {
let mut product = 1.;
for ref value in &node.values {
match Calc::simplify_value_to_number(value) {
Some(number) => product *= number,
_ => return None
}
}
Some(product)
}
fn simplify_products_in_sum(node: &CalcSumNode) -> Result<SimplifiedValueNode, ()> {
let mut simplified = Vec::new();
for product in &node.products {
match try!(Calc::simplify_product(product)) {
SimplifiedValueNode::Sum(box sum) => simplified.push_all(&sum.values),
val => simplified.push(val),
}
}
if simplified.len() == 1 {
Ok(simplified[0].clone())
} else {
Ok(SimplifiedValueNode::Sum(box SimplifiedSumNode { values: simplified } ))
}
}
fn simplify_product(node: &CalcProductNode) -> Result<SimplifiedValueNode, ()> {
let mut multiplier = 1.;
let mut node_with_unit = None;
for node in &node.values {
match Calc::simplify_value_to_number(&node) {
Some(number) => multiplier *= number,
_ if node_with_unit.is_none() => {
node_with_unit = Some(match node {
&CalcValueNode::Sum(box ref sum) =>
try!(Calc::simplify_products_in_sum(sum)),
&CalcValueNode::Length(l) => SimplifiedValueNode::Length(l),
&CalcValueNode::Percentage(p) => SimplifiedValueNode::Percentage(p),
_ => unreachable!("Numbers should have been handled by simplify_value_to_nubmer")
})
},
_ => return Err(()),
}
}
match node_with_unit {
None => Ok(SimplifiedValueNode::Number(multiplier)),
Some(ref value) => Ok(value * multiplier)
}
}
pub fn parse(input: &mut Parser) -> Result<Calc, ()> {
let ast = try!(Calc::parse_sum(input));
let mut simplified = Vec::new();
for ref node in ast.products {
match try!(Calc::simplify_product(node)) {
SimplifiedValueNode::Sum(sum) => simplified.push_all(&sum.values),
value => simplified.push(value),
}
}
let mut absolute = None;
let mut vw = None;
let mut vh = None;
let mut vmax = None;
let mut vmin = None;
let mut em = None;
let mut ex = None;
let mut ch = None;
let mut rem = None;
let mut percentage = None;
let mut number = None;
for value in simplified {
match value {
SimplifiedValueNode::Percentage(p) =>
percentage = Some(percentage.unwrap_or(0.) + p),
SimplifiedValueNode::Length(Length::Absolute(Au(au))) =>
absolute = Some(absolute.unwrap_or(0) + au),
SimplifiedValueNode::Length(Length::ViewportPercentage(v)) =>
match v {
ViewportPercentageLength::Vw(val) =>
vw = Some(vw.unwrap_or(0.) + val),
ViewportPercentageLength::Vh(val) =>
vh = Some(vh.unwrap_or(0.) + val),
ViewportPercentageLength::Vmin(val) =>
vmin = Some(vmin.unwrap_or(0.) + val),
ViewportPercentageLength::Vmax(val) =>
vmax = Some(vmax.unwrap_or(0.) + val),
},
SimplifiedValueNode::Length(Length::FontRelative(f)) =>
match f {
FontRelativeLength::Em(val) =>
em = Some(em.unwrap_or(0.) + val),
FontRelativeLength::Ex(val) =>
ex = Some(ex.unwrap_or(0.) + val),
FontRelativeLength::Ch(val) =>
ch = Some(ch.unwrap_or(0.) + val),
FontRelativeLength::Rem(val) =>
rem = Some(rem.unwrap_or(0.) + val),
},
SimplifiedValueNode::Number(val) => number = Some(number.unwrap_or(0.) + val),
_ => unreachable!()
}
}
Ok(Calc {
absolute: absolute.map(Au),
vw: vw.map(ViewportPercentageLength::Vw),
vh: vh.map(ViewportPercentageLength::Vh),
vmax: vmax.map(ViewportPercentageLength::Vmax),
vmin: vmin.map(ViewportPercentageLength::Vmin),
em: em.map(FontRelativeLength::Em),
ex: ex.map(FontRelativeLength::Ex),
ch: ch.map(FontRelativeLength::Ch),
rem: rem.map(FontRelativeLength::Rem),
percentage: percentage.map(Percentage),
})
}
}
impl ToCss for Calc {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
macro_rules! count {
( $( $val:ident ),* ) => {
{
let mut count = 0;
$(
if let Some(_) = self.$val {
count += 1;
}
)*
count
}
};
}
macro_rules! serialize {
( $( $val:ident ),* ) => {
{
let mut first_value = true;
$(
if let Some(val) = self.$val {
if !first_value {
try!(write!(dest, " + "));
} else {
first_value = false;
}
try!(val.to_css(dest));
}
)*
}
};
}
let count = count!(ch, em, ex, absolute, rem, vh, vmax, vmin, vw, percentage);
assert!(count > 0);
if count > 1 {
try!(write!(dest, "calc("));
}
serialize!(ch, em, ex, absolute, rem, vh, vmax, vmin, vw, percentage);
if count > 1 {
try!(write!(dest, ")"));
}
Ok(())
}
}
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub struct Percentage(pub CSSFloat); // [0 .. 100%] maps to [0.0 .. 1.0]
impl ToCss for Percentage {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
write!(dest, "{}%", self.0 * 100.)
}
}
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub enum LengthOrPercentage {
Length(Length),
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
Percentage(Percentage),
Calc(Calc),
}
impl ToCss for LengthOrPercentage {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match self {
&LengthOrPercentage::Length(length) => length.to_css(dest),
&LengthOrPercentage::Percentage(percentage)
=> write!(dest, "{}%", percentage * 100.),
&LengthOrPercentage::Percentage(percentage) => percentage.to_css(dest),
&LengthOrPercentage::Calc(calc) => calc.to_css(dest),
}
}
}
@ -386,9 +726,13 @@ pub mod specified {
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentage::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentage::Percentage(value.unit_value)),
Ok(LengthOrPercentage::Percentage(Percentage(value.unit_value))),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentage::Length(Length::Absolute(Au(0)))),
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
let calc = try!(input.parse_nested_block(Calc::parse));
Ok(LengthOrPercentage::Calc(calc))
},
_ => Err(())
}
}
@ -406,17 +750,18 @@ pub mod specified {
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub enum LengthOrPercentageOrAuto {
Length(Length),
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
Percentage(Percentage),
Auto,
Calc(Calc),
}
impl ToCss for LengthOrPercentageOrAuto {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match self {
&LengthOrPercentageOrAuto::Length(length) => length.to_css(dest),
&LengthOrPercentageOrAuto::Percentage(percentage)
=> write!(dest, "{}%", percentage * 100.),
&LengthOrPercentageOrAuto::Percentage(percentage) => percentage.to_css(dest),
&LengthOrPercentageOrAuto::Auto => dest.write_str("auto"),
&LengthOrPercentageOrAuto::Calc(calc) => calc.to_css(dest),
}
}
}
@ -429,11 +774,15 @@ pub mod specified {
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentageOrAuto::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentageOrAuto::Percentage(value.unit_value)),
Ok(LengthOrPercentageOrAuto::Percentage(Percentage(value.unit_value))),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentageOrAuto::Length(Length::Absolute(Au(0)))),
Token::Ident(ref value) if value.eq_ignore_ascii_case("auto") =>
Ok(LengthOrPercentageOrAuto::Auto),
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
let calc = try!(input.parse_nested_block(Calc::parse));
Ok(LengthOrPercentageOrAuto::Calc(calc))
},
_ => Err(())
}
}
@ -450,7 +799,7 @@ pub mod specified {
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub enum LengthOrPercentageOrNone {
Length(Length),
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
Percentage(Percentage),
None,
}
@ -458,8 +807,7 @@ pub mod specified {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match self {
&LengthOrPercentageOrNone::Length(length) => length.to_css(dest),
&LengthOrPercentageOrNone::Percentage(percentage) =>
write!(dest, "{}%", percentage * 100.),
&LengthOrPercentageOrNone::Percentage(percentage) => percentage.to_css(dest),
&LengthOrPercentageOrNone::None => dest.write_str("none"),
}
}
@ -472,7 +820,7 @@ pub mod specified {
Token::Dimension(ref value, ref unit) if context.is_ok(value.value) =>
Length::parse_dimension(value.value, unit).map(LengthOrPercentageOrNone::Length),
Token::Percentage(ref value) if context.is_ok(value.unit_value) =>
Ok(LengthOrPercentageOrNone::Percentage(value.unit_value)),
Ok(LengthOrPercentageOrNone::Percentage(Percentage(value.unit_value))),
Token::Number(ref value) if value.value == 0. =>
Ok(LengthOrPercentageOrNone::Length(Length::Absolute(Au(0)))),
Token::Ident(ref value) if value.eq_ignore_ascii_case("none") =>
@ -534,7 +882,7 @@ pub mod specified {
#[derive(Clone, PartialEq, Copy)]
pub enum PositionComponent {
Length(Length),
Percentage(CSSFloat), // [0 .. 100%] maps to [0.0 .. 1.0]
Percentage(Percentage),
Center,
Left,
Right,
@ -549,7 +897,7 @@ pub mod specified {
.map(PositionComponent::Length)
}
Token::Percentage(ref value) => {
Ok(PositionComponent::Percentage(value.unit_value))
Ok(PositionComponent::Percentage(Percentage(value.unit_value)))
}
Token::Number(ref value) if value.value == 0. => {
Ok(PositionComponent::Length(Length::Absolute(Au(0))))
@ -572,11 +920,11 @@ pub mod specified {
match self {
PositionComponent::Length(x) => LengthOrPercentage::Length(x),
PositionComponent::Percentage(x) => LengthOrPercentage::Percentage(x),
PositionComponent::Center => LengthOrPercentage::Percentage(0.5),
PositionComponent::Center => LengthOrPercentage::Percentage(Percentage(0.5)),
PositionComponent::Left |
PositionComponent::Top => LengthOrPercentage::Percentage(0.0),
PositionComponent::Top => LengthOrPercentage::Percentage(Percentage(0.0)),
PositionComponent::Right |
PositionComponent::Bottom => LengthOrPercentage::Percentage(1.0),
PositionComponent::Bottom => LengthOrPercentage::Percentage(Percentage(1.0)),
}
}
}
@ -943,10 +1291,66 @@ pub mod computed {
}
}
#[derive(Clone, PartialEq, Copy, Debug, HeapSizeOf)]
pub struct Calc {
length: Option<Au>,
percentage: Option<CSSFloat>,
}
impl Calc {
pub fn length(&self) -> Au {
self.length.unwrap_or(Au(0))
}
pub fn percentage(&self) -> CSSFloat {
self.percentage.unwrap_or(0.)
}
}
impl ::cssparser::ToCss for Calc {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
match (self.length, self.percentage) {
(None, Some(p)) => write!(dest, "{}%", p * 100.),
(Some(l), None) => write!(dest, "{}px", Au::to_px(l)),
(Some(l), Some(p)) => write!(dest, "calc({}px + {}%)", Au::to_px(l), p * 100.),
_ => unreachable!()
}
}
}
impl ToComputedValue for specified::Calc {
type ComputedValue = Calc;
fn to_computed_value(&self, context: &Context) -> Calc {
let mut length = None;
if let Some(absolute) = self.absolute {
length = Some(length.unwrap_or(Au(0)) + absolute);
}
for val in &[self.vw, self.vh, self.vmin, self.vmax] {
if let Some(val) = *val {
length = Some(length.unwrap_or(Au(0)) +
val.to_computed_value(context.viewport_size));
}
}
for val in &[self.ch, self.em, self.ex, self.rem] {
if let Some(val) = *val {
length = Some(length.unwrap_or(Au(0)) +
val.to_computed_value(context.font_size, context.root_font_size));
}
}
Calc { length: length, percentage: self.percentage.map(|p| p.0) }
}
}
#[derive(PartialEq, Clone, Copy, HeapSizeOf)]
pub enum LengthOrPercentage {
Length(Au),
Percentage(CSSFloat),
Calc(Calc),
}
impl LengthOrPercentage {
@ -960,6 +1364,7 @@ pub mod computed {
match self {
&LengthOrPercentage::Length(length) => write!(f, "{:?}", length),
&LengthOrPercentage::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
&LengthOrPercentage::Calc(calc) => write!(f, "{:?}", calc),
}
}
}
@ -973,7 +1378,10 @@ pub mod computed {
LengthOrPercentage::Length(value.to_computed_value(context))
}
specified::LengthOrPercentage::Percentage(value) => {
LengthOrPercentage::Percentage(value)
LengthOrPercentage::Percentage(value.0)
}
specified::LengthOrPercentage::Calc(calc) => {
LengthOrPercentage::Calc(calc.to_computed_value(context))
}
}
}
@ -985,6 +1393,7 @@ pub mod computed {
&LengthOrPercentage::Length(length) => length.to_css(dest),
&LengthOrPercentage::Percentage(percentage)
=> write!(dest, "{}%", percentage * 100.),
&LengthOrPercentage::Calc(calc) => calc.to_css(dest),
}
}
}
@ -994,6 +1403,7 @@ pub mod computed {
Length(Au),
Percentage(CSSFloat),
Auto,
Calc(Calc),
}
impl fmt::Debug for LengthOrPercentageOrAuto {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@ -1001,6 +1411,7 @@ pub mod computed {
&LengthOrPercentageOrAuto::Length(length) => write!(f, "{:?}", length),
&LengthOrPercentageOrAuto::Percentage(percentage) => write!(f, "{}%", percentage * 100.),
&LengthOrPercentageOrAuto::Auto => write!(f, "auto"),
&LengthOrPercentageOrAuto::Calc(calc) => write!(f, "{:?}", calc),
}
}
}
@ -1015,11 +1426,14 @@ pub mod computed {
LengthOrPercentageOrAuto::Length(value.to_computed_value(context))
}
specified::LengthOrPercentageOrAuto::Percentage(value) => {
LengthOrPercentageOrAuto::Percentage(value)
LengthOrPercentageOrAuto::Percentage(value.0)
}
specified::LengthOrPercentageOrAuto::Auto => {
LengthOrPercentageOrAuto::Auto
}
specified::LengthOrPercentageOrAuto::Calc(calc) => {
LengthOrPercentageOrAuto::Calc(calc.to_computed_value(context))
}
}
}
}
@ -1031,6 +1445,7 @@ pub mod computed {
&LengthOrPercentageOrAuto::Percentage(percentage)
=> write!(dest, "{}%", percentage * 100.),
&LengthOrPercentageOrAuto::Auto => dest.write_str("auto"),
&LengthOrPercentageOrAuto::Calc(calc) => calc.to_css(dest),
}
}
}
@ -1061,7 +1476,7 @@ pub mod computed {
LengthOrPercentageOrNone::Length(value.to_computed_value(context))
}
specified::LengthOrPercentageOrNone::Percentage(value) => {
LengthOrPercentageOrNone::Percentage(value)
LengthOrPercentageOrNone::Percentage(value.0)
}
specified::LengthOrPercentageOrNone::None => {
LengthOrPercentageOrNone::None

View file

@ -9,7 +9,8 @@ use parser::{ParserContext, log_css_error};
use properties::longhands;
use stylesheets::Origin;
use util::geometry::{Au, PagePx, ViewportPx};
use values::specified::{AllowedNumericType, Length, LengthOrPercentageOrAuto};
use values::computed::{Context, ToComputedValue};
use values::specified::{AllowedNumericType, LengthOrPercentageOrAuto};
use std::ascii::AsciiExt;
use std::collections::hash_map::{Entry, HashMap};
@ -420,23 +421,42 @@ impl ViewportConstraints {
let initial_viewport = Size2D::new(Au::from_f32_px(initial_viewport.width.get()),
Au::from_f32_px(initial_viewport.height.get()));
let context = Context {
is_root_element: false,
viewport_size: initial_viewport,
inherited_font_weight: longhands::font_weight::get_initial_value(),
inherited_font_size: longhands::font_size::get_initial_value(),
inherited_text_decorations_in_effect: longhands::_servo_text_decorations_in_effect::get_initial_value(),
font_size: longhands::font_size::get_initial_value(),
root_font_size: longhands::font_size::get_initial_value(),
display: longhands::display::get_initial_value(),
color: longhands::color::get_initial_value(),
text_decoration: longhands::text_decoration::get_initial_value(),
overflow_x: longhands::overflow_x::get_initial_value(),
overflow_y: longhands::overflow_y::get_initial_value(),
positioned: false,
floated: false,
border_top_present: false,
border_right_present: false,
border_bottom_present: false,
border_left_present: false,
outline_style_present: false,
};
macro_rules! to_pixel_length {
($value:ident, $dimension:ident) => {
if let Some($value) = $value {
match $value {
LengthOrPercentageOrAuto::Length(ref value) => Some(match value {
&Length::Absolute(length) => length,
&Length::FontRelative(length) => {
let initial_font_size = longhands::font_size::get_initial_value();
length.to_computed_value(initial_font_size, initial_font_size)
}
&Length::ViewportPercentage(length) =>
length.to_computed_value(initial_viewport),
_ => unreachable!()
}),
LengthOrPercentageOrAuto::Length(value) =>
Some(value.to_computed_value(&context)),
LengthOrPercentageOrAuto::Percentage(value) =>
Some(initial_viewport.$dimension.scale_by(value)),
Some(initial_viewport.$dimension.scale_by(value.0)),
LengthOrPercentageOrAuto::Auto => None,
LengthOrPercentageOrAuto::Calc(calc) => {
let calc = calc.to_computed_value(&context);
Some(initial_viewport.$dimension.scale_by(calc.percentage()) + calc.length())
}
}
} else {
None

View file

@ -82,6 +82,7 @@ flaky_cpu == append_style_a.html append_style_b.html
== box_sizing_border_box_a.html box_sizing_border_box_ref.html
== box_sizing_sanity_check_a.html box_sizing_sanity_check_ref.html
== br.html br-ref.html
== calc-basic.html calc-basic-ref.html
== canvas_as_block_element_a.html canvas_as_block_element_ref.html
== canvas_linear_gradient_a.html canvas_linear_gradient_ref.html
== canvas_radial_gradient_a.html canvas_radial_gradient_ref.html

View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<div id="outer">
<div id="inner">
</div>
</div>
<style>
#outer {
height: 100px;
width: 200px;
}
#inner {
height: 100%;
width: 110px;
background-color: green;
}
</style>

19
tests/ref/calc-basic.html Normal file
View file

@ -0,0 +1,19 @@
<!DOCTYPE html>
<div id="outer">
<div id="inner">
</div>
</div>
<style>
#outer {
height: 100px;
width: 200px;
}
#inner {
height: 100%;
width: calc(50px + 30%);
background-color: green;
}
</style>

View file

@ -1,5 +1,6 @@
<!DOCTYPE html>
<title>font-size (issues #1435, #3417)</title>
<style>p { margin: .5em }</style>
<body style="font-size: 20px">
<p style="font-size: 24pt">24pt is 32px.
<p style="font-size: 2em">2em is 40px.

View file

@ -1,5 +1,6 @@
<!DOCTYPE html>
<title>font-size (issues #1435, #3417)</title>
<style>p { margin: .5em }</style>
<body>
<p style="font-size: 32px">24pt is 32px.
<p style="font-size: 40px">2em is 40px.

View file

@ -299,6 +299,12 @@
"url": "/_mozilla/mozilla/body_listener.html"
}
],
"mozilla/calc.html": [
{
"path": "mozilla/calc.html",
"url": "/_mozilla/mozilla/calc.html"
}
],
"mozilla/caption.html": [
{
"path": "mozilla/caption.html",

View file

@ -0,0 +1,128 @@
[calc.html]
type: testharness
[calc(1px + 1pt + 1pc + 1in + 1cm + 1mm)]
expected: FAIL
[calc(0px + 0pt + 0pc + 0in + 0cm + 0mm + 0rem + 0em + 0ex + 0% + 0vw + 0vh + 0vmin + 0vmax)]
expected: FAIL
[calc for border-width]
expected: FAIL
[calc for border-top-width]
expected: FAIL
[calc for border-left-width]
expected: FAIL
[calc for border-right-width]
expected: FAIL
[calc for border-bottom-width]
expected: FAIL
[calc for outline-width]
expected: FAIL
[calc for outline-offset]
expected: FAIL
[calc for letter-spacing]
expected: FAIL
[calc for word-spacing]
expected: FAIL
[calc for border-spacing]
expected: FAIL
[calc for column-width]
expected: FAIL
[calc for column-gap]
expected: FAIL
[calc for perspective]
expected: FAIL
[calc for border-top-left-radius]
expected: FAIL
[calc for border-bottom-left-radius]
expected: FAIL
[calc for border-top-right-radius]
expected: FAIL
[calc for border-bottom-right-radius]
expected: FAIL
[calc for top]
expected: FAIL
[calc for left]
expected: FAIL
[calc for bottom]
expected: FAIL
[calc for right]
expected: FAIL
[calc for width]
expected: FAIL
[calc for height]
expected: FAIL
[calc for min-width]
expected: FAIL
[calc for min-height]
expected: FAIL
[calc for max-width]
expected: FAIL
[calc for max-height]
expected: FAIL
[calc for line-height]
expected: FAIL
[calc for vertical-align]
expected: FAIL
[calc for background-position]
expected: FAIL
[calc for background-size]
expected: FAIL
[calc for font-size]
expected: FAIL
[calc for text-indent]
expected: FAIL
[calc for transform-origin]
expected: FAIL
[calc for perspective-origin]
expected: FAIL
[calc for transition-delay]
expected: FAIL
[calc for z-index]
expected: FAIL
[calc for column-count]
expected: FAIL
[calc for opacity]
expected: FAIL
[calc for transition-duration]
expected: FAIL

View file

@ -0,0 +1,153 @@
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<style>
#outer {
width: 1000px;
}
</style>
</head>
<body>
<div id="outer">FOO<div id="inner"></div></div>
<script>
var div = document.getElementById('inner');
var widthTests = [
['calc(10px)', '10px', '10px'],
// Basic Arithmetic
['calc(10px + 10px)', '20px', '20px'],
['calc(10px - 5px)', '5px', '5px'],
['calc(2 * 10px)', '20px', '20px'],
['calc(10px / 2)', '5px', '5px'],
// Parse ok
['calc(20px/2)', '10px', '10px'],
['calc(10px*2)', '20px', '20px'],
// Parse errors - value left over from previous test
['calc(10px-10px)', '20px', '20px'],
['calc(5px+5px)', '20px', '20px'],
// Combining units
['calc(10px + 10em)', 'calc(10em + 10px)', '170px'],
['calc(10px + 10em - 10px)', 'calc(10em + 0px)', '160px'],
// Fold absolute units
['calc(1px + 1pt + 1pc + 1in + 1cm + 1mm)', '155.88333333333333px', '155.88333333333333px'],
// Alphabetical order
['calc(0px + 0pt + 0pc + 0in + 0cm + 0mm + 0rem + 0em + 0ex + 0% + 0vw + 0vh + 0vmin + 0vmax)',
'calc(0em + 0ex + 0px + 0rem + 0vh + 0vmax + 0vmin + 0vw + 0%)',
'0px'],
// Simplification
['calc((2 - 1) * 10px)', '10px', '10px'],
['calc(((3 - 1) * (8 + 4)) * 10px)', '240px', '240px'],
['calc(5 * (20px / 2 + 7 * (3em + 12px/4 + (8 - 2) * 2rem)))', 'calc(105em + 155px + 420rem)', '8555px'],
];
widthTests.forEach(function(item) {
test(function() {
div.style.setProperty('width', item[0]);
assert_equals(div.style.getPropertyValue('width'), item[1]);
assert_equals(window.getComputedStyle(div).getPropertyValue('width'), item[2]);
}, item[0]);
});
var lengthProperties = [
'border-width',
'border-top-width',
'border-left-width',
'border-right-width',
'border-bottom-width',
'outline-width',
'outline-offset',
'letter-spacing',
'word-spacing',
'border-spacing',
'column-width',
'column-gap',
'perspective',
];
lengthProperties.forEach(function(prop) {
test(function() {
div.style.setProperty(prop, 'calc(1px)');
assert_equals(div.style.getPropertyValue(prop), '1px');
assert_equals(window.getComputedStyle(div).getPropertyValue(prop), '1px');
}, 'calc for ' + prop);
});
var lengthOrPercentageProperties = [
'border-top-left-radius',
'border-bottom-left-radius',
'border-top-right-radius',
'border-bottom-right-radius',
'top',
'left',
'bottom',
'right',
'width',
'height',
'min-width',
'min-height',
'max-width',
'max-height',
'line-height',
'vertical-align',
'background-position',
'background-size',
'font-size',
'text-indent',
'transform-origin',
'perspective-origin',
];
lengthOrPercentageProperties.forEach(function(prop) {
test(function() {
div.style.setProperty(prop, 'calc(1px + 0%)');
assert_equals(div.style.getPropertyValue(prop), 'calc(1px + 0%)');
assert_equals(window.getComputedStyle(div).getPropertyValue(prop), '1px');
}, 'calc for ' + prop);
});
var timeProperties = [
'transition-delay',
];
timeProperties.forEach(function(prop) {
test(function() {
div.style.setProperty(prop, 'calc(1s)');
assert_equals(div.style.getPropertyValue(prop), '1s');
assert_equals(window.getComputedStyle(div).getPropertyValue(prop), '1s');
}, 'calc for ' + prop);
});
var numberProperties = [
'z-index',
'column-count',
'opacity',
'transition-duration',
];
numberProperties.forEach(function(prop) {
test(function() {
div.style.setProperty(prop, 'calc(1)');
assert_equals(div.style.getPropertyValue(prop), '1');
assert_equals(window.getComputedStyle(div).getPropertyValue(prop), '1');
}, 'calc for ' + prop);
});
/* TODO: test these:
counter-increment, counter-reset,
color, box-shadow, clip, text-shadow, transform
transition-timing-function
*/
</script>
</head>
</html>