Skip to content

Commit 7e238a4

Browse files
committed
Bug 1613491 - Add some calc infrastructure to deal with simplification / sorting / etc. r=heycam
For now, we still bail out at the stage of getting the calc node into a CalcLengthPercentage if we couldn't simplify the min() / max() / clamps() involved. After this plan is to use just CalcNode everywhere instead of specified::CalcLengthPercentage, and then modify the computed CalcLengthPercentage, which would look slightly different as we know all the sum terms for those are a struct like { Length, Percentage, bool has_percentage } or such, so all the simplification code for that becomes much simpler, ideally. Or we could turn CalcNode generic otherwise, if it's too much code... We'll see. Differential Revision: https://phabricator.services.mozilla.com/D61739 --HG-- extra : moz-landing-system : lando
1 parent e0da68a commit 7e238a4

16 files changed

+371
-395
lines changed

servo/components/style/values/specified/calc.rs

Lines changed: 271 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ use crate::parser::ParserContext;
1010
use crate::values::computed;
1111
use crate::values::specified::length::ViewportPercentageLength;
1212
use crate::values::specified::length::{AbsoluteLength, FontRelativeLength, NoCalcLength};
13-
use crate::values::specified::{Angle, Time};
13+
use crate::values::specified::{self, Angle, Time};
1414
use crate::values::{CSSFloat, CSSInteger};
1515
use cssparser::{AngleOrNumber, CowRcStr, NumberOrPercentage, Parser, Token};
1616
use smallvec::SmallVec;
1717
use std::fmt::{self, Write};
18+
use std::{cmp, mem};
1819
use style_traits::values::specified::AllowedNumericType;
1920
use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss};
2021

@@ -31,8 +32,30 @@ pub enum MathFunction {
3132
Clamp,
3233
}
3334

35+
/// This determines the order in which we serialize members of a calc()
36+
/// sum.
37+
///
38+
/// See https://drafts.csswg.org/css-values-4/#sort-a-calculations-children
39+
#[derive(Debug, Copy, Clone, Eq, Ord, PartialEq, PartialOrd)]
40+
enum SortKey {
41+
Number,
42+
Percentage,
43+
Ch,
44+
Deg,
45+
Em,
46+
Ex,
47+
Px,
48+
Rem,
49+
Sec,
50+
Vh,
51+
Vmax,
52+
Vmin,
53+
Vw,
54+
Other,
55+
}
56+
3457
/// Whether we're a `min` or `max` function.
35-
#[derive(Copy, Clone, Debug)]
58+
#[derive(Copy, Clone, Debug, PartialEq)]
3659
pub enum MinMaxOp {
3760
/// `min()`
3861
Min,
@@ -41,7 +64,7 @@ pub enum MinMaxOp {
4164
}
4265

4366
/// A node inside a `Calc` expression's AST.
44-
#[derive(Clone, Debug)]
67+
#[derive(Clone, Debug, PartialEq)]
4568
pub enum CalcNode {
4669
/// `<length>`
4770
Length(NoCalcLength),
@@ -201,27 +224,37 @@ macro_rules! impl_generic_to_type {
201224
// Equivalent to cmp::max(min, cmp::min(center, max))
202225
//
203226
// But preserving units when appropriate.
227+
let center_float = center.$to_float();
228+
let min_float = min.$to_float();
229+
let max_float = max.$to_float();
230+
204231
let mut result = center;
205-
if result.$to_float() > max.$to_float() {
232+
let mut result_float = center_float;
233+
234+
if result_float > max_float {
206235
result = max;
236+
result_float = max_float;
207237
}
208-
if result.$to_float() < min.$to_float() {
209-
result = min;
238+
239+
if result_float < min_float {
240+
min
241+
} else {
242+
result
210243
}
211-
result
212244
},
213245
Self::MinMax(ref nodes, op) => {
214246
let mut result = nodes[0].$to_self()?;
247+
let mut result_float = result.$to_float();
215248
for node in nodes.iter().skip(1) {
216249
let candidate = node.$to_self()?;
217250
let candidate_float = candidate.$to_float();
218-
let result_float = result.$to_float();
219251
let candidate_wins = match op {
220252
MinMaxOp::Min => candidate_float < result_float,
221253
MinMaxOp::Max => candidate_float > result_float,
222254
};
223255
if candidate_wins {
224256
result = candidate;
257+
result_float = candidate_float;
225258
}
226259
}
227260
result
@@ -235,6 +268,20 @@ macro_rules! impl_generic_to_type {
235268
}}
236269
}
237270

271+
impl PartialOrd for CalcNode {
272+
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
273+
use self::CalcNode::*;
274+
match (self, other) {
275+
(&Length(ref one), &Length(ref other)) => one.partial_cmp(other),
276+
(&Percentage(ref one), &Percentage(ref other)) => one.partial_cmp(other),
277+
(&Angle(ref one), &Angle(ref other)) => one.degrees().partial_cmp(&other.degrees()),
278+
(&Time(ref one), &Time(ref other)) => one.seconds().partial_cmp(&other.seconds()),
279+
(&Number(ref one), &Number(ref other)) => one.partial_cmp(other),
280+
_ => None,
281+
}
282+
}
283+
}
284+
238285
impl CalcNode {
239286
fn negate(&mut self) {
240287
self.mul_by(-1.);
@@ -286,8 +333,221 @@ impl CalcNode {
286333
max.mul_by(scalar);
287334
// For negatives we need to swap min / max.
288335
if scalar < 0. {
289-
std::mem::swap(min, max);
336+
mem::swap(min, max);
337+
}
338+
},
339+
}
340+
}
341+
342+
fn calc_node_sort_key(&self) -> SortKey {
343+
match *self {
344+
Self::Number(..) => SortKey::Number,
345+
Self::Percentage(..) => SortKey::Percentage,
346+
Self::Time(..) => SortKey::Sec,
347+
Self::Angle(..) => SortKey::Deg,
348+
Self::Length(ref l) => {
349+
match *l {
350+
NoCalcLength::Absolute(..) => SortKey::Px,
351+
NoCalcLength::FontRelative(ref relative) => {
352+
match *relative {
353+
FontRelativeLength::Ch(..) => SortKey::Ch,
354+
FontRelativeLength::Em(..) => SortKey::Em,
355+
FontRelativeLength::Ex(..) => SortKey::Ex,
356+
FontRelativeLength::Rem(..) => SortKey::Rem,
357+
}
358+
},
359+
NoCalcLength::ViewportPercentage(ref vp) => {
360+
match *vp {
361+
ViewportPercentageLength::Vh(..) => SortKey::Vh,
362+
ViewportPercentageLength::Vw(..) => SortKey::Vw,
363+
ViewportPercentageLength::Vmax(..) => SortKey::Vmax,
364+
ViewportPercentageLength::Vmin(..) => SortKey::Vmin,
365+
}
366+
},
367+
NoCalcLength::ServoCharacterWidth(..) => unreachable!(),
368+
}
369+
},
370+
Self::Sum(..) | Self::MinMax(..) | Self::Clamp { .. } => SortKey::Other,
371+
}
372+
}
373+
374+
/// Tries to merge one sum to another, that is, perform `x` + `y`.
375+
///
376+
/// Only handles leaf nodes, it's the caller's responsibility to simplify
377+
/// them before calling this if needed.
378+
fn try_sum_in_place(&mut self, other: &Self) -> Result<(), ()> { use
379+
self::CalcNode::*;
380+
381+
match (self, other) { (&mut Number(ref mut one), &Number(ref other)) |
382+
(&mut Percentage(ref mut one), &Percentage(ref other)) => { *one +=
383+
*other; } (&mut Angle(ref mut one), &Angle(ref other)) => { *one
384+
= specified::Angle::from_calc(one.degrees() +
385+
other.degrees()); } (&mut Time(ref mut one), &Time(ref
386+
other)) => { *one =
387+
specified::Time::from_calc(one.seconds() +
388+
other.seconds()); } (&mut Length(ref mut one),
389+
&Length(ref other)) => { *one =
390+
one.try_sum(other)?; } _ => return Err(()),
391+
}
392+
393+
Ok(()) }
394+
395+
/// Simplifies and sorts the calculation. This is only needed if it's going
396+
/// to be preserved after parsing (so, for `<length-percentage>`). Otherwise
397+
/// we can just evaluate it and we'll come up with a simplified value
398+
/// anyways.
399+
fn simplify_and_sort_children(&mut self) {
400+
macro_rules! replace_self_with {
401+
($slot:expr) => {{
402+
let result = mem::replace($slot, Self::Number(0.));
403+
mem::replace(self, result);
404+
}}
405+
}
406+
match *self {
407+
Self::Clamp { ref mut min, ref mut center, ref mut max } => {
408+
min.simplify_and_sort_children();
409+
center.simplify_and_sort_children();
410+
max.simplify_and_sort_children();
411+
412+
// NOTE: clamp() is max(min, min(center, max))
413+
let min_cmp_center = match min.partial_cmp(&center) {
414+
Some(o) => o,
415+
None => return,
416+
};
417+
418+
// So if we can prove that min is more than center, then we won,
419+
// as that's what we should always return.
420+
if matches!(min_cmp_center, cmp::Ordering::Greater) {
421+
return replace_self_with!(&mut **min);
422+
}
423+
424+
// Otherwise try with max.
425+
let max_cmp_center = match max.partial_cmp(&center) {
426+
Some(o) => o,
427+
None => return,
428+
};
429+
430+
if matches!(max_cmp_center, cmp::Ordering::Less) {
431+
// max is less than center, so we need to return effectively
432+
// `max(min, max)`.
433+
let max_cmp_min = match max.partial_cmp(&min) {
434+
Some(o) => o,
435+
None => {
436+
debug_assert!(
437+
false,
438+
"We compared center with min and max, how are \
439+
min / max not comparable with each other?"
440+
);
441+
return;
442+
},
443+
};
444+
445+
if matches!(max_cmp_min, cmp::Ordering::Less) {
446+
return replace_self_with!(&mut **min);
447+
}
448+
449+
return replace_self_with!(&mut **max);
450+
}
451+
452+
// Otherwise we're the center node.
453+
return replace_self_with!(&mut **center);
454+
},
455+
Self::MinMax(ref mut children, op) => {
456+
for child in &mut **children {
457+
child.simplify_and_sort_children();
458+
}
459+
460+
let winning_order = match op {
461+
MinMaxOp::Min => cmp::Ordering::Less,
462+
MinMaxOp::Max => cmp::Ordering::Greater,
463+
};
464+
465+
let mut result = 0;
466+
for i in 1..children.len() {
467+
let o = match children[i].partial_cmp(&children[result]) {
468+
// We can't compare all the children, so we can't
469+
// know which one will actually win. Bail out and
470+
// keep ourselves as a min / max function.
471+
//
472+
// TODO: Maybe we could simplify compatible children,
473+
// see https://github.com/w3c/csswg-drafts/issues/4756
474+
None => return,
475+
Some(o) => o,
476+
};
477+
478+
if o == winning_order {
479+
result = i;
480+
}
481+
}
482+
483+
replace_self_with!(&mut children[result]);
484+
},
485+
Self::Sum(ref mut children_slot) => {
486+
let mut sums_to_merge = SmallVec::<[_; 3]>::new();
487+
let mut extra_kids = 0;
488+
for (i, child) in children_slot.iter_mut().enumerate() {
489+
child.simplify_and_sort_children();
490+
if let Self::Sum(ref mut children) = *child {
491+
extra_kids += children.len();
492+
sums_to_merge.push(i);
493+
}
290494
}
495+
496+
// If we only have one kid, we've already simplified it, and it
497+
// doesn't really matter whether it's a sum already or not, so
498+
// lift it up and continue.
499+
if children_slot.len() == 1 {
500+
return replace_self_with!(&mut children_slot[0]);
501+
}
502+
503+
let mut children = mem::replace(children_slot, Box::new([])).into_vec();
504+
505+
if !sums_to_merge.is_empty() {
506+
children.reserve(extra_kids - sums_to_merge.len());
507+
// Merge all our nested sums, in reverse order so that the
508+
// list indices are not invalidated.
509+
for i in sums_to_merge.drain(..).rev() {
510+
let kid_children = match children.swap_remove(i) {
511+
Self::Sum(c) => c,
512+
_ => unreachable!(),
513+
};
514+
515+
// This would be nicer with
516+
// https://github.com/rust-lang/rust/issues/59878 fixed.
517+
children.extend(kid_children.into_vec());
518+
}
519+
}
520+
521+
debug_assert!(
522+
children.len() >= 2,
523+
"Should still have multiple kids!"
524+
);
525+
526+
// Sort by spec order.
527+
children.sort_unstable_by_key(|c| c.calc_node_sort_key());
528+
529+
// NOTE: if the function returns true, by the docs of dedup_by,
530+
// a is removed.
531+
children.dedup_by(|a, b| b.try_sum_in_place(a).is_ok());
532+
533+
if children.len() == 1 {
534+
// If only one children remains, lift it up, and carry on.
535+
replace_self_with!(&mut children[0]);
536+
} else {
537+
// Else put our simplified children back.
538+
mem::replace(children_slot, children.into_boxed_slice());
539+
}
540+
},
541+
Self::Length(ref mut len) => {
542+
if let NoCalcLength::Absolute(ref mut absolute_length) = *len {
543+
*absolute_length = AbsoluteLength::Px(absolute_length.to_px());
544+
}
545+
}
546+
Self::Percentage(..) |
547+
Self::Angle(..) |
548+
Self::Time(..) |
549+
Self::Number(..) => {
550+
// These are leaves already, nothing to do.
291551
},
292552
}
293553
}
@@ -509,13 +769,14 @@ impl CalcNode {
509769
/// Tries to simplify this expression into a `<length>` or `<percentage`>
510770
/// value.
511771
fn to_length_or_percentage(
512-
&self,
772+
&mut self,
513773
clamping_mode: AllowedNumericType,
514774
) -> Result<CalcLengthPercentage, ()> {
515775
let mut ret = CalcLengthPercentage {
516776
clamping_mode,
517777
..Default::default()
518778
};
779+
self.simplify_and_sort_children();
519780
self.add_length_or_percentage_to(&mut ret, 1.0)?;
520781
Ok(ret)
521782
}

0 commit comments

Comments
 (0)