use super::values::format_value;
use crate::Options;
use crate::resolve::{AttrMap, ResolvedValue, VarTable};
pub const PAINT_PROPS: &[(&str, &str)] = &[
("fill", "fill"),
("stroke", "stroke"),
("stroke-width", "stroke-width"),
("opacity", "opacity"),
("color", "color"),
("font-family", "font-family"),
("font-size", "font-size"),
("font-weight", "font-weight"),
("font-style", "font-style"),
("text-transform", "text-transform"),
("text-decoration", "text-decoration"),
("text-shadow", "text-shadow"),
];
pub struct Rule {
pub class: String,
pub props: Vec<(String, String)>,
}
pub struct RuleSet {
pub rules: Vec<Rule>,
}
impl RuleSet {
pub fn emit(&self, out: &mut String) {
for rule in &self.rules {
if rule.props.is_empty() {
continue;
}
if rule.class == "lini" {
out.push_str(" .lini {");
} else {
out.push_str(" .lini .");
out.push_str(&rule.class);
out.push_str(" {");
}
for (prop, value) in &rule.props {
out.push(' ');
out.push_str(prop);
out.push_str(": ");
out.push_str(value);
out.push(';');
}
out.push_str(" }\n");
}
}
pub fn provided(&self, classes: &[String], prop: &str) -> Option<&str> {
let mut hit = None;
for rule in &self.rules {
if !classes.contains(&rule.class) {
continue;
}
if let Some((_, v)) = rule.props.iter().find(|(p, _)| p == prop) {
hit = Some(v.as_str());
}
}
hit
}
pub fn inline_paint_diff<'a>(
&self,
classes: &[String],
attrs: &AttrMap,
value_of: impl Fn(&str) -> Option<&'a ResolvedValue>,
fmt: impl Fn(&str, &ResolvedValue) -> String,
) -> Vec<(&'static str, String)> {
let mut decls = Vec::new();
for (lini, css) in PAINT_PROPS {
let Some(v) = value_of(lini) else { continue };
let formatted = fmt(lini, v);
if self.provided(classes, css) != Some(formatted.as_str()) {
decls.push((*css, formatted));
}
}
if let Some(value) = dash_value(attrs)
&& self.provided(classes, "stroke-dasharray") != Some(value.as_str())
{
decls.push(("stroke-dasharray", value));
}
decls
}
pub fn marker_fill(&self, classes: &[String]) -> Option<&str> {
let mut hit = None;
for rule in &self.rules {
let matches = rule.class == "lini-marker"
|| rule
.class
.strip_suffix(" .lini-marker")
.is_some_and(|prefix| classes.iter().any(|c| c == prefix));
if matches && let Some((_, v)) = rule.props.iter().find(|(p, _)| p == "fill") {
hit = Some(v.as_str());
}
}
hit
}
}
pub fn effective_stroke(
attrs: &AttrMap,
classes: &[String],
set: &RuleSet,
vars: &VarTable,
opts: &Options,
) -> String {
if let Some(v) = attrs.get("stroke") {
return format_value(v, vars, opts);
}
if let Some(v) = set.provided(classes, "stroke") {
return v.to_string();
}
super::values::attr_or_var(&AttrMap::default(), "stroke", "stroke", vars, opts)
}
pub(super) fn dash_value(attrs: &AttrMap) -> Option<String> {
attrs.get("stroke-style")?;
let width = attrs.number("stroke-width").unwrap_or(0.0);
let dash = super::values::dasharray_value(attrs, width);
Some(if dash.is_empty() {
"none".to_string()
} else {
dash
})
}
pub(super) fn ensure_dash_none(props: &mut Vec<(String, String)>) {
if !props.iter().any(|(p, _)| p == "stroke-dasharray") {
props.push(("stroke-dasharray".into(), "none".into()));
}
}