use azul_css::props::{
basic::{
pixel::{DEFAULT_FONT_SIZE, PT_TO_PX},
PixelValue, SizeMetric,
},
layout::dimensions::{CalcAstItem, CalcAstItemVec},
};
#[derive(Debug, Clone)]
#[repr(C)]
pub struct CalcResolveContext {
pub items: CalcAstItemVec,
pub em_size: f32,
pub rem_size: f32,
}
#[derive(Clone, Debug)]
enum CalcFlatItem {
Num(f32),
Op(CalcOp),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum CalcOp {
Add,
Sub,
Mul,
Div,
}
pub fn evaluate_calc(ctx: &CalcResolveContext, basis: f32) -> f32 {
evaluate_calc_ast(ctx.items.as_slice(), basis, ctx.em_size, ctx.rem_size)
}
pub fn evaluate_calc_ast(
items: &[CalcAstItem],
basis: f32,
em_size: f32,
rem_size: f32,
) -> f32 {
let mut flat: Vec<CalcFlatItem> = Vec::with_capacity(items.len());
let mut i = 0;
while i < items.len() {
match &items[i] {
CalcAstItem::Value(pv) => {
flat.push(CalcFlatItem::Num(resolve_pixel_value(
pv, basis, em_size, rem_size,
)));
}
CalcAstItem::Add => flat.push(CalcFlatItem::Op(CalcOp::Add)),
CalcAstItem::Sub => flat.push(CalcFlatItem::Op(CalcOp::Sub)),
CalcAstItem::Mul => flat.push(CalcFlatItem::Op(CalcOp::Mul)),
CalcAstItem::Div => flat.push(CalcFlatItem::Op(CalcOp::Div)),
CalcAstItem::BraceOpen => {
let start = i + 1;
let mut depth = 1u32;
let mut j = start;
while j < items.len() && depth > 0 {
match &items[j] {
CalcAstItem::BraceOpen => depth += 1,
CalcAstItem::BraceClose => depth -= 1,
_ => {}
}
if depth > 0 {
j += 1;
}
}
let sub_val = evaluate_calc_ast(&items[start..j], basis, em_size, rem_size);
flat.push(CalcFlatItem::Num(sub_val));
i = j; }
CalcAstItem::BraceClose => { }
}
i += 1;
}
let mut pass2: Vec<CalcFlatItem> = Vec::with_capacity(flat.len());
let mut k = 0;
while k < flat.len() {
if let CalcFlatItem::Op(op @ (CalcOp::Mul | CalcOp::Div)) = &flat[k] {
if let (Some(CalcFlatItem::Num(lhs)), Some(CalcFlatItem::Num(rhs))) =
(pass2.last(), flat.get(k + 1))
{
let result = match op {
CalcOp::Mul => lhs * rhs,
CalcOp::Div => {
if *rhs != 0.0 {
lhs / rhs
} else {
0.0
}
}
_ => unreachable!(),
};
*pass2.last_mut().unwrap() = CalcFlatItem::Num(result);
k += 2; continue;
}
}
pass2.push(flat[k].clone());
k += 1;
}
let mut result = match pass2.first() {
Some(CalcFlatItem::Num(v)) => *v,
_ => return 0.0,
};
let mut m = 1;
while m < pass2.len() {
if let (CalcFlatItem::Op(op), Some(CalcFlatItem::Num(rhs))) =
(&pass2[m], pass2.get(m + 1))
{
match op {
CalcOp::Add => result += rhs,
CalcOp::Sub => result -= rhs,
_ => {} }
m += 2;
} else {
m += 1;
}
}
result
}
pub fn resolve_pixel_value(
pv: &PixelValue,
basis: f32,
em_size: f32,
rem_size: f32,
) -> f32 {
match pv.metric {
SizeMetric::Px => pv.number.get(),
SizeMetric::Pt => pv.number.get() * PT_TO_PX,
SizeMetric::In => pv.number.get() * 96.0,
SizeMetric::Cm => pv.number.get() * 96.0 / 2.54,
SizeMetric::Mm => pv.number.get() * 96.0 / 25.4,
SizeMetric::Em => pv.number.get() * em_size,
SizeMetric::Rem => pv.number.get() * rem_size,
SizeMetric::Percent => basis * (pv.number.get() / 100.0),
SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => {
pv.number.get()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use azul_css::props::basic::{FloatValue, PixelValue, SizeMetric};
fn pv(metric: SizeMetric, number: f32) -> PixelValue {
PixelValue {
metric,
number: FloatValue::new(number),
}
}
fn items(v: Vec<CalcAstItem>) -> CalcAstItemVec {
v.into()
}
fn ctx(ast: Vec<CalcAstItem>) -> CalcResolveContext {
CalcResolveContext {
items: items(ast),
em_size: DEFAULT_FONT_SIZE,
rem_size: DEFAULT_FONT_SIZE,
}
}
fn ctx_with_fonts(ast: Vec<CalcAstItem>, em: f32, rem: f32) -> CalcResolveContext {
CalcResolveContext {
items: items(ast),
em_size: em,
rem_size: rem,
}
}
#[test]
fn single_px_value() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Px, 100.0))]);
assert_eq!(evaluate_calc(&c, 0.0), 100.0);
}
#[test]
fn single_percent_value() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Percent, 50.0))]);
assert_eq!(evaluate_calc(&c, 400.0), 200.0);
}
#[test]
fn single_pt_value() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Pt, 12.0))]);
let result = evaluate_calc(&c, 0.0);
assert!((result - 16.0).abs() < 0.01);
}
#[test]
fn simple_addition() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 150.0);
}
#[test]
fn simple_subtraction() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 200.0)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 150.0);
}
#[test]
fn percent_minus_px() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Percent, 100.0)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
]);
assert_eq!(evaluate_calc(&c, 300.0), 280.0);
}
#[test]
fn thirds_calc() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Percent, 33.333)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
]);
let result = evaluate_calc(&c, 900.0);
assert!((result - 289.997).abs() < 0.01);
}
#[test]
fn simple_multiplication() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 50.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 150.0);
}
#[test]
fn simple_division() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 300.0)),
CalcAstItem::Div,
CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 75.0);
}
#[test]
fn division_by_zero() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
CalcAstItem::Div,
CalcAstItem::Value(pv(SizeMetric::Px, 0.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 0.0);
}
#[test]
fn precedence_mul_before_add() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 25.0);
}
#[test]
fn precedence_div_before_sub() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 60.0)),
CalcAstItem::Div,
CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 80.0);
}
#[test]
fn precedence_complex() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 26.0);
}
#[test]
fn simple_parens() {
let c = ctx(vec![
CalcAstItem::BraceOpen,
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
CalcAstItem::BraceClose,
]);
assert_eq!(evaluate_calc(&c, 0.0), 30.0);
}
#[test]
fn parens_override_precedence() {
let c = ctx(vec![
CalcAstItem::BraceOpen,
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
CalcAstItem::BraceClose,
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 3.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 45.0);
}
#[test]
fn nested_parens() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
CalcAstItem::Sub,
CalcAstItem::BraceOpen,
CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
CalcAstItem::Add,
CalcAstItem::BraceOpen,
CalcAstItem::Value(pv(SizeMetric::Px, 5.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
CalcAstItem::BraceClose,
CalcAstItem::BraceClose,
]);
assert_eq!(evaluate_calc(&c, 0.0), 70.0);
}
#[test]
fn em_with_default_font_size() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Em, 2.0))]);
assert_eq!(evaluate_calc(&c, 0.0), DEFAULT_FONT_SIZE * 2.0);
}
#[test]
fn em_with_custom_font_size() {
let c = ctx_with_fonts(
vec![CalcAstItem::Value(pv(SizeMetric::Em, 2.0))],
24.0,
16.0,
);
assert_eq!(evaluate_calc(&c, 0.0), 48.0);
}
#[test]
fn rem_with_custom_root_font_size() {
let c = ctx_with_fonts(
vec![CalcAstItem::Value(pv(SizeMetric::Rem, 1.5))],
16.0,
20.0,
);
assert_eq!(evaluate_calc(&c, 0.0), 30.0);
}
#[test]
fn em_and_rem_differ() {
let c = ctx_with_fonts(
vec![
CalcAstItem::Value(pv(SizeMetric::Em, 1.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Rem, 1.0)),
],
24.0,
20.0,
);
assert_eq!(evaluate_calc(&c, 0.0), 44.0);
}
#[test]
fn em_percent_mixed() {
let c = ctx_with_fonts(
vec![
CalcAstItem::Value(pv(SizeMetric::Percent, 50.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Em, 2.0)),
],
12.0,
16.0,
);
assert_eq!(evaluate_calc(&c, 200.0), 124.0);
}
#[test]
fn flexbox_three_column() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Percent, 33.333)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
]);
let result = evaluate_calc(&c, 600.0);
assert!((result - 189.998).abs() < 0.01);
}
#[test]
fn sidebar_main_layout() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Percent, 100.0)),
CalcAstItem::Sub,
CalcAstItem::Value(pv(SizeMetric::Px, 250.0)),
]);
assert_eq!(evaluate_calc(&c, 1024.0), 774.0);
}
#[test]
fn responsive_padding() {
let c = ctx_with_fonts(
vec![
CalcAstItem::Value(pv(SizeMetric::Rem, 1.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Percent, 2.0)),
],
16.0,
16.0,
);
assert_eq!(evaluate_calc(&c, 800.0), 32.0);
}
#[test]
fn empty_expression() {
let c = ctx(vec![]);
assert_eq!(evaluate_calc(&c, 100.0), 0.0);
}
#[test]
fn only_operator_no_values() {
let c = ctx(vec![CalcAstItem::Add]);
assert_eq!(evaluate_calc(&c, 100.0), 0.0);
}
#[test]
fn multiple_additions() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 10.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 20.0)),
CalcAstItem::Add,
CalcAstItem::Value(pv(SizeMetric::Px, 30.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 60.0);
}
#[test]
fn cm_unit() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Cm, 2.54))]);
let result = evaluate_calc(&c, 0.0);
assert!((result - 96.0).abs() < 0.01);
}
#[test]
fn mm_unit() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::Mm, 25.4))]);
let result = evaluate_calc(&c, 0.0);
assert!((result - 96.0).abs() < 0.01);
}
#[test]
fn in_unit() {
let c = ctx(vec![CalcAstItem::Value(pv(SizeMetric::In, 1.0))]);
assert_eq!(evaluate_calc(&c, 0.0), 96.0);
}
#[test]
fn chained_mul_div() {
let c = ctx(vec![
CalcAstItem::Value(pv(SizeMetric::Px, 100.0)),
CalcAstItem::Mul,
CalcAstItem::Value(pv(SizeMetric::Px, 2.0)),
CalcAstItem::Div,
CalcAstItem::Value(pv(SizeMetric::Px, 4.0)),
]);
assert_eq!(evaluate_calc(&c, 0.0), 50.0);
}
#[test]
fn resolve_px() {
assert_eq!(resolve_pixel_value(&pv(SizeMetric::Px, 42.0), 0.0, 16.0, 16.0), 42.0);
}
#[test]
fn resolve_percent() {
assert_eq!(resolve_pixel_value(&pv(SizeMetric::Percent, 25.0), 400.0, 16.0, 16.0), 100.0);
}
#[test]
fn resolve_em_custom() {
assert_eq!(resolve_pixel_value(&pv(SizeMetric::Em, 2.0), 0.0, 20.0, 16.0), 40.0);
}
#[test]
fn resolve_rem_custom() {
assert_eq!(resolve_pixel_value(&pv(SizeMetric::Rem, 2.0), 0.0, 20.0, 18.0), 36.0);
}
}