style: Add resolution support to calc()

Differential Revision: https://phabricator.services.mozilla.com/D172338
This commit is contained in:
Emilio Cobos Álvarez 2023-03-13 11:33:22 +00:00 committed by Martin Robinson
parent 76e8eeda72
commit 2c986f0005
6 changed files with 171 additions and 47 deletions

View file

@ -770,7 +770,7 @@ impl specified::CalcLengthPercentage {
result result
} }
}), }),
Leaf::Number(..) | Leaf::Angle(..) | Leaf::Time(..) => { Leaf::Number(..) | Leaf::Angle(..) | Leaf::Time(..) | Leaf::Resolution(..) => {
unreachable!("Shouldn't have parsed") unreachable!("Shouldn't have parsed")
}, },
}); });

View file

@ -36,12 +36,12 @@ impl ToComputedValue for specified::Resolution {
#[inline] #[inline]
fn to_computed_value(&self, _: &Context) -> Self::ComputedValue { fn to_computed_value(&self, _: &Context) -> Self::ComputedValue {
Resolution(self.to_dppx()) Resolution(self.dppx())
} }
#[inline] #[inline]
fn from_computed_value(computed: &Self::ComputedValue) -> Self { fn from_computed_value(computed: &Self::ComputedValue) -> Self {
specified::Resolution::Dppx(computed.dppx()) specified::Resolution::from_dppx(computed.dppx())
} }
} }

View file

@ -101,6 +101,7 @@ pub enum SortKey {
Cqmin, Cqmin,
Cqw, Cqw,
Deg, Deg,
Dppx,
Dvb, Dvb,
Dvh, Dvh,
Dvi, Dvi,

View file

@ -11,7 +11,7 @@ use crate::values::generics::calc as generic;
use crate::values::generics::calc::{MinMaxOp, ModRemOp, RoundingStrategy, SortKey}; use crate::values::generics::calc::{MinMaxOp, ModRemOp, RoundingStrategy, SortKey};
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength}; use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength}; use crate::values::specified::length::{ContainerRelativeLength, ViewportPercentageLength};
use crate::values::specified::{self, Angle, Time}; use crate::values::specified::{self, Angle, Resolution, Time};
use crate::values::{CSSFloat, CSSInteger}; use crate::values::{CSSFloat, CSSInteger};
use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token}; use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token};
use smallvec::SmallVec; use smallvec::SmallVec;
@ -107,6 +107,8 @@ pub enum Leaf {
Angle(Angle), Angle(Angle),
/// `<time>` /// `<time>`
Time(Time), Time(Time),
/// `<resolution>`
Resolution(Resolution),
/// `<percentage>` /// `<percentage>`
Percentage(CSSFloat), Percentage(CSSFloat),
/// `<number>` /// `<number>`
@ -130,6 +132,7 @@ impl ToCss for Leaf {
match *self { match *self {
Self::Length(ref l) => l.to_css(dest), Self::Length(ref l) => l.to_css(dest),
Self::Number(ref n) => n.to_css(dest), Self::Number(ref n) => n.to_css(dest),
Self::Resolution(ref r) => r.to_css(dest),
Self::Percentage(p) => crate::values::serialize_percentage(p, dest), Self::Percentage(p) => crate::values::serialize_percentage(p, dest),
Self::Angle(ref a) => a.to_css(dest), Self::Angle(ref a) => a.to_css(dest),
Self::Time(ref t) => t.to_css(dest), Self::Time(ref t) => t.to_css(dest),
@ -148,10 +151,11 @@ bitflags! {
const PERCENTAGE = 1 << 1; const PERCENTAGE = 1 << 1;
const ANGLE = 1 << 2; const ANGLE = 1 << 2;
const TIME = 1 << 3; const TIME = 1 << 3;
const RESOLUTION = 1 << 3;
const LENGTH_PERCENTAGE = Self::LENGTH.bits | Self::PERCENTAGE.bits; const LENGTH_PERCENTAGE = Self::LENGTH.bits | Self::PERCENTAGE.bits;
// NOTE: When you add to this, make sure to make Atan2 deal with these. // NOTE: When you add to this, make sure to make Atan2 deal with these.
const ALL = Self::LENGTH.bits | Self::PERCENTAGE.bits | Self::ANGLE.bits | Self::TIME.bits; const ALL = Self::LENGTH.bits | Self::PERCENTAGE.bits | Self::ANGLE.bits | Self::TIME.bits | Self::RESOLUTION.bits;
} }
} }
@ -208,10 +212,12 @@ impl PartialOrd for Leaf {
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other), (&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
(&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()), (&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()), (&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
(&Resolution(ref one), &Resolution(ref other)) => one.dppx().partial_cmp(&other.dppx()),
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other), (&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
_ => { _ => {
match *self { match *self {
Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) => {}, Length(..) | Percentage(..) | Angle(..) | Time(..) | Number(..) |
Resolution(..) => {},
} }
unsafe { unsafe {
debug_unreachable!("Forgot a branch?"); debug_unreachable!("Forgot a branch?");
@ -226,6 +232,7 @@ impl generic::CalcNodeLeaf for Leaf {
match *self { match *self {
Self::Length(ref l) => l.unitless_value(), Self::Length(ref l) => l.unitless_value(),
Self::Percentage(n) | Self::Number(n) => n, Self::Percentage(n) | Self::Number(n) => n,
Self::Resolution(ref r) => r.dppx(),
Self::Angle(ref a) => a.degrees(), Self::Angle(ref a) => a.degrees(),
Self::Time(ref t) => t.seconds(), Self::Time(ref t) => t.seconds(),
} }
@ -244,6 +251,9 @@ impl generic::CalcNodeLeaf for Leaf {
Self::Angle(ref mut a) => { Self::Angle(ref mut a) => {
*a = Angle::from_calc(a.degrees() * scalar); *a = Angle::from_calc(a.degrees() * scalar);
}, },
Self::Resolution(ref mut r) => {
*r = Resolution::from_dppx(r.dppx() * scalar);
},
Self::Time(ref mut t) => { Self::Time(ref mut t) => {
*t = Time::from_seconds(t.seconds() * scalar); *t = Time::from_seconds(t.seconds() * scalar);
}, },
@ -258,6 +268,7 @@ impl generic::CalcNodeLeaf for Leaf {
Self::Number(..) => SortKey::Number, Self::Number(..) => SortKey::Number,
Self::Percentage(..) => SortKey::Percentage, Self::Percentage(..) => SortKey::Percentage,
Self::Time(..) => SortKey::Sec, Self::Time(..) => SortKey::Sec,
Self::Resolution(..) => SortKey::Dppx,
Self::Angle(..) => SortKey::Deg, Self::Angle(..) => SortKey::Deg,
Self::Length(ref l) => match *l { Self::Length(ref l) => match *l {
NoCalcLength::Absolute(..) => SortKey::Px, NoCalcLength::Absolute(..) => SortKey::Px,
@ -336,12 +347,16 @@ impl generic::CalcNodeLeaf for Leaf {
(&mut Time(ref mut one), &Time(ref other)) => { (&mut Time(ref mut one), &Time(ref other)) => {
*one = specified::Time::from_seconds(one.seconds() + other.seconds()); *one = specified::Time::from_seconds(one.seconds() + other.seconds());
}, },
(&mut Resolution(ref mut one), &Resolution(ref other)) => {
*one = specified::Resolution::from_dppx(one.dppx() + other.dppx());
},
(&mut Length(ref mut one), &Length(ref other)) => { (&mut Length(ref mut one), &Length(ref other)) => {
*one = one.try_op(other, std::ops::Add::add)?; *one = one.try_op(other, std::ops::Add::add)?;
}, },
_ => { _ => {
match *other { match *other {
Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) => {}, Number(..) | Percentage(..) | Angle(..) | Time(..) | Resolution(..) |
Length(..) => {},
} }
unsafe { unsafe {
debug_unreachable!(); debug_unreachable!();
@ -375,6 +390,12 @@ impl generic::CalcNodeLeaf for Leaf {
other.degrees(), other.degrees(),
)))); ))));
}, },
(&Resolution(ref one), &Resolution(ref other)) => {
return Ok(Leaf::Resolution(specified::Resolution::from_dppx(op(
one.dppx(),
other.dppx(),
))));
},
(&Time(ref one), &Time(ref other)) => { (&Time(ref one), &Time(ref other)) => {
return Ok(Leaf::Time(specified::Time::from_seconds(op( return Ok(Leaf::Time(specified::Time::from_seconds(op(
one.seconds(), one.seconds(),
@ -386,7 +407,8 @@ impl generic::CalcNodeLeaf for Leaf {
}, },
_ => { _ => {
match *other { match *other {
Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) => {}, Number(..) | Percentage(..) | Angle(..) | Time(..) | Length(..) |
Resolution(..) => {},
} }
unsafe { unsafe {
debug_unreachable!(); debug_unreachable!();
@ -432,6 +454,11 @@ impl CalcNode {
return Ok(CalcNode::Leaf(Leaf::Time(t))); return Ok(CalcNode::Leaf(Leaf::Time(t)));
} }
} }
if allowed_units.intersects(CalcUnits::RESOLUTION) {
if let Ok(t) = Resolution::parse_dimension(value, unit) {
return Ok(CalcNode::Leaf(Leaf::Resolution(t)));
}
}
return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError)); return Err(location.new_custom_error(StyleParseErrorKind::UnspecifiedError));
}, },
&Token::Percentage { unit_value, .. } &Token::Percentage { unit_value, .. }
@ -606,6 +633,11 @@ impl CalcNode {
return Ok(a.radians().atan2(b.radians())); return Ok(a.radians().atan2(b.radians()));
} }
if let Ok(a) = a.to_resolution() {
let b = b.to_resolution()?;
return Ok(a.dppx().atan2(b.dppx()));
}
let a = a.into_length_or_percentage(AllowedNumericType::All)?; let a = a.into_length_or_percentage(AllowedNumericType::All)?;
let b = b.into_length_or_percentage(AllowedNumericType::All)?; let b = b.into_length_or_percentage(AllowedNumericType::All)?;
let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?; let (a, b) = CalcLengthPercentage::same_unit_length_as(&a, &b).ok_or(())?;
@ -831,12 +863,24 @@ impl CalcNode {
_ => Err(()), _ => Err(()),
})?; })?;
let result = Time::from_seconds_with_calc_clamping_mode(if nan_inf_enabled() { Ok(Time::from_seconds_with_calc_clamping_mode(
seconds if nan_inf_enabled() { seconds } else { crate::values::normalize(seconds) },
clamping_mode
))
}
/// Tries to simplify the expression into a `<resolution>` value.
fn to_resolution(&self) -> Result<Resolution, ()> {
let dppx = self.resolve(|leaf| match *leaf {
Leaf::Resolution(ref r) => Ok(r.dppx()),
_ => Err(()),
})?;
Ok(Resolution::from_dppx_calc(if nan_inf_enabled() {
dppx
} else { } else {
crate::values::normalize(seconds) crate::values::normalize(dppx)
}, clamping_mode); }))
Ok(result)
} }
/// Tries to simplify this expression into an `Angle` value. /// Tries to simplify this expression into an `Angle` value.
@ -984,6 +1028,17 @@ impl CalcNode {
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError)) .map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
} }
/// Convenience parsing function for `<resolution>`.
pub fn parse_resolution<'i, 't>(
context: &ParserContext,
input: &mut Parser<'i, 't>,
function: MathFunction,
) -> Result<Resolution, ParseError<'i>> {
Self::parse(context, input, function, CalcUnits::RESOLUTION)?
.to_resolution()
.map_err(|()| input.new_custom_error(StyleParseErrorKind::UnspecifiedError))
}
/// Convenience parsing function for `<number>` or `<percentage>`. /// Convenience parsing function for `<number>` or `<percentage>`.
pub fn parse_number_or_percentage<'i, 't>( pub fn parse_number_or_percentage<'i, 't>(
context: &ParserContext, context: &ParserContext,

View file

@ -441,7 +441,7 @@ impl ImageSetItem {
.ok(); .ok();
} }
let resolution = resolution.unwrap_or(Resolution::X(1.0)); let resolution = resolution.unwrap_or_else(|| Resolution::from_x(1.0));
let has_mime_type = mime_type.is_some(); let has_mime_type = mime_type.is_some();
let mime_type = mime_type.unwrap_or_default(); let mime_type = mime_type.unwrap_or_default();

View file

@ -7,67 +7,135 @@
//! https://drafts.csswg.org/css-values/#resolution //! https://drafts.csswg.org/css-values/#resolution
use crate::parser::{Parse, ParserContext}; use crate::parser::{Parse, ParserContext};
use crate::values::specified::CalcNode;
use crate::values::CSSFloat; use crate::values::CSSFloat;
use cssparser::{Parser, Token}; use cssparser::{Parser, Token};
use style_traits::{ParseError, StyleParseErrorKind}; use std::fmt::{self, Write};
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
/// A specified resolution. /// A specified resolution.
#[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)] #[derive(Clone, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem)]
pub enum Resolution { pub struct Resolution {
value: CSSFloat,
unit: ResolutionUnit,
was_calc: bool,
}
#[derive(Clone, Copy, Debug, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToCss, ToShmem)]
enum ResolutionUnit {
/// Dots per inch. /// Dots per inch.
#[css(dimension)] Dpi,
Dpi(CSSFloat),
/// An alias unit for dots per pixel. /// An alias unit for dots per pixel.
#[css(dimension)] X,
X(CSSFloat),
/// Dots per pixel. /// Dots per pixel.
#[css(dimension)] Dppx,
Dppx(CSSFloat),
/// Dots per centimeter. /// Dots per centimeter.
#[css(dimension)] Dpcm,
Dpcm(CSSFloat), }
impl ResolutionUnit {
fn as_str(self) -> &'static str {
match self {
Self::Dpi => "dpi",
Self::X => "x",
Self::Dppx => "dppx",
Self::Dpcm => "dpcm",
}
}
} }
impl Resolution { impl Resolution {
/// Returns a resolution value from dppx units.
pub fn from_dppx(value: CSSFloat) -> Self {
Self {
value,
unit: ResolutionUnit::Dppx,
was_calc: false,
}
}
/// Returns a resolution value from dppx units.
pub fn from_x(value: CSSFloat) -> Self {
Self {
value,
unit: ResolutionUnit::X,
was_calc: false,
}
}
/// Returns a resolution value from dppx units.
pub fn from_dppx_calc(value: CSSFloat) -> Self {
Self {
value,
unit: ResolutionUnit::Dppx,
was_calc: true,
}
}
/// Convert this resolution value to dppx units. /// Convert this resolution value to dppx units.
pub fn to_dppx(&self) -> CSSFloat { pub fn dppx(&self) -> CSSFloat {
match *self { match self.unit {
Resolution::X(f) | Resolution::Dppx(f) => f, ResolutionUnit::X | ResolutionUnit::Dppx => self.value,
_ => self.to_dpi() / 96.0, _ => self.dpi() / 96.0,
} }
} }
/// Convert this resolution value to dpi units. /// Convert this resolution value to dpi units.
pub fn to_dpi(&self) -> CSSFloat { pub fn dpi(&self) -> CSSFloat {
match *self { match self.unit {
Resolution::Dpi(f) => f, ResolutionUnit::Dpi => self.value,
Resolution::X(f) | Resolution::Dppx(f) => f * 96.0, ResolutionUnit::X | ResolutionUnit::Dppx => self.value * 96.0,
Resolution::Dpcm(f) => f * 2.54, ResolutionUnit::Dpcm => self.value * 2.54,
} }
} }
/// Parse a resolution given a value and unit.
pub fn parse_dimension<'i, 't>(value: CSSFloat, unit: &str) -> Result<Self, ()> {
let unit = match_ignore_ascii_case! { &unit,
"dpi" => ResolutionUnit::Dpi,
"dppx" => ResolutionUnit::Dppx,
"dpcm" => ResolutionUnit::Dpcm,
"x" => ResolutionUnit::X,
_ => return Err(())
};
Ok(Self {
value,
unit,
was_calc: false,
})
}
}
impl ToCss for Resolution {
fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
where
W: Write,
{
crate::values::serialize_specified_dimension(
self.value,
self.unit.as_str(),
self.was_calc,
dest,
)
}
} }
impl Parse for Resolution { impl Parse for Resolution {
fn parse<'i, 't>( fn parse<'i, 't>(
_: &ParserContext, context: &ParserContext,
input: &mut Parser<'i, 't>, input: &mut Parser<'i, 't>,
) -> Result<Self, ParseError<'i>> { ) -> Result<Self, ParseError<'i>> {
let location = input.current_source_location(); let location = input.current_source_location();
let (value, unit) = match *input.next()? { match *input.next()? {
Token::Dimension { Token::Dimension {
value, ref unit, .. value, ref unit, ..
} => (value, unit), } => Self::parse_dimension(value, unit)
.map_err(|()| location.new_custom_error(StyleParseErrorKind::UnspecifiedError)),
Token::Function(ref name) => {
let function = CalcNode::math_function(name, location)?;
CalcNode::parse_resolution(context, input, function)
},
ref t => return Err(location.new_unexpected_token_error(t.clone())), ref t => return Err(location.new_unexpected_token_error(t.clone())),
};
match_ignore_ascii_case! { &unit,
"dpi" => Ok(Resolution::Dpi(value)),
"dppx" => Ok(Resolution::Dppx(value)),
"dpcm" => Ok(Resolution::Dpcm(value)),
"x" => Ok(Resolution::X(value)),
_ => Err(location.new_custom_error(
StyleParseErrorKind::UnexpectedDimension(unit.clone())
)),
} }
} }
} }