use azul_css::props::{
basic::{
pixel::PT_TO_PX,
PixelValue, SizeMetric,
},
layout::dimensions::{CalcAstItem, CalcAstItemVec},
};
const PX_PER_INCH: f32 = 96.0;
const CM_PER_INCH: f32 = 2.54;
const MM_PER_INCH: f32 = 25.4;
#[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)
}
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() * PX_PER_INCH,
SizeMetric::Cm => pv.number.get() * PX_PER_INCH / CM_PER_INCH,
SizeMetric::Mm => pv.number.get() * PX_PER_INCH / MM_PER_INCH,
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()
}
}
}
pub fn resolve_pixel_value_with_viewport(
pv: &PixelValue,
basis: f32,
em_size: f32,
rem_size: f32,
viewport_width: f32,
viewport_height: f32,
) -> f32 {
match pv.metric {
SizeMetric::Vw => pv.number.get() / 100.0 * viewport_width,
SizeMetric::Vh => pv.number.get() / 100.0 * viewport_height,
SizeMetric::Vmin => pv.number.get() / 100.0 * viewport_width.min(viewport_height),
SizeMetric::Vmax => pv.number.get() / 100.0 * viewport_width.max(viewport_height),
_ => resolve_pixel_value(pv, basis, em_size, rem_size),
}
}