@@ -10,11 +10,12 @@ use crate::parser::ParserContext;
10
10
use crate :: values:: computed;
11
11
use crate :: values:: specified:: length:: ViewportPercentageLength ;
12
12
use crate :: values:: specified:: length:: { AbsoluteLength , FontRelativeLength , NoCalcLength } ;
13
- use crate :: values:: specified:: { Angle , Time } ;
13
+ use crate :: values:: specified:: { self , Angle , Time } ;
14
14
use crate :: values:: { CSSFloat , CSSInteger } ;
15
15
use cssparser:: { AngleOrNumber , CowRcStr , NumberOrPercentage , Parser , Token } ;
16
16
use smallvec:: SmallVec ;
17
17
use std:: fmt:: { self , Write } ;
18
+ use std:: { cmp, mem} ;
18
19
use style_traits:: values:: specified:: AllowedNumericType ;
19
20
use style_traits:: { CssWriter , ParseError , SpecifiedValueInfo , StyleParseErrorKind , ToCss } ;
20
21
@@ -31,8 +32,30 @@ pub enum MathFunction {
31
32
Clamp ,
32
33
}
33
34
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
+
34
57
/// Whether we're a `min` or `max` function.
35
- #[ derive( Copy , Clone , Debug ) ]
58
+ #[ derive( Copy , Clone , Debug , PartialEq ) ]
36
59
pub enum MinMaxOp {
37
60
/// `min()`
38
61
Min ,
@@ -41,7 +64,7 @@ pub enum MinMaxOp {
41
64
}
42
65
43
66
/// A node inside a `Calc` expression's AST.
44
- #[ derive( Clone , Debug ) ]
67
+ #[ derive( Clone , Debug , PartialEq ) ]
45
68
pub enum CalcNode {
46
69
/// `<length>`
47
70
Length ( NoCalcLength ) ,
@@ -201,27 +224,37 @@ macro_rules! impl_generic_to_type {
201
224
// Equivalent to cmp::max(min, cmp::min(center, max))
202
225
//
203
226
// 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
+
204
231
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 {
206
235
result = max;
236
+ result_float = max_float;
207
237
}
208
- if result. $to_float( ) < min. $to_float( ) {
209
- result = min;
238
+
239
+ if result_float < min_float {
240
+ min
241
+ } else {
242
+ result
210
243
}
211
- result
212
244
} ,
213
245
Self :: MinMax ( ref nodes, op) => {
214
246
let mut result = nodes[ 0 ] . $to_self( ) ?;
247
+ let mut result_float = result. $to_float( ) ;
215
248
for node in nodes. iter( ) . skip( 1 ) {
216
249
let candidate = node. $to_self( ) ?;
217
250
let candidate_float = candidate. $to_float( ) ;
218
- let result_float = result. $to_float( ) ;
219
251
let candidate_wins = match op {
220
252
MinMaxOp :: Min => candidate_float < result_float,
221
253
MinMaxOp :: Max => candidate_float > result_float,
222
254
} ;
223
255
if candidate_wins {
224
256
result = candidate;
257
+ result_float = candidate_float;
225
258
}
226
259
}
227
260
result
@@ -235,6 +268,20 @@ macro_rules! impl_generic_to_type {
235
268
} }
236
269
}
237
270
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
+
238
285
impl CalcNode {
239
286
fn negate ( & mut self ) {
240
287
self . mul_by ( -1. ) ;
@@ -286,8 +333,221 @@ impl CalcNode {
286
333
max. mul_by ( scalar) ;
287
334
// For negatives we need to swap min / max.
288
335
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
+ }
290
494
}
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.
291
551
} ,
292
552
}
293
553
}
@@ -509,13 +769,14 @@ impl CalcNode {
509
769
/// Tries to simplify this expression into a `<length>` or `<percentage`>
510
770
/// value.
511
771
fn to_length_or_percentage (
512
- & self ,
772
+ & mut self ,
513
773
clamping_mode : AllowedNumericType ,
514
774
) -> Result < CalcLengthPercentage , ( ) > {
515
775
let mut ret = CalcLengthPercentage {
516
776
clamping_mode,
517
777
..Default :: default ( )
518
778
} ;
779
+ self . simplify_and_sort_children ( ) ;
519
780
self . add_length_or_percentage_to ( & mut ret, 1.0 ) ?;
520
781
Ok ( ret)
521
782
}
0 commit comments