use crate::ast::*;
use crate::errors::PikruError;
use crate::types::{Angle, EvalValue, Length as Inches, OffsetIn, Point};
use super::context::RenderContext;
use super::types::*;
impl From<f64> for EvalValue {
fn from(value: f64) -> Self {
EvalValue::Scalar(value)
}
}
impl From<f32> for EvalValue {
fn from(value: f32) -> Self {
EvalValue::Scalar(value as f64)
}
}
impl From<f64> for Inches {
fn from(value: f64) -> Self {
Inches::inches(value)
}
}
impl From<f32> for Inches {
fn from(value: f32) -> Self {
Inches::inches(value as f64)
}
}
fn get_nth_vertex(obj: &RenderedObject, nth: &Nth) -> PointIn {
if let Some(waypoints) = obj.waypoints() {
let len = waypoints.len();
if len == 0 {
return obj.center();
}
let index = match nth {
Nth::First(_) | Nth::Ordinal(1, _, _) => 0,
Nth::Last(_) | Nth::Previous(_) => len - 1,
Nth::Ordinal(n, _, _) => {
let idx = (*n as usize).saturating_sub(1);
idx.min(len - 1)
}
};
crate::log::debug!(
nth = ?nth,
waypoints_len = len,
index = index,
vertex_x = waypoints[index].x.raw(),
vertex_y = waypoints[index].y.raw(),
"get_nth_vertex"
);
waypoints[index]
} else {
match nth {
Nth::First(_) | Nth::Ordinal(1, _, _) => obj.start(),
Nth::Last(_) | Nth::Previous(_) => obj.end(),
Nth::Ordinal(_, _, _) => obj.center(),
}
}
}
pub fn eval_expr(ctx: &RenderContext, expr: &Expr) -> Result<Value, PikruError> {
match expr {
Expr::Number(n) => {
if !n.is_finite() {
return Err(PikruError::Generic(
"Invalid numeric literal: not finite".to_string(),
));
}
Ok(Value::Scalar(*n))
}
Expr::Variable(name) => {
if let Some(val) = ctx.variables.get(name) {
Ok(Value::from(*val))
} else {
let color = name.parse::<crate::types::Color>().unwrap();
let rgb_str = color.to_rgb_string();
if let Some(rgb) = rgb_str
.strip_prefix("rgb(")
.and_then(|s| s.strip_suffix(')'))
{
let parts: Vec<&str> = rgb.split(',').collect();
if parts.len() == 3
&& let (Ok(r), Ok(g), Ok(b)) = (
parts[0].trim().parse::<u32>(),
parts[1].trim().parse::<u32>(),
parts[2].trim().parse::<u32>(),
)
{
let color_val = (r << 16) | (g << 8) | b;
return Ok(Value::from(EvalValue::Color(color_val)));
}
}
Err(PikruError::Generic(format!("Undefined variable: {}", name)))
}
}
Expr::BuiltinVar(b) => {
let key = match b {
BuiltinVar::Fill => "fill",
BuiltinVar::Color => "color",
BuiltinVar::Thickness => "thickness",
};
ctx.variables
.get(key)
.copied()
.map(Value::from)
.ok_or_else(|| PikruError::Generic(format!("Undefined builtin: {}", key)))
}
Expr::BinaryOp(lhs, op, rhs) => {
let l = eval_expr(ctx, lhs)?;
let r = eval_expr(ctx, rhs)?;
use Value::*;
let result = match (l, r, op) {
(Len(a), Len(b), BinaryOp::Add) => Len(a + b),
(Len(a), Len(b), BinaryOp::Sub) => Len(a - b),
(Len(a), Len(b), BinaryOp::Mul) => Scalar(a.raw() * b.raw()),
(Len(a), Len(b), BinaryOp::Div) => a
.checked_div(b)
.map(|s| Scalar(s.raw()))
.ok_or_else(|| PikruError::Generic("Division by zero".to_string()))?,
(Len(a), Scalar(b), BinaryOp::Add) => Len(a + Inches::inches(b)),
(Len(a), Scalar(b), BinaryOp::Sub) => Len(a - Inches::inches(b)),
(Len(a), Scalar(b), BinaryOp::Mul) => Len(a * b),
(Len(a), Scalar(b), BinaryOp::Div) => {
if b == 0.0 {
return Err(PikruError::Generic("Division by zero".to_string()));
}
Len(a / b)
}
(Scalar(a), Len(b), BinaryOp::Add) => Len(Inches::inches(a) + b),
(Scalar(a), Len(b), BinaryOp::Sub) => Len(Inches::inches(a) - b),
(Scalar(a), Len(b), BinaryOp::Mul) => Len(crate::types::Scalar(a) * b),
(Scalar(a), Len(b), BinaryOp::Div) => {
if b.raw() == 0.0 {
return Err(PikruError::Generic("Division by zero".to_string()));
}
Scalar(a / b.raw())
}
(Scalar(a), Scalar(b), BinaryOp::Add) => Scalar(a + b),
(Scalar(a), Scalar(b), BinaryOp::Sub) => Scalar(a - b),
(Scalar(a), Scalar(b), BinaryOp::Mul) => Scalar(a * b),
(Scalar(a), Scalar(b), BinaryOp::Div) => {
if b == 0.0 {
return Err(PikruError::Generic("Division by zero".to_string()));
}
Scalar(a / b)
}
(Color(_), _, _) | (_, Color(_), _) => {
return Err(PikruError::Generic(
"Cannot perform math operations on colors".to_string(),
));
}
};
validate_value(result)
}
Expr::UnaryOp(op, e) => {
let v = eval_expr(ctx, e)?;
Ok(match (op, v) {
(UnaryOp::Neg, Value::Len(l)) => Value::Len(-l), (UnaryOp::Pos, Value::Len(l)) => Value::Len(l),
(UnaryOp::Neg, Value::Scalar(s)) => Value::Scalar(-s),
(UnaryOp::Pos, Value::Scalar(s)) => Value::Scalar(s),
(_, Value::Color(_)) => {
return Err(PikruError::Generic(
"Cannot perform unary operations on colors".to_string(),
));
}
})
}
Expr::ParenExpr(e) => eval_expr(ctx, e),
Expr::FuncCall(fc) => {
let args: Result<Vec<Value>, _> = fc.args.iter().map(|a| eval_expr(ctx, a)).collect();
let args = args?;
use Value::*;
let result = match fc.func {
Function::Abs => match args[0] {
Len(l) => Len(l.abs()), Scalar(s) => Scalar(s.abs()),
Color(_) => {
return Err(PikruError::Generic(
"Cannot take abs() of a color".to_string(),
));
}
},
Function::Cos => {
let v = match args[0] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take cos() of a color".to_string(),
));
}
};
Scalar(v.to_radians().cos())
}
Function::Sin => {
let v = match args[0] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take sin() of a color".to_string(),
));
}
};
Scalar(v.to_radians().sin())
}
Function::Int => match args[0] {
Len(l) => Len(Inches::inches(l.raw().trunc())),
Scalar(s) => Scalar(s.trunc()),
Color(_) => {
return Err(PikruError::Generic(
"Cannot take int() of a color".to_string(),
));
}
},
Function::Sqrt => match args[0] {
Len(l) if l.raw() < 0.0 => {
return Err(PikruError::Generic("sqrt of negative".to_string()));
}
Len(l) => Len(Inches::inches(l.raw().sqrt())),
Scalar(s) if s < 0.0 => {
return Err(PikruError::Generic("sqrt of negative".to_string()));
}
Scalar(s) => Scalar(s.sqrt()),
Color(_) => {
return Err(PikruError::Generic(
"Cannot take sqrt() of a color".to_string(),
));
}
},
Function::Max => {
let a = match args[0] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take max() of a color".to_string(),
));
}
};
let b = match args[1] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take max() of a color".to_string(),
));
}
};
Scalar(a.max(b))
}
Function::Min => {
let a = match args[0] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take min() of a color".to_string(),
));
}
};
let b = match args[1] {
Len(l) => l.raw(),
Scalar(s) => s,
Color(_) => {
return Err(PikruError::Generic(
"Cannot take min() of a color".to_string(),
));
}
};
Scalar(a.min(b))
}
};
validate_value(result)
}
Expr::DistCall(p1, p2) => {
let a = eval_position(ctx, p1)?;
let b = eval_position(ctx, p2)?;
let offset = b - a;
let dist = Inches::inches((offset.dx.raw().powi(2) + offset.dy.raw().powi(2)).sqrt());
Ok(Value::Len(dist))
}
Expr::ObjectProp(obj, prop_ref) => {
let r = resolve_object(ctx, obj).ok_or_else(|| {
PikruError::Generic("Unknown object in property lookup".to_string())
})?;
match prop_ref {
PropertyRef::Num(prop) => {
let val = match prop {
NumProperty::Width => r.width(),
NumProperty::Height => r.height(),
NumProperty::Radius | NumProperty::Diameter => {
r.width().min(r.height()) / 2.0
}
NumProperty::Thickness => r.style().stroke_width,
};
Ok(Value::Len(val))
}
PropertyRef::Dash(prop) => {
let val = match prop {
DashProperty::Dashed => {
r.style().dashed.unwrap_or(Inches::ZERO)
}
DashProperty::Dotted => {
r.style().dotted.unwrap_or(Inches::ZERO)
}
};
Ok(Value::Len(val))
}
PropertyRef::Color(prop) => {
let color_str = match prop {
ColorProperty::Color => &r.style().stroke,
ColorProperty::Fill => &r.style().fill,
};
let color = color_str.parse::<crate::types::Color>().unwrap();
let rgb = color.to_u32();
Ok(Value::Scalar(rgb as f64))
}
}
}
Expr::ObjectCoord(obj, coord) => {
let r = resolve_object(ctx, obj)
.ok_or_else(|| PikruError::Generic("Unknown object in coord lookup".to_string()))?;
Ok(Value::Len(match coord {
Coord::X => r.center().x,
Coord::Y => r.center().y,
}))
}
Expr::ObjectEdgeCoord(obj, edge, coord) => {
let r = resolve_object(ctx, obj).ok_or_else(|| {
PikruError::Generic("Unknown object in edge coord lookup".to_string())
})?;
let pt = get_edge_point(r, edge);
Ok(Value::Len(match coord {
Coord::X => pt.x,
Coord::Y => pt.y,
}))
}
Expr::VertexCoord(nth, obj, coord) => {
let r = resolve_object(ctx, obj).ok_or_else(|| {
PikruError::Generic("Unknown object in vertex coord lookup".to_string())
})?;
let target = get_nth_vertex(r, nth);
Ok(Value::Len(match coord {
Coord::X => target.x,
Coord::Y => target.y,
}))
}
Expr::PlaceName(name) => Err(PikruError::Generic(format!(
"Unsupported place name in expression: {}",
name
))),
}
}
pub fn eval_len(ctx: &RenderContext, expr: &Expr) -> Result<Inches, PikruError> {
match eval_expr(ctx, expr)? {
Value::Len(l) => Ok(l),
Value::Scalar(s) => Ok(Inches(s)), Value::Color(_) => Err(PikruError::Generic(
"Cannot use a color as a length".to_string(),
)),
}
}
pub fn eval_scalar(ctx: &RenderContext, expr: &Expr) -> Result<f64, PikruError> {
match eval_expr(ctx, expr)? {
Value::Scalar(s) => Ok(s),
Value::Len(l) => Ok(l.0),
Value::Color(_) => Err(PikruError::Generic(
"Cannot use a color as a scalar".to_string(),
)),
}
}
pub fn eval_rvalue(ctx: &RenderContext, rvalue: &RValue) -> Result<EvalValue, PikruError> {
match rvalue {
RValue::Expr(e) => {
crate::log::debug!("eval_rvalue: RValue::Expr({:?})", e);
let value = eval_expr(ctx, e)?;
let eval_val = EvalValue::from(value);
crate::log::debug!("eval_rvalue: converted to {:?}", eval_val);
Ok(eval_val)
}
RValue::PlaceName(name) => {
crate::log::debug!("eval_rvalue: RValue::PlaceName({})", name);
let color = name.parse::<crate::types::Color>().unwrap();
let rgb_str = color.to_rgb_string();
crate::log::debug!("eval_rvalue: parsed color {} -> {}", name, rgb_str);
if let Some(rgb) = rgb_str
.strip_prefix("rgb(")
.and_then(|s| s.strip_suffix(')'))
{
let parts: Vec<&str> = rgb.split(',').collect();
if parts.len() == 3
&& let (Ok(r), Ok(g), Ok(b)) = (
parts[0].trim().parse::<u32>(),
parts[1].trim().parse::<u32>(),
parts[2].trim().parse::<u32>(),
)
{
let color_val = (r << 16) | (g << 8) | b;
crate::log::debug!("eval_rvalue: returning Color({})", color_val);
return Ok(EvalValue::Color(color_val));
}
}
crate::log::debug!("eval_rvalue: failed to parse color, returning Scalar(0.0)");
Ok(EvalValue::Scalar(0.0))
}
}
}
pub fn eval_position(ctx: &RenderContext, pos: &Position) -> Result<PointIn, PikruError> {
match pos {
Position::Coords(x, y) => {
let px = eval_len(ctx, x)?;
let py = eval_len(ctx, y)?;
Ok(Point::new(px, py))
}
Position::Place(place) => {
let result = eval_place(ctx, place)?;
crate::log::debug!(
?place,
result_x = result.x.0,
result_y = result.y.0,
"Position::Place"
);
Ok(result)
}
Position::PlaceOffset(place, op, dx, dy) => {
let base = eval_place(ctx, place)?;
let dx_val = eval_len(ctx, dx)?;
let dy_val = eval_len(ctx, dy)?;
let offset = OffsetIn::new(dx_val, dy_val);
match op {
BinaryOp::Add => Ok(base + offset),
BinaryOp::Sub => Ok(base - offset),
_ => Ok(base),
}
}
Position::Between(factor, pos1, pos2) => {
let f = eval_scalar(ctx, factor)?;
let p1 = eval_position(ctx, pos1)?;
let p2 = eval_position(ctx, pos2)?;
let result = p1 + (p2 - p1) * f;
crate::log::debug!(
f = f,
p1_x = p1.x.0,
p1_y = p1.y.0,
p2_x = p2.x.0,
p2_y = p2.y.0,
result_x = result.x.0,
result_y = result.y.0,
"Position::Between calculation"
);
Ok(result)
}
Position::Bracket(factor, pos1, pos2) => {
let f = eval_scalar(ctx, factor)?;
let p1 = eval_position(ctx, pos1)?;
let p2 = eval_position(ctx, pos2)?;
Ok(p1 + (p2 - p1) * f)
}
Position::AboveBelow(dist, ab, base_pos) => {
let d = eval_len(ctx, dist)?;
let base = eval_position(ctx, base_pos)?;
let offset = match ab {
AboveBelow::Above => OffsetIn::new(Inches::ZERO, d),
AboveBelow::Below => OffsetIn::new(Inches::ZERO, -d),
};
Ok(base + offset)
}
Position::LeftRightOf(dist, lr, base_pos) => {
let d = eval_len(ctx, dist)?;
let base = eval_position(ctx, base_pos)?;
let offset = match lr {
LeftRight::Left => OffsetIn::new(-d, Inches::ZERO),
LeftRight::Right => OffsetIn::new(d, Inches::ZERO),
};
Ok(base + offset)
}
Position::EdgePointOf(dist, edge, base_pos) => {
let d = eval_len(ctx, dist)?;
let base = eval_position(ctx, base_pos)?;
let dir = edge.to_unit_vec();
Ok(base + dir * d)
}
Position::Heading(dist, heading, base_pos) => {
let d = eval_len(ctx, dist)?;
let base = eval_position(ctx, base_pos)?;
let angle = match heading {
HeadingDir::EdgePoint(ep) => ep.to_angle(),
HeadingDir::Expr(e) => Angle::degrees(eval_scalar(ctx, e).unwrap_or(0.0)),
};
let rad = angle.to_radians();
Ok(Point::new(
base.x + Inches(d.0 * rad.sin()),
base.y + Inches(d.0 * rad.cos()),
))
}
Position::Tuple(pos1, pos2) => {
let p1 = eval_position(ctx, pos1)?;
let p2 = eval_position(ctx, pos2)?;
Ok(Point::new(p1.x, p2.y))
}
}
}
pub fn endpoint_object_from_position(
ctx: &RenderContext,
pos: &Position,
) -> Option<EndpointObject> {
match pos {
Position::Place(place) => endpoint_object_from_place(ctx, place),
Position::PlaceOffset(place, _, _, _) => endpoint_object_from_place(ctx, place),
Position::Between(_, _, _) => None,
Position::AboveBelow(_, _, _) => None,
Position::Bracket(_, _, _) => None,
_ => None,
}
}
fn endpoint_object_from_place(ctx: &RenderContext, place: &Place) -> Option<EndpointObject> {
match place {
Place::Object(obj) => {
if let Object::Named(name) = obj
&& !name.path.is_empty()
{
crate::log::debug!(
?name,
"endpoint_object_from_place: dotted name (explicit chop works, implicit autochop disabled)"
);
return resolve_object(ctx, obj).map(EndpointObject::from_rendered_dotted);
}
resolve_object(ctx, obj).map(EndpointObject::from_rendered)
}
Place::ObjectEdge(_, _) | Place::EdgePointOf(_, _) | Place::Vertex(_, _) => None,
}
}
fn eval_place(ctx: &RenderContext, place: &Place) -> Result<PointIn, PikruError> {
match place {
Place::Object(obj) => {
if let Some(rendered) = resolve_object(ctx, obj) {
Ok(rendered.center())
} else {
if let Object::Named(name) = obj
&& let ObjectNameBase::PlaceName(n) = &name.base
&& let Some(pos) = ctx.get_named_position(n)
{
crate::log::debug!(
name = %n,
x = pos.x.raw(),
y = pos.y.raw(),
"eval_place: found named position"
);
return Ok(pos);
}
Ok(ctx.position)
}
}
Place::ObjectEdge(obj, edge) => {
if let Some(rendered) = resolve_object(ctx, obj) {
let edge_point = get_edge_point(rendered, edge);
crate::log::debug!(
?edge,
center_x = rendered.center().x.raw(),
center_y = rendered.center().y.raw(),
edge_x = edge_point.x.raw(),
edge_y = edge_point.y.raw(),
"eval_place: ObjectEdge"
);
Ok(edge_point)
} else {
Ok(ctx.position)
}
}
Place::EdgePointOf(edge, obj) => {
if let Some(rendered) = resolve_object(ctx, obj) {
Ok(get_edge_point(rendered, edge))
} else {
Ok(ctx.position)
}
}
Place::Vertex(nth, obj) => {
if let Some(rendered) = resolve_object(ctx, obj) {
Ok(get_nth_vertex(rendered, nth))
} else {
Ok(ctx.position)
}
}
}
}
#[allow(unused_variables)]
pub fn resolve_object<'a>(ctx: &'a RenderContext, obj: &Object) -> Option<&'a RenderedObject> {
match obj {
Object::Named(name) => {
let base_obj = match &name.base {
ObjectNameBase::PlaceName(n) => ctx.get_object(n),
ObjectNameBase::This => ctx.current_object.as_ref().or_else(|| ctx.last_object()),
}?;
if name.path.is_empty() {
Some(base_obj)
} else {
resolve_path_in_object(base_obj, &name.path)
}
}
Object::Nth(nth) => resolve_nth(ctx, nth),
Object::NthOf(nth, container) => {
let container_obj = resolve_object(ctx, container)?;
let children = container_obj.children()?;
resolve_nth_in_children(children, nth)
}
}
}
fn resolve_nth<'a>(ctx: &'a RenderContext, nth: &Nth) -> Option<&'a RenderedObject> {
match nth {
Nth::Last(class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
ctx.get_last_object(oc)
}
Nth::First(class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
let obj = ctx.get_nth_object(1, oc);
if let Some(_o) = obj {
crate::log::debug!(
name = ?_o.name,
class = ?_o.class(),
start_x = _o.start().x.0,
start_y = _o.start().y.0,
"resolve_object Nth::First"
);
}
obj
}
Nth::Ordinal(n, modifier, class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
match modifier {
NthModifier::Last | NthModifier::Previous => {
ctx.get_nth_last_object(*n as usize, oc)
}
NthModifier::None => {
ctx.get_nth_object(*n as usize, oc)
}
}
}
Nth::Previous(class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
if oc.is_some() {
ctx.get_last_object(oc)
} else {
ctx.object_list.last()
}
}
}
}
fn resolve_nth_in_children<'a>(children: &'a [RenderedObject], nth: &Nth) -> Option<&'a RenderedObject> {
match nth {
Nth::First(class) | Nth::Last(class) | Nth::Previous(class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
let matching: Vec<&RenderedObject> = if let Some(cn) = oc {
children.iter().filter(|c| c.class() == cn).collect()
} else {
children.iter().collect()
};
match nth {
Nth::First(_) => matching.first().copied(),
_ => matching.last().copied(), }
}
Nth::Ordinal(n, modifier, class) => {
let oc = class.as_ref().and_then(nth_class_to_class_name);
let matching: Vec<&RenderedObject> = if let Some(cn) = oc {
children.iter().filter(|c| c.class() == cn).collect()
} else {
children.iter().collect()
};
match modifier {
NthModifier::Last | NthModifier::Previous => {
let idx = matching.len().checked_sub(*n as usize)?;
matching.get(idx).copied()
}
NthModifier::None => {
matching.get((*n as usize).checked_sub(1)?).copied()
}
}
}
}
}
fn resolve_path_in_object<'a>(
obj: &'a RenderedObject,
path: &[String],
) -> Option<&'a RenderedObject> {
if path.is_empty() {
return Some(obj);
}
let (next_name, remaining) = path.split_first().unwrap();
let children = obj.children()?;
let child = children
.iter()
.find(|child| child.name.as_deref() == Some(next_name.as_str()))?;
crate::log::debug!(
parent_name = ?obj.name,
child_name = next_name,
child_center_x = child.center().x.raw(),
child_center_y = child.center().y.raw(),
child_start_x = child.start().x.raw(),
child_start_y = child.start().y.raw(),
"resolve_path_in_object: found child"
);
resolve_path_in_object(child, remaining)
}
fn nth_class_to_class_name(nc: &NthClass) -> Option<ClassName> {
match nc {
NthClass::ClassName(cn) => Some(*cn),
NthClass::Sublist => Some(ClassName::Sublist),
}
}
#[allow(clippy::let_and_return)] fn get_edge_point(obj: &RenderedObject, edge: &EdgePoint) -> PointIn {
use crate::ast::Direction;
use crate::render::shapes::ShapeEnum;
let is_closed = obj.style().close_path;
match (&obj.shape, edge) {
(ShapeEnum::Line(line), EdgePoint::Start) => {
return line.waypoints.first().copied().unwrap_or(obj.center());
}
(ShapeEnum::Line(line), EdgePoint::End) if !is_closed => {
return line.waypoints.last().copied().unwrap_or(obj.center());
}
(ShapeEnum::Arc(arc), EdgePoint::Start) => {
return arc.start;
}
(ShapeEnum::Arc(arc), EdgePoint::End) => {
return arc.end;
}
(ShapeEnum::Spline(spline), EdgePoint::Start) => {
return spline.waypoints.first().copied().unwrap_or(obj.center());
}
(ShapeEnum::Spline(spline), EdgePoint::End) if !is_closed => {
return spline.waypoints.last().copied().unwrap_or(obj.center());
}
_ => {}
}
let resolved_edge = match edge {
EdgePoint::Start => {
match obj.direction {
Direction::Right => EdgePoint::West,
Direction::Down => EdgePoint::North,
Direction::Left => EdgePoint::East,
Direction::Up => EdgePoint::South,
}
}
EdgePoint::End => {
match obj.direction {
Direction::Right => EdgePoint::East,
Direction::Down => EdgePoint::South,
Direction::Left => EdgePoint::West,
Direction::Up => EdgePoint::North,
}
}
other => *other,
};
let result = match resolved_edge {
EdgePoint::Center | EdgePoint::C => obj.center(),
_ => obj.edge_point(resolved_edge.to_unit_vec()),
};
crate::log::debug!(
?edge,
?resolved_edge,
result_x = result.x.raw(),
result_y = result.y.raw(),
"get_edge_point"
);
result
}
pub fn eval_color(ctx: &RenderContext, rvalue: &RValue) -> String {
match rvalue {
RValue::PlaceName(name) => name.parse::<crate::types::Color>().unwrap().to_string(),
RValue::Expr(expr) => match expr {
Expr::Variable(name) => {
if let Some(val) = ctx.variables.get(name) {
match val {
EvalValue::Color(c) => format!("#{:06x}", c),
EvalValue::Scalar(s) => format!("#{:06x}", *s as u32),
EvalValue::Length(l) => format!("#{:06x}", l.raw() as u32),
}
} else {
name.parse::<crate::types::Color>().unwrap().to_string()
}
}
Expr::Number(n) => {
format!("#{:06x}", *n as u32)
}
_ => "black".to_string(),
},
}
}
pub fn get_length(ctx: &RenderContext, name: &str, default: f64) -> f64 {
ctx.variables
.get(name)
.map(|v| match v {
EvalValue::Length(l) => l.raw(),
EvalValue::Scalar(s) => *s,
EvalValue::Color(_) => default,
})
.unwrap_or(default)
}
pub fn get_scalar(ctx: &RenderContext, name: &str, default: f64) -> f64 {
ctx.variables
.get(name)
.map(|v| v.as_scalar())
.unwrap_or(default)
}
fn validate_value(v: Value) -> Result<Value, PikruError> {
match v {
Value::Len(l) if !l.is_finite() => Err(PikruError::Generic(
"Arithmetic overflow (result is infinite or NaN)".to_string(),
)),
Value::Scalar(s) if !s.is_finite() => Err(PikruError::Generic(
"Arithmetic overflow (result is infinite or NaN)".to_string(),
)),
_ => Ok(v),
}
}