swc_css_minifier/compressor/
calc_sum.rs

1use rustc_hash::FxHashMap;
2use swc_atoms::{atom, Atom};
3use swc_common::Span;
4use swc_css_ast::*;
5
6use super::Compressor;
7use crate::compressor::math::{is_calc_function_name, transform_calc_value_into_component_value};
8
9// Calc-operator node of the calculation tree
10// https://www.w3.org/TR/css-values-4/#calculation-tree-calc-operator-nodes
11#[derive(Debug, Clone)]
12enum CalcOperatorNode {
13    Sum(Vec<CalcNode>),
14    Product(Vec<CalcNode>),
15    Negate(CalcNode),
16    Invert(CalcNode),
17}
18
19#[derive(Debug, Clone)]
20enum CalcNode {
21    Number(Number),
22    Dimension(Dimension),
23    Percentage(Percentage),
24    Constant(Ident),
25    Function(Function),
26    OperatorNode(Box<CalcOperatorNode>),
27}
28
29fn get_precision(n: f64) -> u32 {
30    let n_as_str = n.to_string();
31    n_as_str
32        .find('.')
33        .map_or(0, |sep| (n_as_str.len() - sep) as u32 - 1)
34}
35
36// Parse our AST (subtree) into a calculation tree (as defined by the spec)
37// https://www.w3.org/TR/css-values-4/#parse-a-calculation
38fn collect_calc_sum_into_calc_node(calc_sum: &CalcSum) -> CalcNode {
39    let mut is_negated = false;
40    let mut operands: Vec<CalcNode> = Vec::new();
41    for node in &calc_sum.expressions {
42        match &node {
43            CalcProductOrOperator::Product(calc_product) => {
44                let mut node = collect_calc_product_into_calc_node(calc_product);
45                if is_negated {
46                    node = CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(node)));
47                }
48                operands.push(node);
49            }
50            CalcProductOrOperator::Operator(CalcOperator {
51                value: CalcOperatorType::Sub,
52                ..
53            }) => {
54                is_negated = true;
55            }
56            CalcProductOrOperator::Operator(CalcOperator {
57                value: CalcOperatorType::Add,
58                ..
59            }) => {
60                is_negated = false;
61            }
62            _ => {}
63        }
64    }
65
66    if operands.len() == 1 {
67        operands[0].clone()
68    } else {
69        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(operands)))
70    }
71}
72
73fn collect_calc_product_into_calc_node(calc_product: &CalcProduct) -> CalcNode {
74    let mut is_inverted = false;
75    let mut operands: Vec<CalcNode> = Vec::new();
76    for node in &calc_product.expressions {
77        match &node {
78            CalcValueOrOperator::Value(calc_value) => {
79                let mut node = collect_calc_value_into_calc_node(calc_value);
80                if is_inverted {
81                    node = CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(node)));
82                }
83                operands.push(node);
84            }
85            CalcValueOrOperator::Operator(CalcOperator {
86                value: CalcOperatorType::Div,
87                ..
88            }) => {
89                is_inverted = true;
90            }
91            CalcValueOrOperator::Operator(CalcOperator {
92                value: CalcOperatorType::Mul,
93                ..
94            }) => {
95                is_inverted = false;
96            }
97            _ => {}
98        }
99    }
100
101    if operands.len() == 1 {
102        operands[0].clone()
103    } else {
104        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Product(operands)))
105    }
106}
107
108fn collect_calc_value_into_calc_node(calc_value: &CalcValue) -> CalcNode {
109    match calc_value {
110        CalcValue::Number(n) => CalcNode::Number(n.clone()),
111        CalcValue::Dimension(d) => CalcNode::Dimension(d.clone()),
112        CalcValue::Percentage(p) => CalcNode::Percentage(p.clone()),
113        CalcValue::Constant(c) => CalcNode::Constant(c.clone()),
114        CalcValue::Function(f @ Function { name, value, .. })
115            if is_calc_function_name(name) && value.len() == 1 =>
116        {
117            match &value[0] {
118                ComponentValue::CalcSum(calc_sum) => collect_calc_sum_into_calc_node(calc_sum),
119                _ => CalcNode::Function(f.clone()),
120            }
121        }
122        CalcValue::Function(f) => CalcNode::Function(f.clone()),
123        CalcValue::Sum(calc_sum) => collect_calc_sum_into_calc_node(calc_sum),
124    }
125}
126
127// https://www.w3.org/TR/css-values-4/#calc-simplification
128// Impl. note: our "simplify" implementation does not always return a canonical
129// result For example:
130// * we do not want to transform calc(10px / 3) in to 3.33333333333333px (we'd
131//   lose precision)
132// * we can not transform relative unit (em, vh, etc) into their canonical form
133//   (we do not have the screen/browser context to compute it)
134fn simplify_calc_node(calc_node: &CalcNode) -> CalcNode {
135    match calc_node {
136        CalcNode::Number(_)
137        | CalcNode::Dimension(_)
138        | CalcNode::Percentage(_)
139        | CalcNode::Constant(_)
140        | CalcNode::Function(_) => calc_node.clone(),
141        CalcNode::OperatorNode(op) => simplify_calc_operator_node(op),
142    }
143}
144
145fn simplify_calc_operator_node(calc_operator_node: &CalcOperatorNode) -> CalcNode {
146    match calc_operator_node {
147        CalcOperatorNode::Invert(inverted_calc_node) => {
148            simplify_calc_operator_node_invert(inverted_calc_node)
149        }
150        CalcOperatorNode::Negate(negated_calc_node) => {
151            simplify_calc_operator_node_negate(negated_calc_node)
152        }
153        CalcOperatorNode::Product(nodes) => simplify_calc_operator_node_product(nodes),
154        CalcOperatorNode::Sum(nodes) => simplify_calc_operator_node_sum(nodes),
155    }
156}
157
158// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 7
159// TODO for a better compress we should not always compute the reciprocal
160// TODO currently we transform "x / 2" => "x * .5"... we should avoid it
161fn simplify_calc_operator_node_invert(calc_node: &CalcNode) -> CalcNode {
162    let calc_node = simplify_calc_node(calc_node);
163    match &calc_node {
164        CalcNode::Number(n) => {
165            // If root’s child is a number (not a percentage or dimension) return the
166            // reciprocal of the child’s value.
167            if let Some(result) = try_to_divide_values(1.0, n.value) {
168                CalcNode::Number(Number {
169                    value: result,
170                    span: n.span,
171                    raw: n.raw.clone(),
172                })
173            } else {
174                CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone())))
175            }
176        }
177        CalcNode::OperatorNode(op) => {
178            match &**op {
179                // If root’s child is an Invert node, return the child’s child.
180                CalcOperatorNode::Invert(node) => node.clone(),
181                CalcOperatorNode::Product(children) => {
182                    // Impl. note: not in spec, try to invert a fraction
183                    if let Some(inverted_fraction) =
184                        try_to_invert_if_children_are_fraction(children)
185                    {
186                        inverted_fraction
187                    } else {
188                        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(
189                            calc_node.clone(),
190                        )))
191                    }
192                }
193                _ => CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone()))),
194            }
195        }
196        _ => CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(calc_node.clone()))),
197    }
198}
199
200// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 6
201fn simplify_calc_operator_node_negate(calc_node: &CalcNode) -> CalcNode {
202    try_to_switch_sign_of_nodes(&[simplify_calc_node(calc_node)]).unwrap_or_else(|| {
203        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(calc_node.clone())))
204    })
205}
206
207// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 9
208fn simplify_calc_operator_node_product(nodes: &[CalcNode]) -> CalcNode {
209    let mut nodes = nodes.to_vec();
210
211    // For each of root’s children that are Product nodes, replace them with their
212    // children.
213    let mut idx = 0;
214    while idx < nodes.len() {
215        nodes[idx] = simplify_calc_node(&nodes[idx]);
216        if let Some(children) = get_children_if_node_is_product_operator(&nodes[idx]) {
217            nodes.remove(idx);
218            let mut i = idx;
219            for nested_node in children {
220                nodes.insert(i, nested_node);
221                i += 1;
222            }
223        } else {
224            idx += 1;
225        }
226    }
227
228    // If root has multiple children that are numbers (not percentages or
229    // dimensions), remove them and replace them with a single number containing
230    // the product of the removed nodes.
231    let mut number: Option<usize> = None;
232    let mut idx = 0;
233    while idx < nodes.len() {
234        match &nodes[idx] {
235            CalcNode::Number(_) => {
236                if let Some(prev_idx) = number {
237                    let previous_value = get_value(&nodes[prev_idx]);
238                    let value = get_value(&nodes[idx]);
239                    if let Some(result) = try_to_multiply_values(previous_value, value) {
240                        set_value(&mut nodes[prev_idx], result);
241                        nodes.remove(idx);
242                    } else {
243                        idx += 1;
244                    }
245                } else {
246                    number = Some(idx);
247                    idx += 1;
248                }
249            }
250            _ => {
251                idx += 1;
252            }
253        }
254    }
255
256    // If root contains only two children, one of which is a number (not a
257    // percentage or dimension) and the other of which is a Sum whose children
258    // are all numeric values, multiply all of the Sum’s children by the number,
259    // then return the Sum.
260    if nodes.len() == 2 {
261        match (&nodes[0], &nodes[1]) {
262            (CalcNode::Number(Number { value, .. }), CalcNode::OperatorNode(op))
263            | (CalcNode::OperatorNode(op), CalcNode::Number(Number { value, .. })) => {
264                if let CalcOperatorNode::Sum(children) = &**op {
265                    if let Some(sum) =
266                        try_to_multiply_all_numeric_sum_children_by_value(children, *value)
267                    {
268                        return sum;
269                    }
270                }
271            }
272            _ => {}
273        }
274    }
275
276    // If root contains only numeric values and/or Invert nodes containing numeric
277    // values, and multiplying the types of all the children (noting that the
278    // type of an Invert node is the inverse of its child’s type) results in a
279    // type that matches any of the types that a math function can resolve to,
280    // return the result of multiplying all the values of the children
281    // (noting that the value of an Invert node is the reciprocal of its child’s
282    // value), expressed in the result’s canonical unit.
283    nodes = try_to_multiply_all_numeric_and_invert_nodes(&nodes);
284
285    if nodes.len() == 1 {
286        nodes[0].clone()
287    } else {
288        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Product(nodes.clone())))
289    }
290}
291
292// https://www.w3.org/TR/css-values-4/#calc-simplification - Step 8
293fn simplify_calc_operator_node_sum(nodes: &[CalcNode]) -> CalcNode {
294    let mut nodes = nodes.to_vec();
295
296    // For each of root’s children that are Sum nodes, replace them with their
297    // children.
298    let mut idx = 0;
299    while idx < nodes.len() {
300        nodes[idx] = simplify_calc_node(&nodes[idx]);
301        if let Some(children) = get_children_if_node_is_sum_operator(&nodes[idx]) {
302            nodes.remove(idx);
303            let mut i = idx;
304            for nested_node in children {
305                nodes.insert(i, nested_node);
306                i += 1;
307            }
308        } else {
309            idx += 1;
310        }
311    }
312
313    // For each set of root’s children that are numeric values with identical units,
314    // remove those children and replace them with a single numeric value
315    // containing the sum of the removed nodes, and with the same unit. (E.g.
316    // combine numbers, combine percentages, combine px values, etc.)
317    let mut number: Option<usize> = None;
318    let mut percentage: Option<usize> = None;
319    let mut dimensions: FxHashMap<String, usize> = FxHashMap::default();
320    let mut idx = 0;
321    while idx < nodes.len() {
322        match &nodes[idx] {
323            CalcNode::Number(_) => {
324                if let Some(prev_idx) = number {
325                    if try_to_sum_nodes(&mut nodes, prev_idx, idx) {
326                        nodes.remove(idx);
327                    } else {
328                        idx += 1;
329                    }
330                } else {
331                    number = Some(idx);
332                    idx += 1;
333                }
334            }
335            CalcNode::Dimension(d) => {
336                let unit = get_dimension_unit_lowercase(d);
337                match &dimensions.get(&*unit) {
338                    Some(prev_idx) => {
339                        if try_to_sum_nodes(&mut nodes, **prev_idx, idx) {
340                            nodes.remove(idx);
341                        } else {
342                            idx += 1;
343                        }
344                    }
345                    None => {
346                        dimensions.insert(unit.to_string(), idx);
347                        idx += 1;
348                    }
349                }
350            }
351            CalcNode::Percentage(_) => {
352                if let Some(prev_idx) = percentage {
353                    if try_to_sum_nodes(&mut nodes, prev_idx, idx) {
354                        nodes.remove(idx);
355                    } else {
356                        idx += 1;
357                    }
358                } else {
359                    percentage = Some(idx);
360                    idx += 1;
361                }
362            }
363            _ => {
364                idx += 1;
365            }
366        }
367    }
368
369    // Impl. node: try to convert some units
370    try_to_reduce_node_with_dimensions(&mut dimensions, &mut nodes);
371
372    // If root has only a single child at this point, return the child. Otherwise,
373    // return root.
374    if nodes.len() == 1 {
375        nodes[0].clone()
376    } else {
377        CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(nodes.clone())))
378    }
379}
380
381fn get_children_if_node_is_sum_operator(calc_node: &CalcNode) -> Option<Vec<CalcNode>> {
382    match calc_node {
383        CalcNode::OperatorNode(op) => match &**op {
384            CalcOperatorNode::Sum(children) => Some(children.clone()),
385            _ => None,
386        },
387        _ => None,
388    }
389}
390
391fn get_children_if_node_is_product_operator(calc_node: &CalcNode) -> Option<Vec<CalcNode>> {
392    match calc_node {
393        CalcNode::OperatorNode(op) => match &**op {
394            CalcOperatorNode::Product(children) => Some(children.clone()),
395            _ => None,
396        },
397        _ => None,
398    }
399}
400
401// Recursive impl. of https://www.w3.org/TR/css-values-4/#calc-simplification - Step 6
402fn try_to_switch_sign_of_nodes(nodes: &[CalcNode]) -> Option<CalcNode> {
403    let mut nodes = nodes.to_vec();
404    let mut idx = 0;
405    while idx < nodes.len() {
406        let calc_node = &nodes[idx];
407        nodes[idx] = match calc_node {
408            // If root’s child is a numeric value, return an equivalent numeric value, but with the
409            // value negated (0 - value).
410            CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => {
411                let mut negated_node = calc_node.clone();
412                set_value(&mut negated_node, -get_value(calc_node));
413                negated_node
414            }
415            CalcNode::OperatorNode(op) => {
416                match &**op {
417                    // If root’s child is a Negate node, return the child’s child.
418                    CalcOperatorNode::Negate(node) => node.clone(),
419                    CalcOperatorNode::Sum(nodes) => {
420                        // Impl. note: not in the spec, but we try to propagate the sign before
421                        // continuing the simplification process
422                        try_to_switch_sign_of_nodes(nodes)?
423                    }
424                    _ => return None,
425                }
426            }
427            _ => {
428                // Just wrap the constant (or function)
429                CalcNode::OperatorNode(Box::new(CalcOperatorNode::Negate(calc_node.clone())))
430            }
431        };
432        idx += 1;
433    }
434
435    if nodes.len() == 1 {
436        Some(nodes[0].clone())
437    } else {
438        Some(CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(
439            nodes.to_vec(),
440        ))))
441    }
442}
443
444fn try_to_reduce_node_with_dimensions(
445    dimensions: &mut FxHashMap<String, usize>,
446    nodes: &mut Vec<CalcNode>,
447) {
448    try_to_reduce_node_with_absolute_lengths(dimensions, nodes);
449    try_to_reduce_node_with_durations(dimensions, nodes);
450    try_to_reduce_node_with_frequencies(dimensions, nodes);
451    try_to_reduce_node_with_resolutions(dimensions, nodes);
452}
453
454// https://www.w3.org/TR/css-values-4/#absolute-lengths
455fn try_to_reduce_node_with_absolute_lengths(
456    dimensions: &mut FxHashMap<String, usize>,
457    nodes: &mut Vec<CalcNode>,
458) {
459    if let (Some(idx_cm), Some(idx_mm)) = (dimensions.get("cm"), dimensions.get("mm")) {
460        let value_cm = get_value(&nodes[*idx_cm]);
461        let value_mm = get_value(&nodes[*idx_mm]);
462        if let Some(result) = try_to_sum_values(value_cm, value_mm, Some(10.0)) {
463            set_value(&mut nodes[*idx_mm], result);
464            nodes.remove(*idx_cm);
465        }
466    }
467
468    if let (Some(idx_mm), Some(idx_q)) = (dimensions.get("mm"), dimensions.get("q")) {
469        let value_mm = get_value(&nodes[*idx_mm]);
470        let value_q = get_value(&nodes[*idx_q]);
471        if let Some(result) = try_to_sum_values(value_mm, value_q, Some(4.0)) {
472            set_value(&mut nodes[*idx_q], result);
473            nodes.remove(*idx_mm);
474        }
475    }
476
477    if let (Some(idx_cm), Some(idx_q)) = (dimensions.get("cm"), dimensions.get("q")) {
478        let value_cm = get_value(&nodes[*idx_cm]);
479        let value_q = get_value(&nodes[*idx_q]);
480        if let Some(result) = try_to_sum_values(value_cm, value_q, Some(40.0)) {
481            set_value(&mut nodes[*idx_q], result);
482            nodes.remove(*idx_cm);
483        }
484    }
485
486    if let (Some(idx_in), Some(idx_px)) = (dimensions.get("in"), dimensions.get("px")) {
487        let value_in = get_value(&nodes[*idx_in]);
488        let value_px = get_value(&nodes[*idx_px]);
489        if let Some(result) = try_to_sum_values(value_in, value_px, Some(96.0)) {
490            set_value(&mut nodes[*idx_px], result);
491            nodes.remove(*idx_in);
492        }
493    }
494
495    if let (Some(idx_in), Some(idx_pc)) = (dimensions.get("in"), dimensions.get("pc")) {
496        let value_in = get_value(&nodes[*idx_in]);
497        let value_pc = get_value(&nodes[*idx_pc]);
498        if let Some(result) = try_to_sum_values(value_in, value_pc, Some(6.0)) {
499            set_value(&mut nodes[*idx_pc], result);
500            nodes.remove(*idx_in);
501        }
502    }
503
504    if let (Some(idx_pc), Some(idx_pt)) = (dimensions.get("pc"), dimensions.get("pt")) {
505        let value_pc = get_value(&nodes[*idx_pc]);
506        let value_pt = get_value(&nodes[*idx_pt]);
507        if let Some(result) = try_to_sum_values(value_pc, value_pt, Some(12.0)) {
508            set_value(&mut nodes[*idx_pt], result);
509            nodes.remove(*idx_pc);
510        }
511    }
512
513    if let (Some(idx_pc), Some(idx_px)) = (dimensions.get("pc"), dimensions.get("px")) {
514        let value_pc = get_value(&nodes[*idx_pc]);
515        let value_px = get_value(&nodes[*idx_px]);
516        if let Some(result) = try_to_sum_values(value_pc, value_px, Some(16.0)) {
517            set_value(&mut nodes[*idx_px], result);
518            nodes.remove(*idx_pc);
519        }
520    }
521}
522
523// https://www.w3.org/TR/css-values-4/#time
524fn try_to_reduce_node_with_durations(
525    dimensions: &mut FxHashMap<String, usize>,
526    nodes: &mut Vec<CalcNode>,
527) {
528    if let (Some(idx_ms), Some(idx_s)) = (dimensions.get("ms"), dimensions.get("s")) {
529        let value_ms = get_value(&nodes[*idx_ms]);
530        let value_s = get_value(&nodes[*idx_s]);
531        if let Some(result) = try_to_sum_values(value_s, value_ms, Some(1000.0)) {
532            set_value(&mut nodes[*idx_ms], result);
533            nodes.remove(*idx_s);
534            dimensions.remove("s");
535        }
536    }
537}
538
539// https://www.w3.org/TR/css-values-4/#frequency
540fn try_to_reduce_node_with_frequencies(
541    dimensions: &mut FxHashMap<String, usize>,
542    nodes: &mut Vec<CalcNode>,
543) {
544    if let (Some(idx_hz), Some(idx_khz)) = (dimensions.get("hz"), dimensions.get("khz")) {
545        let value_hz = get_value(&nodes[*idx_hz]);
546        let value_khz = get_value(&nodes[*idx_khz]);
547        if let Some(result) = try_to_sum_values(value_khz, value_hz, Some(1000.0)) {
548            set_value(&mut nodes[*idx_hz], result);
549            nodes.remove(*idx_khz);
550            dimensions.remove("khz");
551        }
552    }
553}
554
555// https://www.w3.org/TR/css-values-4/#resolution
556fn try_to_reduce_node_with_resolutions(
557    dimensions: &mut FxHashMap<String, usize>,
558    nodes: &mut Vec<CalcNode>,
559) {
560    match (dimensions.get("dppx"), dimensions.get("x")) {
561        // "x" is an alias for "dppx"
562        (Some(idx_dppx), Some(idx_x)) => {
563            let value_dppx = get_value(&nodes[*idx_dppx]);
564            let value_x = get_value(&nodes[*idx_x]);
565            if let Some(result) = try_to_sum_values(value_x, value_dppx, None) {
566                set_value(&mut nodes[*idx_x], result);
567                nodes.remove(*idx_dppx);
568                dimensions.remove("dppx");
569            }
570        }
571        (Some(idx_dppx), None) => {
572            // rename "dppx" into "x"
573            if let CalcNode::Dimension(Dimension::Resolution(r)) = &mut nodes[*idx_dppx] {
574                r.unit.value = atom!("x");
575                dimensions.insert("x".into(), *idx_dppx);
576                dimensions.remove("dppx");
577            }
578        }
579        _ => {}
580    }
581
582    if let (Some(idx_x), Some(idx_dpi)) = (dimensions.get("x"), dimensions.get("dpi")) {
583        let value_x = get_value(&nodes[*idx_x]);
584        let value_dpi = get_value(&nodes[*idx_dpi]);
585        if let Some(result) = try_to_sum_values(value_x, value_dpi, Some(96.0)) {
586            set_value(&mut nodes[*idx_dpi], result);
587            nodes.remove(*idx_x);
588            dimensions.remove("x");
589        }
590    }
591
592    if let (Some(idx_dpcm), Some(idx_dpi)) = (dimensions.get("dpcm"), dimensions.get("dpi")) {
593        let value_dpcm = get_value(&nodes[*idx_dpcm]);
594        let value_dpi = get_value(&nodes[*idx_dpi]);
595        if let Some(result) = try_to_sum_values(value_dpcm, value_dpi, Some(2.54)) {
596            set_value(&mut nodes[*idx_dpi], result);
597            nodes.remove(*idx_dpcm);
598            dimensions.remove("dpcm");
599        }
600    }
601}
602
603fn try_to_multiply_all_numeric_sum_children_by_value(
604    nodes: &[CalcNode],
605    value: f64,
606) -> Option<CalcNode> {
607    let mut operands = Vec::new();
608
609    for calc_node in nodes {
610        match calc_node {
611            CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => {
612                let node_value = get_value(calc_node);
613                if let Some(result) = try_to_multiply_values(node_value, value) {
614                    let mut node = calc_node.clone();
615                    set_value(&mut node, result);
616                    operands.push(node);
617                } else {
618                    return None;
619                }
620            }
621            _ => return None,
622        }
623    }
624
625    Some(CalcNode::OperatorNode(Box::new(CalcOperatorNode::Sum(
626        operands,
627    ))))
628}
629
630fn try_to_invert_if_children_are_fraction(children: &[CalcNode]) -> Option<CalcNode> {
631    if children.len() == 2 {
632        match (&children[0], &children[1]) {
633            (CalcNode::OperatorNode(_), _) => None,
634            (numerator, CalcNode::OperatorNode(op)) => {
635                if let CalcOperatorNode::Invert(denominator) = &**op {
636                    Some(CalcNode::OperatorNode(Box::new(CalcOperatorNode::Product(
637                        vec![
638                            denominator.clone(),
639                            CalcNode::OperatorNode(Box::new(CalcOperatorNode::Invert(
640                                numerator.clone(),
641                            ))),
642                        ],
643                    ))))
644                } else {
645                    None
646                }
647            }
648            (_, _) => None,
649        }
650    } else {
651        None
652    }
653}
654
655// https://drafts.css-houdini.org/css-typed-om-1/#cssnumericvalue-multiply-two-types
656// This implementation does not handle "multiplying the types of all the
657// children". This function only handles numeric values and invert of numeric
658// values
659fn try_to_multiply_all_numeric_and_invert_nodes(children: &[CalcNode]) -> Vec<CalcNode> {
660    let mut nodes = children.to_vec();
661
662    let mut numeric: Option<usize> = None;
663    let mut idx = 0;
664    while idx < nodes.len() {
665        match numeric {
666            None => {
667                match &nodes[idx] {
668                    CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => {
669                        numeric = Some(idx);
670                    }
671                    CalcNode::OperatorNode(op) => {
672                        if let CalcOperatorNode::Invert(CalcNode::Number(_)) = &**op {
673                            numeric = Some(idx);
674                        }
675                    }
676                    _ => {}
677                };
678                idx += 1;
679            }
680            Some(prev_idx) => {
681                let prev_numeric_node = &nodes[prev_idx];
682                let cur_numeric_node = &nodes[idx];
683                match (prev_numeric_node, cur_numeric_node) {
684                    // x * y => z
685                    // x * y% => z%
686                    // x * yPX => zPX
687                    (CalcNode::Number(_), other_node @ CalcNode::Number(_))
688                    | (CalcNode::Number(_), other_node @ CalcNode::Percentage(_))
689                    | (other_node @ CalcNode::Percentage(_), CalcNode::Number(_))
690                    | (CalcNode::Number(_), other_node @ CalcNode::Dimension(_))
691                    | (other_node @ CalcNode::Dimension(_), CalcNode::Number(_)) => {
692                        let prev_value = get_value(prev_numeric_node);
693                        let value = get_value(cur_numeric_node);
694                        if let Some(result) = try_to_multiply_values(prev_value, value) {
695                            nodes[prev_idx] = other_node.clone();
696                            set_value(&mut nodes[prev_idx], result);
697                            nodes.remove(idx);
698                        } else {
699                            idx += 1;
700                        }
701                    }
702                    // 1/x * 1/y => 1/z
703                    (CalcNode::OperatorNode(prev_op), CalcNode::OperatorNode(op)) => {
704                        if let CalcOperatorNode::Invert(prev_numeric_node @ CalcNode::Number(_)) =
705                            &**prev_op
706                        {
707                            if let CalcOperatorNode::Invert(
708                                cur_numeric_node @ CalcNode::Number(_),
709                            ) = &**op
710                            {
711                                let prev_value = get_value(prev_numeric_node);
712                                let value = get_value(cur_numeric_node);
713                                if let Some(result) = try_to_multiply_values(prev_value, value) {
714                                    let mut result_node = prev_numeric_node.clone();
715                                    set_value(&mut result_node, result);
716                                    nodes[prev_idx] = CalcNode::OperatorNode(Box::new(
717                                        CalcOperatorNode::Invert(result_node),
718                                    ));
719                                    nodes.remove(idx);
720                                } else {
721                                    idx += 1;
722                                }
723                            } else {
724                                idx += 1;
725                            }
726                        } else {
727                            idx += 1;
728                        }
729                    }
730                    // 1/x * y => z
731                    // 1/x * y% => z%
732                    // 1/x * yPX => zPX
733                    (numeric_node @ CalcNode::Number(_), CalcNode::OperatorNode(op))
734                    | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Number(_))
735                    | (numeric_node @ CalcNode::Dimension(_), CalcNode::OperatorNode(op))
736                    | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Dimension(_))
737                    | (numeric_node @ CalcNode::Percentage(_), CalcNode::OperatorNode(op))
738                    | (CalcNode::OperatorNode(op), numeric_node @ CalcNode::Percentage(_)) => {
739                        if let CalcOperatorNode::Invert(inverted_node @ CalcNode::Number(_)) = &**op
740                        {
741                            let numerator = get_value(numeric_node);
742                            let denominator = get_value(inverted_node);
743                            if let Some(result) = try_to_divide_values(numerator, denominator) {
744                                nodes[prev_idx] = numeric_node.clone();
745                                set_value(&mut nodes[prev_idx], result);
746                                nodes.remove(idx);
747                            } else {
748                                idx += 1;
749                            }
750                        } else {
751                            idx += 1;
752                        }
753                    }
754                    (CalcNode::Percentage(_), CalcNode::Percentage(_))
755                    | (CalcNode::Percentage(_), CalcNode::Dimension(_))
756                    | (CalcNode::Dimension(_), CalcNode::Percentage(_))
757                    | (CalcNode::Dimension(_), CalcNode::Dimension(_)) => {
758                        // Not handled by this impl, just skip it
759                        idx += 1;
760                    }
761                    // Not a (inverted) numeric value
762                    _ => idx += 1,
763                }
764            }
765        }
766    }
767
768    nodes
769}
770
771fn try_to_multiply_values(v1: f64, v2: f64) -> Option<f64> {
772    let result = v1 * v2;
773
774    let precision1 = get_precision(v1);
775    let precision2 = get_precision(v2);
776    let result_precision = get_precision(result);
777
778    if result_precision <= (precision1 + precision2) {
779        Some(result)
780    } else {
781        None
782    }
783}
784
785fn try_to_sum_nodes(nodes: &mut [CalcNode], prev_idx: usize, idx: usize) -> bool {
786    let previous_value = get_value(&nodes[prev_idx]);
787    let value = get_value(&nodes[idx]);
788    if let Some(result) = try_to_sum_values(previous_value, value, None) {
789        set_value(&mut nodes[prev_idx], result);
790        true
791    } else {
792        false
793    }
794}
795
796/// ratio is applied to n1
797fn try_to_sum_values(n1: f64, n2: f64, ratio: Option<f64>) -> Option<f64> {
798    let result = match ratio {
799        None => n1 + n2,
800        Some(r) => n1.mul_add(r, n2),
801    };
802
803    let precision1 = get_precision(n1);
804    let precision2 = get_precision(n2) + ratio.map_or(0, get_precision);
805    let result_precision = get_precision(result);
806
807    if result_precision <= precision1.max(precision2) {
808        Some(result)
809    } else {
810        None
811    }
812}
813
814fn try_to_divide_values(numerator: f64, denominator: f64) -> Option<f64> {
815    let result = numerator / denominator;
816    let result_precision = get_precision(result);
817
818    if result_precision <= f64::DIGITS {
819        Some(result)
820    } else {
821        None
822    }
823}
824
825// Serialize our calculation tree into a "calc" AST
826// https://www.w3.org/TR/css-values-4/#serialize-a-calculation-tree
827// Impl. note: we cannot strictly follow the spec, we need to adapt it to our
828// typed AST
829fn serialize_calculation_node_into_calc_sum(calc_node: &CalcNode) -> CalcSum {
830    match calc_node {
831        CalcNode::Number(_)
832        | CalcNode::Dimension(_)
833        | CalcNode::Percentage(_)
834        | CalcNode::Constant(_)
835        | CalcNode::Function(_) => CalcSum {
836            expressions: vec![serialize_calc_node_into_calc_product(calc_node)],
837            span: Span::dummy_with_cmt(),
838        },
839        CalcNode::OperatorNode(op) => match &**op {
840            CalcOperatorNode::Sum(nodes) => {
841                let mut expr: Vec<CalcProductOrOperator> = Vec::new();
842
843                let nodes = sort_calculations_children(nodes);
844
845                let mut nodes_iter = nodes.iter();
846
847                if let Some(calc_node) = nodes_iter.next() {
848                    expr.push(serialize_calc_node_into_calc_product(calc_node));
849                }
850
851                for calc_node in nodes_iter {
852                    match calc_node {
853                        CalcNode::Number(_) | CalcNode::Dimension(_) | CalcNode::Percentage(_) => {
854                            let value = get_value(calc_node);
855                            if value.is_sign_negative() {
856                                // Instead of serializing "x + -10", we want to have "x - 10"
857                                let mut node = calc_node.clone();
858                                set_value(&mut node, -value);
859                                expr.push(CalcProductOrOperator::Operator(CalcOperator {
860                                    value: CalcOperatorType::Sub,
861                                    span: Span::dummy_with_cmt(),
862                                }));
863                                expr.push(serialize_calc_node_into_calc_product(&node));
864                            } else {
865                                expr.push(CalcProductOrOperator::Operator(CalcOperator {
866                                    value: CalcOperatorType::Add,
867                                    span: Span::dummy_with_cmt(),
868                                }));
869                                expr.push(serialize_calc_node_into_calc_product(calc_node));
870                            }
871                        }
872                        CalcNode::Constant(_) | CalcNode::Function(_) => {
873                            expr.push(CalcProductOrOperator::Operator(CalcOperator {
874                                value: CalcOperatorType::Add,
875                                span: Span::dummy_with_cmt(),
876                            }));
877                            expr.push(serialize_calc_node_into_calc_product(calc_node));
878                        }
879                        CalcNode::OperatorNode(op) => match &**op {
880                            CalcOperatorNode::Product(_) => {
881                                expr.push(CalcProductOrOperator::Operator(CalcOperator {
882                                    value: CalcOperatorType::Add,
883                                    span: Span::dummy_with_cmt(),
884                                }));
885                                expr.push(serialize_calc_node_into_calc_product(calc_node));
886                            }
887                            CalcOperatorNode::Negate(calc_node) => {
888                                expr.push(CalcProductOrOperator::Operator(CalcOperator {
889                                    value: CalcOperatorType::Sub,
890                                    span: Span::dummy_with_cmt(),
891                                }));
892                                expr.push(serialize_calc_node_into_calc_product(calc_node));
893                            }
894                            _ => unreachable!("Cannot transform sum children into CalcProduct"),
895                        },
896                    }
897                }
898                CalcSum {
899                    expressions: expr,
900                    span: Span::dummy_with_cmt(),
901                }
902            }
903            _ => CalcSum {
904                expressions: vec![serialize_calc_node_into_calc_product(calc_node)],
905                span: Span::dummy_with_cmt(),
906            },
907        },
908    }
909}
910
911fn serialize_calc_node_into_calc_product(calc_node: &CalcNode) -> CalcProductOrOperator {
912    match calc_node {
913        CalcNode::Number(_)
914        | CalcNode::Dimension(_)
915        | CalcNode::Percentage(_)
916        | CalcNode::Constant(_)
917        | CalcNode::Function(_) => CalcProductOrOperator::Product(CalcProduct {
918            expressions: vec![CalcValueOrOperator::Value(
919                serialize_calc_node_into_calc_value(calc_node),
920            )],
921            span: Span::dummy_with_cmt(),
922        }),
923        CalcNode::OperatorNode(op) => match &**op {
924            CalcOperatorNode::Negate(_) => CalcProductOrOperator::Product(CalcProduct {
925                expressions: vec![
926                    CalcValueOrOperator::Value(CalcValue::Number(Number {
927                        value: -1.0,
928                        span: Span::dummy_with_cmt(),
929                        raw: None,
930                    })),
931                    CalcValueOrOperator::Operator(CalcOperator {
932                        value: CalcOperatorType::Mul,
933                        span: Span::dummy_with_cmt(),
934                    }),
935                    CalcValueOrOperator::Value(serialize_calc_node_into_calc_value(calc_node)),
936                ],
937                span: Span::dummy_with_cmt(),
938            }),
939            CalcOperatorNode::Invert(_) => CalcProductOrOperator::Product(CalcProduct {
940                expressions: vec![
941                    CalcValueOrOperator::Value(CalcValue::Number(Number {
942                        value: 1.0,
943                        span: Span::dummy_with_cmt(),
944                        raw: None,
945                    })),
946                    CalcValueOrOperator::Operator(CalcOperator {
947                        value: CalcOperatorType::Div,
948                        span: Span::dummy_with_cmt(),
949                    }),
950                    CalcValueOrOperator::Value(serialize_calc_node_into_calc_value(calc_node)),
951                ],
952                span: Span::dummy_with_cmt(),
953            }),
954            CalcOperatorNode::Product(nodes) => {
955                let mut expr: Vec<CalcValueOrOperator> = Vec::new();
956
957                let nodes = sort_calculations_children(nodes);
958
959                let mut nodes_iter = nodes.iter();
960
961                if let Some(calc_node) = nodes_iter.next() {
962                    expr.push(CalcValueOrOperator::Value(
963                        serialize_calc_node_into_calc_value(calc_node),
964                    ))
965                }
966
967                for calc_node in nodes_iter {
968                    match calc_node {
969                        CalcNode::Number(_)
970                        | CalcNode::Dimension(_)
971                        | CalcNode::Percentage(_)
972                        | CalcNode::Constant(_)
973                        | CalcNode::Function(_) => {
974                            expr.push(CalcValueOrOperator::Operator(CalcOperator {
975                                value: CalcOperatorType::Mul,
976                                span: Span::dummy_with_cmt(),
977                            }));
978                            expr.push(CalcValueOrOperator::Value(
979                                serialize_calc_node_into_calc_value(calc_node),
980                            ));
981                        }
982                        CalcNode::OperatorNode(op) => match &**op {
983                            CalcOperatorNode::Invert(calc_node) => {
984                                expr.push(CalcValueOrOperator::Operator(CalcOperator {
985                                    value: CalcOperatorType::Div,
986                                    span: Span::dummy_with_cmt(),
987                                }));
988                                expr.push(CalcValueOrOperator::Value(
989                                    serialize_calc_node_into_calc_value(calc_node),
990                                ));
991                            }
992                            CalcOperatorNode::Product(_) | CalcOperatorNode::Sum(_) => {
993                                expr.push(CalcValueOrOperator::Operator(CalcOperator {
994                                    value: CalcOperatorType::Mul,
995                                    span: Span::dummy_with_cmt(),
996                                }));
997                                expr.push(CalcValueOrOperator::Value(
998                                    serialize_calc_node_into_calc_value(calc_node),
999                                ));
1000                            }
1001                            _ => unreachable!("Cannot transform product children into CalcProduct"),
1002                        },
1003                    }
1004                }
1005                CalcProductOrOperator::Product(CalcProduct {
1006                    expressions: expr,
1007                    span: Span::dummy_with_cmt(),
1008                })
1009            }
1010            CalcOperatorNode::Sum(_) => CalcProductOrOperator::Product(CalcProduct {
1011                expressions: vec![CalcValueOrOperator::Value(
1012                    serialize_calc_node_into_calc_value(calc_node),
1013                )],
1014                span: Span::dummy_with_cmt(),
1015            }),
1016        },
1017    }
1018}
1019
1020fn serialize_calc_node_into_calc_value(calc_node: &CalcNode) -> CalcValue {
1021    match calc_node {
1022        CalcNode::Number(n) => CalcValue::Number(n.clone()),
1023        CalcNode::Dimension(d) => CalcValue::Dimension(d.clone()),
1024        CalcNode::Percentage(p) => CalcValue::Percentage(p.clone()),
1025        CalcNode::Constant(i) => CalcValue::Constant(i.clone()),
1026        CalcNode::Function(f) => CalcValue::Function(f.clone()),
1027        CalcNode::OperatorNode(op) => match &**op {
1028            CalcOperatorNode::Sum(_) => {
1029                CalcValue::Sum(serialize_calculation_node_into_calc_sum(calc_node))
1030            }
1031            CalcOperatorNode::Product(_) => CalcValue::Sum(CalcSum {
1032                expressions: vec![serialize_calc_node_into_calc_product(calc_node)],
1033                span: Span::dummy_with_cmt(),
1034            }),
1035            _ => unreachable!("Cannot transform CalcNode::OperatorNode into CalcValue"),
1036        },
1037    }
1038}
1039
1040// https://www.w3.org/TR/css-values-4/#sort-a-calculations-children
1041// Impl. note: since some computations cannot be done (because of a potential
1042// loss of precision), we need to keep all numbers, percentages, and so on
1043// (instead of only one)
1044fn sort_calculations_children(nodes: &[CalcNode]) -> Vec<CalcNode> {
1045    let mut ret: Vec<CalcNode> = Vec::new();
1046
1047    // If nodes contains a number, remove it from nodes and append it to ret.
1048    let mut numbers: Vec<CalcNode> = nodes
1049        .iter()
1050        .filter(|n| match n {
1051            CalcNode::Number(_) => true,
1052            _ => false,
1053        })
1054        .cloned()
1055        .collect();
1056    ret.append(&mut numbers);
1057
1058    // If nodes contains a percentage, remove it from nodes and append it to ret.
1059    let mut percentages: Vec<CalcNode> = nodes
1060        .iter()
1061        .filter(|n| match n {
1062            CalcNode::Percentage(_) => true,
1063            _ => false,
1064        })
1065        .cloned()
1066        .collect();
1067    ret.append(&mut percentages);
1068
1069    // If nodes contains any dimensions, remove them from nodes, sort them by their
1070    // units, ordered ASCII case-insensitively, and append them to ret.
1071    let mut dimensions: Vec<CalcNode> = nodes
1072        .iter()
1073        .filter(|n| match n {
1074            CalcNode::Dimension(_) => true,
1075            _ => false,
1076        })
1077        .cloned()
1078        .collect();
1079    dimensions.sort_by(|a, b| match (a, b) {
1080        (CalcNode::Dimension(d1), CalcNode::Dimension(d2)) => {
1081            let u1 = get_dimension_unit_lowercase(d1);
1082            let u2 = get_dimension_unit_lowercase(d2);
1083            u1.cmp(&u2)
1084        }
1085        _ => unreachable!("The vector should only contain dimensions"),
1086    });
1087    ret.append(&mut dimensions);
1088
1089    // If nodes still contains any items, append them to ret in the same order.
1090    let mut any_items: Vec<CalcNode> = nodes
1091        .iter()
1092        .filter(|n| match n {
1093            CalcNode::Number(_) | CalcNode::Percentage(_) | CalcNode::Dimension(_) => false,
1094            _ => true,
1095        })
1096        .cloned()
1097        .collect();
1098    ret.append(&mut any_items);
1099
1100    // Impl. note: not in spec, for a better compression, we need to be sure the
1101    // first item is not negative (if it exists)
1102    // "-3px + .75rem" => ".75rem - 3px"
1103    if let Some(idx) = ret.iter().position(is_positive_value) {
1104        // Move the first positive value at the beginning
1105        let positive_value = ret.remove(idx);
1106        ret.insert(0, positive_value);
1107    }
1108
1109    ret
1110}
1111
1112fn get_dimension_unit_lowercase(d: &Dimension) -> Atom {
1113    match d {
1114        Dimension::Length(l) => l.unit.value.to_ascii_lowercase(),
1115        Dimension::Angle(a) => a.unit.value.to_ascii_lowercase(),
1116        Dimension::Time(t) => t.unit.value.to_ascii_lowercase(),
1117        Dimension::Frequency(f) => f.unit.value.to_ascii_lowercase(),
1118        Dimension::Resolution(r) => r.unit.value.to_ascii_lowercase(),
1119        Dimension::Flex(f) => f.unit.value.to_ascii_lowercase(),
1120        Dimension::UnknownDimension(u) => u.unit.value.to_ascii_lowercase(),
1121    }
1122}
1123
1124fn is_positive_value(calc_node: &CalcNode) -> bool {
1125    match calc_node {
1126        CalcNode::Number(_) | CalcNode::Percentage(_) | CalcNode::Dimension(_) => {
1127            get_value(calc_node).is_sign_positive()
1128        }
1129        _ => false,
1130    }
1131}
1132
1133fn get_value(calc_node: &CalcNode) -> f64 {
1134    match calc_node {
1135        CalcNode::Number(n) => n.value,
1136        CalcNode::Percentage(p) => p.value.value,
1137        CalcNode::Dimension(Dimension::Length(l)) => l.value.value,
1138        CalcNode::Dimension(Dimension::Angle(a)) => a.value.value,
1139        CalcNode::Dimension(Dimension::Time(t)) => t.value.value,
1140        CalcNode::Dimension(Dimension::Frequency(f)) => f.value.value,
1141        CalcNode::Dimension(Dimension::Resolution(r)) => r.value.value,
1142        CalcNode::Dimension(Dimension::Flex(f)) => f.value.value,
1143        CalcNode::Dimension(Dimension::UnknownDimension(u)) => u.value.value,
1144        _ => unreachable!("Can only get a value from a value node"),
1145    }
1146}
1147
1148fn set_value(calc_node: &mut CalcNode, value: f64) {
1149    match calc_node {
1150        CalcNode::Number(n) => n.value = value,
1151        CalcNode::Percentage(p) => p.value.value = value,
1152        CalcNode::Dimension(Dimension::Length(l)) => l.value.value = value,
1153        CalcNode::Dimension(Dimension::Angle(a)) => a.value.value = value,
1154        CalcNode::Dimension(Dimension::Time(t)) => t.value.value = value,
1155        CalcNode::Dimension(Dimension::Frequency(f)) => f.value.value = value,
1156        CalcNode::Dimension(Dimension::Resolution(r)) => r.value.value = value,
1157        CalcNode::Dimension(Dimension::Flex(f)) => f.value.value = value,
1158        CalcNode::Dimension(Dimension::UnknownDimension(u)) => u.value.value = value,
1159        _ => unreachable!("Can only set a value into a value node"),
1160    }
1161}
1162
1163impl Compressor {
1164    // https://www.w3.org/TR/css-values-4/#calculation-tree
1165    pub(super) fn compress_calc_sum(&mut self, calc_sum: &mut CalcSum) {
1166        let root_calc_node = collect_calc_sum_into_calc_node(calc_sum);
1167
1168        let simplified_calc_tree = simplify_calc_node(&root_calc_node);
1169
1170        let simplified_calc_sum = serialize_calculation_node_into_calc_sum(&simplified_calc_tree);
1171        calc_sum.expressions = simplified_calc_sum.expressions;
1172    }
1173
1174    pub(super) fn compress_calc_sum_in_component_value(
1175        &mut self,
1176        component_value: &mut ComponentValue,
1177    ) {
1178        match &component_value {
1179            // Transform "calc(calc-sum)" into "simple value" when calc-sum is not a complex
1180            // expression
1181            ComponentValue::Function(function)
1182                if is_calc_function_name(&function.name) && function.value.len() == 1 =>
1183            {
1184                match &function.value[0] {
1185                    ComponentValue::CalcSum(calc_sum) if calc_sum.expressions.len() == 1 => {
1186                        match &calc_sum.expressions[0] {
1187                            CalcProductOrOperator::Product(CalcProduct {
1188                                expressions: calc_product_expressions,
1189                                ..
1190                            }) if calc_product_expressions.len() == 1 => {
1191                                if let CalcValueOrOperator::Value(calc_value) =
1192                                    &calc_product_expressions[0]
1193                                {
1194                                    if let Some(cv) =
1195                                        transform_calc_value_into_component_value(calc_value)
1196                                    {
1197                                        *component_value = cv;
1198                                    }
1199                                }
1200                            }
1201                            _ => {}
1202                        }
1203                    }
1204                    _ => {}
1205                }
1206            }
1207            _ => {}
1208        }
1209    }
1210}