style: Make CalcNode the specified representation of <length> and <length-percentage> values.

This is the meat of the patch. There are a couple improvements done in a couple
later patches which should hopefully be straight-forward.

Differential Revision: https://phabricator.services.mozilla.com/D63397
This commit is contained in:
Emilio Cobos Álvarez 2020-02-21 00:46:41 +00:00
parent 426edbd991
commit 7e8dbd0896
9 changed files with 320 additions and 385 deletions

View file

@ -17,7 +17,7 @@ use crate::values::{specified, CSSFloat};
use crate::Zero;
use app_units::Au;
use std::fmt::{self, Write};
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
use std::ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub};
use style_traits::{CSSPixel, CssWriter, ToCss};
pub use super::image::Image;
@ -331,6 +331,13 @@ impl Div<CSSFloat> for CSSPixelLength {
}
}
impl MulAssign<CSSFloat> for CSSPixelLength {
#[inline]
fn mul_assign(&mut self, other: CSSFloat) {
self.0 *= other;
}
}
impl Mul<CSSFloat> for CSSPixelLength {
type Output = Self;

View file

@ -25,9 +25,9 @@
//! our expectations.
use super::{Context, Length, Percentage, ToComputedValue};
use crate::values::animated::{ToAnimatedValue, ToAnimatedZero};
use crate::values::animated::{Animate, Procedure, ToAnimatedValue, ToAnimatedZero};
use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
use crate::values::generics::NonNegative;
use crate::values::generics::{calc, NonNegative};
use crate::values::specified::length::FontBaseSize;
use crate::values::{specified, CSSFloat};
use crate::Zero;
@ -35,6 +35,7 @@ use app_units::Au;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use serde::{Deserialize, Serialize};
use std::fmt::{self, Write};
use std::borrow::Cow;
use style_traits::values::specified::AllowedNumericType;
use style_traits::{CssWriter, ToCss};
@ -162,7 +163,7 @@ impl MallocSizeOf for LengthPercentage {
}
/// An unpacked `<length-percentage>` that borrows the `calc()` variant.
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, ToCss)]
enum Unpacked<'a> {
Calc(&'a CalcLengthPercentage),
Length(Length),
@ -185,6 +186,14 @@ impl LengthPercentage {
Self::new_length(Length::new(1.))
}
fn to_calc_node(&self) -> Cow<CalcNode> {
match self.unpack() {
Unpacked::Length(l) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l))),
Unpacked::Percentage(p) => Cow::Owned(CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(p))),
Unpacked::Calc(p) => Cow::Borrowed(&p.node),
}
}
/// Constructs a length value.
#[inline]
pub fn new_length(length: Length) -> Self {
@ -211,25 +220,48 @@ impl LengthPercentage {
percent
}
/// Given a `LengthPercentage` value `v`, construct the value representing
/// `calc(100% - v)`.
pub fn hundred_percent_minus(v: Self, clamping_mode: AllowedNumericType) -> Self {
// TODO: This could in theory take ownership of the calc node in `v` if
// possible instead of cloning.
let mut node = v.to_calc_node().into_owned();
node.negate();
let new_node = CalcNode::Sum(vec![
CalcNode::Leaf(CalcLengthPercentageLeaf::Percentage(Percentage::hundred())),
node,
].into());
Self::new_calc(new_node, clamping_mode)
}
/// Constructs a `calc()` value.
#[inline]
pub fn new_calc(
length: Length,
percentage: Option<Percentage>,
mut node: CalcNode,
clamping_mode: AllowedNumericType,
) -> Self {
let percentage = match percentage {
Some(p) => p,
None => return Self::new_length(Length::new(clamping_mode.clamp(length.px()))),
};
if length.is_zero() {
return Self::new_percent(Percentage(clamping_mode.clamp(percentage.0)));
node.simplify_and_sort_children();
match node {
CalcNode::Leaf(l) => {
return match l {
CalcLengthPercentageLeaf::Length(l) => {
Self::new_length(Length::new(clamping_mode.clamp(l.px())))
},
CalcLengthPercentageLeaf::Percentage(p) => {
Self::new_percent(Percentage(clamping_mode.clamp(p.0)))
},
}
}
_ => {
Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
clamping_mode,
node,
}))
}
}
Self::new_calc_unchecked(Box::new(CalcLengthPercentage {
length,
percentage,
clamping_mode,
}))
}
/// Private version of new_calc() that constructs a calc() variant without
@ -313,57 +345,7 @@ impl LengthPercentage {
match self.unpack() {
Unpacked::Length(l) => l.px() == 0.0,
Unpacked::Percentage(p) => p.0 == 0.0,
Unpacked::Calc(ref c) => {
debug_assert_ne!(
c.length.px(),
0.0,
"Should've been simplified to a percentage"
);
false
},
}
}
/// Returns the `<length>` component of this `calc()`, unclamped.
#[inline]
pub fn unclamped_length(&self) -> Length {
match self.unpack() {
Unpacked::Length(l) => l,
Unpacked::Percentage(..) => Zero::zero(),
Unpacked::Calc(c) => c.unclamped_length(),
}
}
/// Returns this `calc()` as a `<length>`.
///
/// Panics in debug mode if a percentage is present in the expression.
#[inline]
fn length(&self) -> Length {
debug_assert!(!self.has_percentage());
self.length_component()
}
/// Returns the `<length>` component of this `calc()`, clamped.
#[inline]
pub fn length_component(&self) -> Length {
match self.unpack() {
Unpacked::Length(l) => l,
Unpacked::Percentage(..) => Zero::zero(),
Unpacked::Calc(c) => c.length_component(),
}
}
/// Returns the `<percentage>` component of this `calc()`, unclamped, as a
/// float.
///
/// FIXME: This are very different semantics from length(), we should
/// probably rename this.
#[inline]
pub fn percentage(&self) -> CSSFloat {
match self.unpack() {
Unpacked::Length(..) => 0.,
Unpacked::Percentage(p) => p.0,
Unpacked::Calc(c) => c.percentage.0,
Unpacked::Calc(..) => false,
}
}
@ -407,25 +389,8 @@ impl LengthPercentage {
#[inline]
pub fn to_percentage(&self) -> Option<Percentage> {
match self.unpack() {
Unpacked::Length(..) => None,
Unpacked::Percentage(p) => Some(p),
Unpacked::Calc(ref c) => {
debug_assert!(!c.length.is_zero());
None
},
}
}
/// Return the specified percentage if any.
#[inline]
pub fn specified_percentage(&self) -> Option<Percentage> {
match self.unpack() {
Unpacked::Length(..) => None,
Unpacked::Percentage(p) => Some(p),
Unpacked::Calc(ref c) => {
debug_assert!(self.has_percentage());
Some(c.percentage)
},
Unpacked::Length(..) | Unpacked::Calc(..) => None,
}
}
@ -452,10 +417,10 @@ impl LengthPercentage {
/// the height property), they apply whenever a calc() expression contains
/// percentages.
pub fn maybe_percentage_relative_to(&self, container_len: Option<Length>) -> Option<Length> {
if self.has_percentage() {
return Some(self.resolve(container_len?));
if let Unpacked::Length(l) = self.unpack() {
return Some(l);
}
Some(self.length())
Some(self.resolve(container_len?))
}
/// Returns the clamped non-negative values.
@ -549,7 +514,7 @@ impl ToCss for LengthPercentage {
where
W: Write,
{
specified::LengthPercentage::from_computed_value(self).to_css(dest)
self.unpack().to_css(dest)
}
}
@ -584,46 +549,135 @@ impl<'de> Deserialize<'de> for LengthPercentage {
}
}
/// The leaves of a `<length-percentage>` calc expression.
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize, ToAnimatedZero, ToCss, ToResolvedValue)]
#[allow(missing_docs)]
pub enum CalcLengthPercentageLeaf {
Length(Length),
Percentage(Percentage),
}
impl CalcLengthPercentageLeaf {
fn is_zero_length(&self) -> bool {
match *self {
Self::Length(ref l) => l.is_zero(),
Self::Percentage(..) => false,
}
}
}
impl PartialOrd for CalcLengthPercentageLeaf {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
use self::CalcLengthPercentageLeaf::*;
if std::mem::discriminant(self) != std::mem::discriminant(other) {
return None;
}
match (self, other) {
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
_ => {
match *self {
Length(..) | Percentage(..) => {},
}
unsafe { debug_unreachable!("Forgot a branch?"); }
}
}
}
}
impl calc::CalcNodeLeaf for CalcLengthPercentageLeaf {
fn is_negative(&self) -> bool {
match *self {
Self::Length(ref l) => l.px() < 0.,
Self::Percentage(ref p) => p.0 < 0.,
}
}
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> {
use self::CalcLengthPercentageLeaf::*;
// 0px plus anything else is equal to the right hand side.
if self.is_zero_length() {
*self = other.clone();
return Ok(());
}
if other.is_zero_length() {
return Ok(());
}
match (self, other) {
(&mut Length(ref mut one), &Length(ref other)) => {
*one += *other;
},
(&mut Percentage(ref mut one), &Percentage(ref other)) => {
one.0 += other.0;
},
_ => return Err(()),
}
Ok(())
}
fn mul_by(&mut self, scalar: f32) {
match *self {
Self::Length(ref mut l) => *l = *l * scalar,
Self::Percentage(ref mut p) => p.0 *= scalar,
}
}
fn simplify(&mut self) {}
fn sort_key(&self) -> calc::SortKey {
match *self {
Self::Length(..) => calc::SortKey::Px,
Self::Percentage(..) => calc::SortKey::Percentage,
}
}
}
/// The computed version of a calc() node for `<length-percentage>` values.
pub type CalcNode = calc::GenericCalcNode<CalcLengthPercentageLeaf>;
/// The representation of a calc() function with mixed lengths and percentages.
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue)]
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize, ToAnimatedZero, ToResolvedValue, ToCss)]
#[repr(C)]
pub struct CalcLengthPercentage {
length: Length,
percentage: Percentage,
#[animation(constant)]
#[css(skip)]
clamping_mode: AllowedNumericType,
node: CalcNode,
}
impl CalcLengthPercentage {
/// Returns the length component of this `calc()`, clamped.
#[inline]
fn length_component(&self) -> Length {
Length::new(self.clamping_mode.clamp(self.length.px()))
}
/// Resolves the percentage.
#[inline]
pub fn resolve(&self, basis: Length) -> Length {
let length = self.length.px() + basis.px() * self.percentage.0;
Length::new(self.clamping_mode.clamp(length))
fn resolve(&self, basis: Length) -> Length {
// TODO: This could be faster (without the extra allocations),
// potentially.
let mut resolved = self.node.map_leafs(|l| {
match l {
CalcLengthPercentageLeaf::Length(..) => l.clone(),
CalcLengthPercentageLeaf::Percentage(ref p) => {
CalcLengthPercentageLeaf::Length(Length::new(basis.px() * p.0))
},
}
});
resolved.simplify_and_sort_children();
match resolved {
CalcNode::Leaf(CalcLengthPercentageLeaf::Length(l)) => l,
other => unreachable!("Didn't manage to resolve <length-percentage>: {:?}", other),
}
}
/// Returns the length, without clamping.
#[inline]
fn unclamped_length(&self) -> Length {
self.length
}
/// Returns the clamped non-negative values.
#[inline]
fn clamp_to_non_negative(&self) -> LengthPercentage {
LengthPercentage::new_calc(
self.length,
Some(self.percentage),
AllowedNumericType::NonNegative,
)
let mut new = self.clone();
new.clamping_mode = AllowedNumericType::NonNegative;
LengthPercentage::new_calc_unchecked(Box::new(new))
}
}
@ -641,7 +695,7 @@ impl CalcLengthPercentage {
// maybe.
impl PartialEq for CalcLengthPercentage {
fn eq(&self, other: &Self) -> bool {
self.length == other.length && self.percentage == other.percentage
self.node == other.node
}
}
@ -656,43 +710,30 @@ impl specified::CalcLengthPercentage {
where
F: Fn(Length) -> Length,
{
use crate::values::specified::length::{FontRelativeLength, ViewportPercentageLength};
use std::f32;
use crate::values::specified::calc::Leaf;
use crate::values::specified::length::NoCalcLength;
let mut length = 0.;
if let Some(absolute) = self.absolute {
length += zoom_fn(absolute.to_computed_value(context)).px();
}
for val in &[
self.vw.map(ViewportPercentageLength::Vw),
self.vh.map(ViewportPercentageLength::Vh),
self.vmin.map(ViewportPercentageLength::Vmin),
self.vmax.map(ViewportPercentageLength::Vmax),
] {
if let Some(val) = *val {
let viewport_size = context.viewport_size_for_viewport_unit_resolution();
length += val.to_computed_value(viewport_size).px();
let node = self.node.map_leaves(|leaf| {
match *leaf {
Leaf::Percentage(p) => CalcLengthPercentageLeaf::Percentage(Percentage(p)),
Leaf::Length(l) => {
CalcLengthPercentageLeaf::Length(match l {
NoCalcLength::Absolute(ref abs) => {
zoom_fn(abs.to_computed_value(context))
},
NoCalcLength::FontRelative(ref fr) => {
fr.to_computed_value(context, base_size)
},
other => other.to_computed_value(context),
})
},
Leaf::Number(..) |
Leaf::Angle(..) |
Leaf::Time(..) => unreachable!("Shouldn't have parsed"),
}
}
});
for val in &[
self.ch.map(FontRelativeLength::Ch),
self.em.map(FontRelativeLength::Em),
self.ex.map(FontRelativeLength::Ex),
self.rem.map(FontRelativeLength::Rem),
] {
if let Some(val) = *val {
length += val.to_computed_value(context, base_size).px();
}
}
LengthPercentage::new_calc(
Length::new(length.min(f32::MAX).max(f32::MIN)),
self.percentage,
self.clamping_mode,
)
LengthPercentage::new_calc(node, self.clamping_mode)
}
/// Compute font-size or line-height taking into account text-zoom if necessary.
@ -711,25 +752,14 @@ impl specified::CalcLengthPercentage {
/// Compute the value into pixel length as CSSFloat without context,
/// so it returns Err(()) if there is any non-absolute unit.
pub fn to_computed_pixel_length_without_context(&self) -> Result<CSSFloat, ()> {
if self.vw.is_some() ||
self.vh.is_some() ||
self.vmin.is_some() ||
self.vmax.is_some() ||
self.em.is_some() ||
self.ex.is_some() ||
self.ch.is_some() ||
self.rem.is_some() ||
self.percentage.is_some()
{
return Err(());
}
use crate::values::specified::calc::Leaf;
use crate::values::specified::length::NoCalcLength;
match self.absolute {
Some(abs) => Ok(abs.to_px()),
None => {
debug_assert!(false, "Someone forgot to handle an unit here: {:?}", self);
Err(())
},
// Simplification should've turned this into an absolute length,
// otherwise it wouldn't have been able to.
match self.node {
calc::CalcNode::Leaf(Leaf::Length(NoCalcLength::Absolute(ref l))) => Ok(l.to_px()),
_ => Err(()),
}
}
@ -740,17 +770,48 @@ impl specified::CalcLengthPercentage {
#[inline]
fn from_computed_value(computed: &CalcLengthPercentage) -> Self {
use crate::values::specified::length::AbsoluteLength;
use crate::values::specified::calc::Leaf;
use crate::values::specified::length::NoCalcLength;
specified::CalcLengthPercentage {
clamping_mode: computed.clamping_mode,
absolute: Some(AbsoluteLength::from_computed_value(&computed.length)),
percentage: Some(computed.percentage),
..Default::default()
node: computed.node.map_leaves(|l| {
match l {
CalcLengthPercentageLeaf::Length(ref l) => Leaf::Length(NoCalcLength::from_px(l.px())),
CalcLengthPercentageLeaf::Percentage(ref p) => Leaf::Percentage(p.0),
}
})
}
}
}
/// https://drafts.csswg.org/css-transitions/#animtype-lpcalc
/// https://drafts.csswg.org/css-values-4/#combine-math
/// https://drafts.csswg.org/css-values-4/#combine-mixed
impl Animate for LengthPercentage {
#[inline]
fn animate(&self, other: &Self, procedure: Procedure) -> Result<Self, ()> {
Ok(match (self.unpack(), other.unpack()) {
(Unpacked::Length(one), Unpacked::Length(other)) => {
Self::new_length(one.animate(&other, procedure)?)
},
(Unpacked::Percentage(one), Unpacked::Percentage(other)) => {
Self::new_percent(one.animate(&other, procedure)?)
},
_ => {
let mut one = self.to_calc_node().into_owned();
let mut other = other.to_calc_node().into_owned();
let (l, r) = procedure.weights();
one.mul_by(l as f32);
other.mul_by(r as f32);
Self::new_calc(CalcNode::Sum(vec![one, other].into()), AllowedNumericType::All)
},
})
}
}
/// A wrapper of LengthPercentage, whose value must be >= 0.
pub type NonNegativeLengthPercentage = NonNegative<LengthPercentage>;