use crate::Options;
use crate::resolve::{AttrMap, ResolvedCall, ResolvedValue, VarKind, VarTable};
pub fn format_value(value: &ResolvedValue, vars: &VarTable, opts: &Options) -> String {
match value {
ResolvedValue::Number(n) => num(*n),
ResolvedValue::String(s) => format!("\"{}\"", s),
ResolvedValue::Hex(h) => format!("#{}", h),
ResolvedValue::Ident(s) => s.clone(),
ResolvedValue::RawCss(s) => s.clone(),
ResolvedValue::Tuple(items) => {
let parts: Vec<String> = items.iter().map(|v| format_value(v, vars, opts)).collect();
parts.join(" ")
}
ResolvedValue::List(items) => {
let parts: Vec<String> = items.iter().map(|v| format_value(v, vars, opts)).collect();
parts.join(", ")
}
ResolvedValue::Call(c) => format_call(c, vars, opts),
ResolvedValue::LiveVar { name, raw, baked } => {
let is_layout = !*raw && vars.get(name).is_some_and(|e| e.kind == VarKind::Layout);
if opts.bake_vars || is_layout {
if *raw {
format!("var(--{})", name)
} else if let Some(entry) = vars.get(name) {
format_value(&entry.value, vars, opts)
} else if let Some(b) = baked {
format_value(b, vars, opts)
} else {
format!("var(--lini-{})", name)
}
} else if *raw {
format!("var(--{})", name)
} else {
format!("var(--lini-{})", name)
}
}
}
}
fn format_call(c: &ResolvedCall, vars: &VarTable, opts: &Options) -> String {
let parts: Vec<String> = c.args.iter().map(|a| format_value(a, vars, opts)).collect();
format!("{}({})", c.name, parts.join(", "))
}
pub fn attr_or_var(
attrs: &AttrMap,
name: &str,
var_name: &str,
vars: &VarTable,
opts: &Options,
) -> String {
match attrs.get(name) {
Some(v) => format_value(v, vars, opts),
None => format_value(
&ResolvedValue::LiveVar {
name: var_name.to_string(),
raw: false,
baked: None,
},
vars,
opts,
),
}
}
pub fn attr_points(attrs: &AttrMap, name: &str) -> Option<Vec<(f64, f64)>> {
match attrs.get(name)? {
ResolvedValue::List(items) => {
let mut out = Vec::with_capacity(items.len());
for item in items {
if let ResolvedValue::Tuple(t) = item
&& t.len() == 2
{
let x = t[0].as_number()?;
let y = t[1].as_number()?;
out.push((x, y));
continue;
}
return None;
}
Some(out)
}
_ => None,
}
}
pub fn dasharray_value(attrs: &AttrMap, width: f64) -> String {
match attrs.get("stroke-style") {
Some(ResolvedValue::Ident(s)) => match s.as_str() {
"dashed" => format!("{},{}", num(width * 4.0), num(width * 4.0)),
"dotted" => format!("{},{}", num(width), num(width * 3.0)),
_ => String::new(),
},
_ => String::new(),
}
}
pub fn num(n: f64) -> String {
if n.is_finite() && n == n.trunc() && n.abs() < 1e15 {
return (n as i64).to_string();
}
let s = format!("{:.4}", n);
let trimmed = s.trim_end_matches('0').trim_end_matches('.');
if trimmed.is_empty() || trimmed == "-" {
"0".to_string()
} else {
trimmed.to_string()
}
}
pub fn escape_xml(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for ch in s.chars() {
match ch {
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'&' => out.push_str("&"),
'"' => out.push_str("""),
'\'' => out.push_str("'"),
_ => out.push(ch),
}
}
out
}
pub fn class_list(
primitive_kind: &str,
type_chain: &[String],
applied_styles: &[String],
) -> Vec<String> {
let mut classes = vec!["lini-node".to_string()];
for name in type_chain {
classes.push(format!("lini-shape-{}", name));
}
classes.push(format!("lini-shape-{}", primitive_kind));
for name in applied_styles {
classes.push(format!("lini-style-{}", name));
}
classes
}