use azul_css::props::{
basic::{
pixel::PT_TO_PX,
PixelValue, SizeMetric,
},
layout::dimensions::{CalcAstItem, CalcAstItemVec},
};
pub(super) const PX_PER_INCH: f32 = 96.0;
pub(super) const CM_PER_INCH: f32 = 2.54;
pub(super) 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,
}
#[allow(variant_size_differences)]
#[derive(Clone, Debug)]
enum CalcFlatItem {
Num(f32),
Op(CalcOp),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum CalcOp {
Add,
Sub,
Mul,
Div,
}
#[inline(never)] #[must_use] pub fn evaluate_calc(ctx: &CalcResolveContext, basis: f32) -> f32 {
evaluate_calc_ast(ctx.items.as_slice(), basis, ctx.em_size, ctx.rem_size)
}
#[inline(never)] 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 {
0.0
} else {
lhs / rhs
}
}
_ => 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
}
#[must_use] 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()
}
}
}
#[inline(never)] #[must_use] 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),
}
}
#[must_use] pub fn resolve_pixel_value_no_percent(
pv: &PixelValue,
em_size: f32,
rem_size: f32,
) -> Option<f32> {
match pv.metric {
SizeMetric::Px => Some(pv.number.get()),
SizeMetric::Pt => Some(pv.number.get() * PT_TO_PX),
SizeMetric::In => Some(pv.number.get() * PX_PER_INCH),
SizeMetric::Cm => Some(pv.number.get() * PX_PER_INCH / CM_PER_INCH),
SizeMetric::Mm => Some(pv.number.get() * PX_PER_INCH / MM_PER_INCH),
SizeMetric::Em => Some(pv.number.get() * em_size),
SizeMetric::Rem => Some(pv.number.get() * rem_size),
SizeMetric::Percent
| SizeMetric::Vw
| SizeMetric::Vh
| SizeMetric::Vmin
| SizeMetric::Vmax => None,
}
}
#[must_use] pub fn resolve_pixel_value_no_percent_with_viewport(
pv: &PixelValue,
em_size: f32,
rem_size: f32,
viewport_width: f32,
viewport_height: f32,
) -> Option<f32> {
match pv.metric {
SizeMetric::Vw => Some(pv.number.get() / 100.0 * viewport_width),
SizeMetric::Vh => Some(pv.number.get() / 100.0 * viewport_height),
SizeMetric::Vmin => Some(pv.number.get() / 100.0 * viewport_width.min(viewport_height)),
SizeMetric::Vmax => Some(pv.number.get() / 100.0 * viewport_width.max(viewport_height)),
_ => resolve_pixel_value_no_percent(pv, em_size, rem_size),
}
}