1use azul_css::props::{
11 basic::{
12 pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
13 PixelValue, SizeMetric,
14 },
15 layout::dimensions::{CalcAstItem, CalcAstItemVec},
16};
17
18#[derive(Debug, Clone)]
24#[repr(C)]
25pub struct CalcResolveContext {
26 pub items: CalcAstItemVec,
28 pub em_size: f32,
30 pub rem_size: f32,
32}
33
34#[derive(Clone, Debug)]
36enum CalcFlatItem {
37 Num(f32),
38 Op(CalcOp),
39}
40
41#[derive(Clone, Copy, Debug, PartialEq)]
43enum CalcOp {
44 Add,
45 Sub,
46 Mul,
47 Div,
48}
49
50pub fn evaluate_calc(ctx: &CalcResolveContext, basis: f32) -> f32 {
53 evaluate_calc_ast(ctx.items.as_slice(), basis, ctx.em_size, ctx.rem_size)
54}
55
56pub fn evaluate_calc_ast(
67 items: &[CalcAstItem],
68 basis: f32,
69 em_size: f32,
70 rem_size: f32,
71) -> f32 {
72 let mut flat: Vec<CalcFlatItem> = Vec::with_capacity(items.len());
74 let mut i = 0;
75 while i < items.len() {
76 match &items[i] {
77 CalcAstItem::Value(pv) => {
78 flat.push(CalcFlatItem::Num(resolve_pixel_value(
79 pv, basis, em_size, rem_size,
80 )));
81 }
82 CalcAstItem::Add => flat.push(CalcFlatItem::Op(CalcOp::Add)),
83 CalcAstItem::Sub => flat.push(CalcFlatItem::Op(CalcOp::Sub)),
84 CalcAstItem::Mul => flat.push(CalcFlatItem::Op(CalcOp::Mul)),
85 CalcAstItem::Div => flat.push(CalcFlatItem::Op(CalcOp::Div)),
86 CalcAstItem::BraceOpen => {
87 let start = i + 1;
89 let mut depth = 1u32;
90 let mut j = start;
91 while j < items.len() && depth > 0 {
92 match &items[j] {
93 CalcAstItem::BraceOpen => depth += 1,
94 CalcAstItem::BraceClose => depth -= 1,
95 _ => {}
96 }
97 if depth > 0 {
98 j += 1;
99 }
100 }
101 let sub_val = evaluate_calc_ast(&items[start..j], basis, em_size, rem_size);
103 flat.push(CalcFlatItem::Num(sub_val));
104 i = j; }
106 CalcAstItem::BraceClose => { }
107 }
108 i += 1;
109 }
110
111 let mut pass2: Vec<CalcFlatItem> = Vec::with_capacity(flat.len());
113 let mut k = 0;
114 while k < flat.len() {
115 if let CalcFlatItem::Op(op @ (CalcOp::Mul | CalcOp::Div)) = &flat[k] {
116 if let (Some(CalcFlatItem::Num(lhs)), Some(CalcFlatItem::Num(rhs))) =
118 (pass2.last(), flat.get(k + 1))
119 {
120 let result = match op {
121 CalcOp::Mul => lhs * rhs,
122 CalcOp::Div => {
123 if *rhs != 0.0 {
124 lhs / rhs
125 } else {
126 0.0
127 }
128 }
129 _ => unreachable!(),
130 };
131 *pass2.last_mut().unwrap() = CalcFlatItem::Num(result);
132 k += 2; continue;
134 }
135 }
136 pass2.push(flat[k].clone());
137 k += 1;
138 }
139
140 let mut result = match pass2.first() {
142 Some(CalcFlatItem::Num(v)) => *v,
143 _ => return 0.0,
144 };
145 let mut m = 1;
146 while m < pass2.len() {
147 if let (CalcFlatItem::Op(op), Some(CalcFlatItem::Num(rhs))) =
148 (&pass2[m], pass2.get(m + 1))
149 {
150 match op {
151 CalcOp::Add => result += rhs,
152 CalcOp::Sub => result -= rhs,
153 _ => {} }
155 m += 2;
156 } else {
157 m += 1;
158 }
159 }
160
161 result
162}
163
164pub fn resolve_pixel_value(
170 pv: &PixelValue,
171 basis: f32,
172 em_size: f32,
173 rem_size: f32,
174) -> f32 {
175 match pv.metric {
176 SizeMetric::Px => pv.number.get(),
177 SizeMetric::Pt => pv.number.get() * PT_TO_PX,
178 SizeMetric::In => pv.number.get() * 96.0,
179 SizeMetric::Cm => pv.number.get() * 96.0 / 2.54,
180 SizeMetric::Mm => pv.number.get() * 96.0 / 25.4,
181 SizeMetric::Em => pv.number.get() * em_size,
182 SizeMetric::Rem => pv.number.get() * rem_size,
183 SizeMetric::Percent => basis * (pv.number.get() / 100.0),
184 SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
185 pv.number.get()
187 }
188 }
189}
190
191#[cfg(test)]
196mod tests {
197 use super::*;
198 use azul_css::props::basic::{FloatValue, PixelValue, SizeMetric};
199
200 fn pv(metric: SizeMetric, number: f32) -> PixelValue {
202 PixelValue {
203 metric,
204 number: FloatValue::new(number),
205 }
206 }
207
208 fn items(v: Vec<CalcAstItem>) -> CalcAstItemVec {
210 v.into()
211 }
212
213 fn ctx(ast: Vec<CalcAstItem>) -> CalcResolveContext {
215 CalcResolveContext {
216 items: items(ast),
217 em_size: DEFAULT_FONT_SIZE,
218 rem_size: DEFAULT_FONT_SIZE,
219 }
220 }
221
222 fn ctx_with_fonts(ast: Vec<CalcAstItem>, em: f32, rem: f32) -> CalcResolveContext {
224 CalcResolveContext {
225 items: items(ast),
226 em_size: em,
227 rem_size: rem,
228 }
229 }
230
231 #[test]
236 fn single_px_value() {
237 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Px, 100.0))]);
239 assert_eq!(evaluate_calc(&c, 0.0), 100.0);
240 }
241
242 #[test]
243 fn single_percent_value() {
244 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Percent, 50.0))]);
246 assert_eq!(evaluate_calc(&c, 400.0), 200.0);
247 }
248
249 #[test]
250 fn single_pt_value() {
251 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Pt, 12.0))]);
253 let result = evaluate_calc(&c, 0.0);
254 assert!((result - 16.0).abs() < 0.01);
255 }
256
257 #[test]
262 fn simple_addition() {
263 let c = ctx(vec![
265 CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
266 CalcAstItem::Add,
267 CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
268 ]);
269 assert_eq!(evaluate_calc(&c, 0.0), 150.0);
270 }
271
272 #[test]
273 fn simple_subtraction() {
274 let c = ctx(vec![
276 CalcAstItem::Value(pv(SizeMetric::Px, 200.0)),
277 CalcAstItem::Sub,
278 CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
279 ]);
280 assert_eq!(evaluate_calc(&c, 0.0), 150.0);
281 }
282
283 #[test]
284 fn percent_minus_px() {
285 let c = ctx(vec![
287 CalcAstItem::Value(pv(SizeMetric::Percent, 100.0)),
288 CalcAstItem::Sub,
289 CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
290 ]);
291 assert_eq!(evaluate_calc(&c, 300.0), 280.0);
292 }
293
294 #[test]
295 fn thirds_calc() {
296 let c = ctx(vec![
298 CalcAstItem::Value(pv(SizeMetric::Percent, 33.333)),
299 CalcAstItem::Sub,
300 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
301 ]);
302 let result = evaluate_calc(&c, 900.0);
303 assert!((result - 289.997).abs() < 0.01);
304 }
305
306 #[test]
311 fn simple_multiplication() {
312 let c = ctx(vec![
314 CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
315 CalcAstItem::Mul,
316 CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
317 ]);
318 assert_eq!(evaluate_calc(&c, 0.0), 150.0);
319 }
320
321 #[test]
322 fn simple_division() {
323 let c = ctx(vec![
325 CalcAstItem::Value(pv(SizeMetric::Px, 300.0)),
326 CalcAstItem::Div,
327 CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
328 ]);
329 assert_eq!(evaluate_calc(&c, 0.0), 75.0);
330 }
331
332 #[test]
333 fn division_by_zero() {
334 let c = ctx(vec![
336 CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
337 CalcAstItem::Div,
338 CalcAstItem::Value(pv(SizeMetric::Px, 0.0)),
339 ]);
340 assert_eq!(evaluate_calc(&c, 0.0), 0.0);
341 }
342
343 #[test]
348 fn precedence_mul_before_add() {
349 let c = ctx(vec![
351 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
352 CalcAstItem::Add,
353 CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
354 CalcAstItem::Mul,
355 CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
356 ]);
357 assert_eq!(evaluate_calc(&c, 0.0), 25.0);
358 }
359
360 #[test]
361 fn precedence_div_before_sub() {
362 let c = ctx(vec![
364 CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
365 CalcAstItem::Sub,
366 CalcAstItem::Value(pv(SizeMetric::Px, 60.0)),
367 CalcAstItem::Div,
368 CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
369 ]);
370 assert_eq!(evaluate_calc(&c, 0.0), 80.0);
371 }
372
373 #[test]
374 fn precedence_complex() {
375 let c = ctx(vec![
377 CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
378 CalcAstItem::Mul,
379 CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
380 CalcAstItem::Add,
381 CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
382 CalcAstItem::Mul,
383 CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
384 ]);
385 assert_eq!(evaluate_calc(&c, 0.0), 26.0);
386 }
387
388 #[test]
393 fn simple_parens() {
394 let c = ctx(vec![
396 CalcAstItem::BraceOpen,
397 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
398 CalcAstItem::Add,
399 CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
400 CalcAstItem::BraceClose,
401 ]);
402 assert_eq!(evaluate_calc(&c, 0.0), 30.0);
403 }
404
405 #[test]
406 fn parens_override_precedence() {
407 let c = ctx(vec![
409 CalcAstItem::BraceOpen,
410 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
411 CalcAstItem::Add,
412 CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
413 CalcAstItem::BraceClose,
414 CalcAstItem::Mul,
415 CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
416 ]);
417 assert_eq!(evaluate_calc(&c, 0.0), 45.0);
418 }
419
420 #[test]
421 fn nested_parens() {
422 let c = ctx(vec![
427 CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
428 CalcAstItem::Sub,
429 CalcAstItem::BraceOpen,
430 CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
431 CalcAstItem::Add,
432 CalcAstItem::BraceOpen,
433 CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
434 CalcAstItem::Mul,
435 CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
436 CalcAstItem::BraceClose,
437 CalcAstItem::BraceClose,
438 ]);
439 assert_eq!(evaluate_calc(&c, 0.0), 70.0);
440 }
441
442 #[test]
447 fn em_with_default_font_size() {
448 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Em, 2.0))]);
450 assert_eq!(evaluate_calc(&c, 0.0), DEFAULT_FONT_SIZE * 2.0);
451 }
452
453 #[test]
454 fn em_with_custom_font_size() {
455 let c = ctx_with_fonts(
457 vec![CalcAstItem::Value(pv(SizeMetric::Em, 2.0))],
458 24.0,
459 16.0,
460 );
461 assert_eq!(evaluate_calc(&c, 0.0), 48.0);
462 }
463
464 #[test]
465 fn rem_with_custom_root_font_size() {
466 let c = ctx_with_fonts(
468 vec![CalcAstItem::Value(pv(SizeMetric::Rem, 1.5))],
469 16.0,
470 20.0,
471 );
472 assert_eq!(evaluate_calc(&c, 0.0), 30.0);
473 }
474
475 #[test]
476 fn em_and_rem_differ() {
477 let c = ctx_with_fonts(
479 vec![
480 CalcAstItem::Value(pv(SizeMetric::Em, 1.0)),
481 CalcAstItem::Add,
482 CalcAstItem::Value(pv(SizeMetric::Rem, 1.0)),
483 ],
484 24.0,
485 20.0,
486 );
487 assert_eq!(evaluate_calc(&c, 0.0), 44.0);
488 }
489
490 #[test]
491 fn em_percent_mixed() {
492 let c = ctx_with_fonts(
494 vec![
495 CalcAstItem::Value(pv(SizeMetric::Percent, 50.0)),
496 CalcAstItem::Add,
497 CalcAstItem::Value(pv(SizeMetric::Em, 2.0)),
498 ],
499 12.0,
500 16.0,
501 );
502 assert_eq!(evaluate_calc(&c, 200.0), 124.0);
503 }
504
505 #[test]
510 fn flexbox_three_column() {
511 let c = ctx(vec![
513 CalcAstItem::Value(pv(SizeMetric::Percent, 33.333)),
514 CalcAstItem::Sub,
515 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
516 ]);
517 let result = evaluate_calc(&c, 600.0);
518 assert!((result - 189.998).abs() < 0.01);
520 }
521
522 #[test]
523 fn sidebar_main_layout() {
524 let c = ctx(vec![
526 CalcAstItem::Value(pv(SizeMetric::Percent, 100.0)),
527 CalcAstItem::Sub,
528 CalcAstItem::Value(pv(SizeMetric::Px, 250.0)),
529 ]);
530 assert_eq!(evaluate_calc(&c, 1024.0), 774.0);
531 }
532
533 #[test]
534 fn responsive_padding() {
535 let c = ctx_with_fonts(
537 vec![
538 CalcAstItem::Value(pv(SizeMetric::Rem, 1.0)),
539 CalcAstItem::Add,
540 CalcAstItem::Value(pv(SizeMetric::Percent, 2.0)),
541 ],
542 16.0,
543 16.0,
544 );
545 assert_eq!(evaluate_calc(&c, 800.0), 32.0);
546 }
547
548 #[test]
553 fn empty_expression() {
554 let c = ctx(vec![]);
555 assert_eq!(evaluate_calc(&c, 100.0), 0.0);
556 }
557
558 #[test]
559 fn only_operator_no_values() {
560 let c = ctx(vec![CalcAstItem::Add]);
561 assert_eq!(evaluate_calc(&c, 100.0), 0.0);
562 }
563
564 #[test]
565 fn multiple_additions() {
566 let c = ctx(vec![
568 CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
569 CalcAstItem::Add,
570 CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
571 CalcAstItem::Add,
572 CalcAstItem::Value(pv(SizeMetric::Px, 30.0)),
573 ]);
574 assert_eq!(evaluate_calc(&c, 0.0), 60.0);
575 }
576
577 #[test]
578 fn cm_unit() {
579 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Cm, 2.54))]);
581 let result = evaluate_calc(&c, 0.0);
582 assert!((result - 96.0).abs() < 0.01);
583 }
584
585 #[test]
586 fn mm_unit() {
587 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Mm, 25.4))]);
589 let result = evaluate_calc(&c, 0.0);
590 assert!((result - 96.0).abs() < 0.01);
591 }
592
593 #[test]
594 fn in_unit() {
595 let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::In, 1.0))]);
597 assert_eq!(evaluate_calc(&c, 0.0), 96.0);
598 }
599
600 #[test]
601 fn chained_mul_div() {
602 let c = ctx(vec![
604 CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
605 CalcAstItem::Mul,
606 CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
607 CalcAstItem::Div,
608 CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
609 ]);
610 assert_eq!(evaluate_calc(&c, 0.0), 50.0);
611 }
612
613 #[test]
618 fn resolve_px() {
619 assert_eq!(resolve_pixel_value(&pv(SizeMetric::Px, 42.0), 0.0, 16.0, 16.0), 42.0);
620 }
621
622 #[test]
623 fn resolve_percent() {
624 assert_eq!(resolve_pixel_value(&pv(SizeMetric::Percent, 25.0), 400.0, 16.0, 16.0), 100.0);
625 }
626
627 #[test]
628 fn resolve_em_custom() {
629 assert_eq!(resolve_pixel_value(&pv(SizeMetric::Em, 2.0), 0.0, 20.0, 16.0), 40.0);
630 }
631
632 #[test]
633 fn resolve_rem_custom() {
634 assert_eq!(resolve_pixel_value(&pv(SizeMetric::Rem, 2.0), 0.0, 20.0, 18.0), 36.0);
635 }
636}