style: Respect calc for percentages.

This commit is contained in:
Emilio Cobos Álvarez 2017-07-14 13:18:29 +02:00
parent 465e6f14fe
commit 310be02ba8
No known key found for this signature in database
GPG key ID: 056B727BB9C1027C
28 changed files with 308 additions and 176 deletions

View file

@ -20,7 +20,7 @@ use stylesheets::CssRuleType;
use super::{AllowQuirks, Number, ToComputedValue};
use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, None_, Normal};
use values::ExtremumLength;
use values::computed::{ComputedValueAsSpecified, Context};
use values::computed::{self, Context};
use values::specified::calc::CalcNode;
pub use values::specified::calc::CalcLengthOrPercentage;
@ -701,39 +701,81 @@ impl<T: Parse> Either<Length, T> {
}
/// A percentage value.
///
/// [0 .. 100%] maps to [0.0 .. 1.0]
///
/// FIXME(emilio): There's no standard property that requires a `<percentage>`
/// without requiring also a `<length>`. If such a property existed, we'd need
/// to add special handling for `calc()` and percentages in here in the same way
/// as for `Angle` and `Time`, but the lack of this this is otherwise
/// undistinguishable (we handle it correctly from `CalcLengthOrPercentage`).
///
/// As of today, only `-moz-image-rect` supports percentages without length.
/// This is not a regression, and that's a non-standard extension anyway, so I'm
/// not implementing it for now.
#[derive(Clone, Copy, Debug, Default, HasViewportPercentage, PartialEq)]
#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
pub struct Percentage(pub CSSFloat);
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
pub struct Percentage {
/// The percentage value as a float.
///
/// [0 .. 100%] maps to [0.0 .. 1.0]
value: CSSFloat,
/// If this percentage came from a calc() expression, this tells how
/// clamping should be done on the value.
calc_clamping_mode: Option<AllowedNumericType>,
}
no_viewport_percentage!(Percentage);
impl ToCss for Percentage {
fn to_css<W>(&self, dest: &mut W) -> fmt::Result
where W: fmt::Write,
{
write!(dest, "{}%", self.0 * 100.)
if self.calc_clamping_mode.is_some() {
dest.write_str("calc(")?;
}
write!(dest, "{}%", self.value * 100.)?;
if self.calc_clamping_mode.is_some() {
dest.write_str(")")?;
}
Ok(())
}
}
impl Percentage {
/// Create a percentage from a numeric value.
pub fn new(value: CSSFloat) -> Self {
Self {
value,
calc_clamping_mode: None,
}
}
/// Get the underlying value for this float.
pub fn get(&self) -> CSSFloat {
self.calc_clamping_mode.map_or(self.value, |mode| mode.clamp(self.value))
}
/// Reverse this percentage, preserving calc-ness.
///
/// For example: If it was 20%, convert it into 80%.
pub fn reverse(&mut self) {
let new_value = 1. - self.value;
self.value = new_value;
}
/// Parse a specific kind of percentage.
pub fn parse_with_clamping_mode<'i, 't>(context: &ParserContext,
input: &mut Parser<'i, 't>,
num_context: AllowedNumericType)
-> Result<Self, ParseError<'i>> {
pub fn parse_with_clamping_mode<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
num_context: AllowedNumericType,
) -> Result<Self, ParseError<'i>> {
match input.next()? {
Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
Ok(Percentage(unit_value))
Ok(Percentage::new(unit_value))
}
Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
let result = input.parse_nested_block(|i| {
CalcNode::parse_percentage(context, i)
})?;
// TODO(emilio): -moz-image-rect is the only thing that uses
// the clamping mode... I guess we could disallow it...
Ok(Percentage {
value: result,
calc_clamping_mode: Some(num_context),
})
}
t => Err(BasicParseError::UnexpectedToken(t).into())
}
@ -749,32 +791,39 @@ impl Percentage {
/// 0%
#[inline]
pub fn zero() -> Self {
Percentage(0.)
Percentage {
value: 0.,
calc_clamping_mode: None,
}
}
/// 100%
#[inline]
pub fn hundred() -> Self {
Percentage(1.)
Percentage {
value: 1.,
calc_clamping_mode: None,
}
}
}
impl Parse for Percentage {
#[inline]
fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
fn parse<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>
) -> Result<Self, ParseError<'i>> {
Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
}
}
impl ComputedValueAsSpecified for Percentage {}
/// A length or a percentage value.
#[allow(missing_docs)]
#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
pub enum LengthOrPercentage {
Length(NoCalcLength),
Percentage(Percentage),
Percentage(computed::Percentage),
Calc(Box<CalcLengthOrPercentage>),
}
@ -797,6 +846,20 @@ impl From<NoCalcLength> for LengthOrPercentage {
impl From<Percentage> for LengthOrPercentage {
#[inline]
fn from(pc: Percentage) -> Self {
if pc.calc_clamping_mode.is_some() {
LengthOrPercentage::Calc(Box::new(CalcLengthOrPercentage {
percentage: Some(computed::Percentage(pc.get())),
.. Default::default()
}))
} else {
LengthOrPercentage::Percentage(computed::Percentage(pc.get()))
}
}
}
impl From<computed::Percentage> for LengthOrPercentage {
#[inline]
fn from(pc: computed::Percentage) -> Self {
LengthOrPercentage::Percentage(pc)
}
}
@ -820,7 +883,7 @@ impl LengthOrPercentage {
NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentage::Length)
}
Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
return Ok(LengthOrPercentage::Percentage(Percentage(unit_value)))
return Ok(LengthOrPercentage::Percentage(computed::Percentage(unit_value)))
}
Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
if value != 0. &&
@ -925,7 +988,7 @@ impl LengthOrPercentage {
#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
pub enum LengthOrPercentageOrAuto {
Length(NoCalcLength),
Percentage(Percentage),
Percentage(computed::Percentage),
Auto,
Calc(Box<CalcLengthOrPercentage>),
}
@ -937,9 +1000,9 @@ impl From<NoCalcLength> for LengthOrPercentageOrAuto {
}
}
impl From<Percentage> for LengthOrPercentageOrAuto {
impl From<computed::Percentage> for LengthOrPercentageOrAuto {
#[inline]
fn from(pc: Percentage) -> Self {
fn from(pc: computed::Percentage) -> Self {
LengthOrPercentageOrAuto::Percentage(pc)
}
}
@ -956,7 +1019,7 @@ impl LengthOrPercentageOrAuto {
NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentageOrAuto::Length)
}
Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
Ok(LengthOrPercentageOrAuto::Percentage(Percentage(unit_value)))
Ok(LengthOrPercentageOrAuto::Percentage(computed::Percentage(unit_value)))
}
Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
if value != 0. &&
@ -1009,7 +1072,7 @@ impl LengthOrPercentageOrAuto {
/// Returns a value representing `0%`.
pub fn zero_percent() -> Self {
LengthOrPercentageOrAuto::Percentage(Percentage::zero())
LengthOrPercentageOrAuto::Percentage(computed::Percentage::zero())
}
}
@ -1037,7 +1100,7 @@ impl LengthOrPercentageOrAuto {
#[allow(missing_docs)]
pub enum LengthOrPercentageOrNone {
Length(NoCalcLength),
Percentage(Percentage),
Percentage(computed::Percentage),
Calc(Box<CalcLengthOrPercentage>),
None,
}
@ -1055,7 +1118,7 @@ impl LengthOrPercentageOrNone {
NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentageOrNone::Length)
}
Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
Ok(LengthOrPercentageOrNone::Percentage(Percentage(unit_value)))
Ok(LengthOrPercentageOrNone::Percentage(computed::Percentage(unit_value)))
}
Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
if value != 0. && !context.parsing_mode.allows_unitless_lengths() &&