use super::super::rules::{Rule, ensure_dash_none};
use super::super::values::{css_value, dash_pattern, format_value, num};
use super::paint_props;
use crate::Options;
use crate::layout::LaidOut;
use crate::resolve::{AttrMap, NodeKind, ResolvedValue, VarTable};
use std::collections::{BTreeSet, HashMap};
pub(super) fn build_frame_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
vars: &VarTable,
opts: &Options,
) {
let font_size = laid.sheet.root_font_size;
let rt = &laid.sheet.root_text;
let global = |attr: &str, var: &str| match rt.get(attr) {
Some(v) => css_value(attr, v, vars, opts),
None => live(var, vars, opts),
};
let mut root_props = vec![
("font-family".into(), global("font-family", "font-family")),
("font-size".into(), format!("{}px", num(font_size))),
("font-weight".into(), global("font-weight", "font-weight")),
("color".into(), global("color", "text-color")),
];
for attr in [
"font-style",
"text-transform",
"text-decoration",
"text-shadow",
] {
if let Some(v) = rt.get(attr) {
root_props.push((attr.to_string(), css_value(attr, v, vars, opts)));
}
}
rules.push(Rule {
class: "lini".into(),
props: root_props,
});
rules.push(Rule {
class: "lini-canvas".into(),
props: vec![("fill".into(), live("bg", vars, opts))],
});
}
pub(super) fn build_shape_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
present: &BTreeSet<&str>,
vars: &VarTable,
opts: &Options,
) {
let class_map: HashMap<&str, &AttrMap> = laid
.sheet
.class_rules
.iter()
.map(|(n, a)| (n.as_str(), a))
.collect();
let shape_paint = |class: &str| -> Vec<(String, String)> {
class_map
.get(class)
.map(|a| paint_props(a, vars, opts))
.unwrap_or_default()
};
const CLOSED: &[NodeKind] = &[
NodeKind::Block,
NodeKind::Oval,
NodeKind::Hex,
NodeKind::Slant,
NodeKind::Cyl,
NodeKind::Diamond,
NodeKind::Poly,
NodeKind::Path,
NodeKind::Sketch,
];
for kind in CLOSED {
if present.contains(kind.as_str()) {
let class = format!("lini-{}", kind.as_str());
let mut props = shape_paint(&class);
ensure_dash_none(&mut props);
rules.push(Rule { class, props });
}
}
if present.contains("line") {
let mut props = shape_paint("lini-line");
ensure_dash_none(&mut props);
rules.push(Rule {
class: "lini-line".into(),
props,
});
}
if present.contains("dim-line") {
rules.push(Rule {
class: "lini-dim-line".into(),
props: vec![
("fill".into(), "none".into()),
("stroke".into(), live("stroke-dark", vars, opts)),
("stroke-width".into(), "1".into()),
],
});
}
if present.contains("ext-line") {
rules.push(Rule {
class: "lini-ext-line".into(),
props: vec![
("fill".into(), "none".into()),
("stroke".into(), live("stroke-light", vars, opts)),
("stroke-width".into(), "1".into()),
],
});
}
if present.contains("dim-text") {
rules.push(Rule {
class: "lini-dim-text".into(),
props: vec![
(
"font-size".into(),
format!("{}px", num(crate::ledger::consts::DRAWING_LINK_FONT_SIZE)),
),
("font-weight".into(), "normal".into()),
],
});
}
if present.contains("text") {
rules.push(Rule {
class: "lini-text".into(),
props: vec![
("fill".into(), "currentColor".into()),
("stroke".into(), "none".into()),
("text-anchor".into(), "middle".into()),
],
});
}
if present.contains("icon") {
let mut props = shape_paint("lini-icon");
ensure_dash_none(&mut props);
rules.push(Rule {
class: "lini-icon".into(),
props,
});
}
}
pub(super) fn build_sequence_text_rules(rules: &mut Vec<Rule>, present: &BTreeSet<&str>) {
if present.contains("chart-title") {
rules.push(Rule {
class: "lini-chart-title".into(),
props: vec![
(
"font-size".into(),
format!("{}px", num(crate::layout::chart::metrics::TITLE_SIZE)),
),
("font-weight".into(), "600".into()),
],
});
}
if present.contains("sequence-tab") {
rules.push(Rule {
class: "lini-sequence-tab".into(),
props: vec![
("font-size".into(), "12px".into()),
("font-weight".into(), "600".into()),
],
});
}
if present.contains("sequence-guard") {
rules.push(Rule {
class: "lini-sequence-guard".into(),
props: vec![
("font-size".into(), "11px".into()),
("font-weight".into(), "normal".into()),
],
});
}
}
pub(super) fn build_gutter_rule(rules: &mut Vec<Rule>, has_gutters: bool) {
if has_gutters {
rules.push(Rule {
class: "lini-gutter".into(),
props: vec![("stroke".into(), "none".into())],
});
}
}
pub(super) fn build_template_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
present: &BTreeSet<&str>,
vars: &VarTable,
opts: &Options,
) {
for (name, attrs) in &laid.sheet.class_rules {
if let Some(tn) = name.strip_prefix("lini-")
&& NodeKind::parse(tn).is_none()
&& present.contains(tn)
{
rules.push(Rule {
class: name.clone(),
props: paint_props(attrs, vars, opts),
});
}
}
}
pub(super) fn build_link_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
vars: &VarTable,
opts: &Options,
) {
if !laid.links.is_empty() || !laid.strays.is_empty() {
let defaults = &laid.sheet.link_defaults;
let dp = paint_props(defaults, vars, opts);
let from_defaults = |p: &str| dp.iter().find(|(k, _)| k == p).map(|(_, v)| v.clone());
let mut props = vec![
("fill".into(), "none".into()),
(
"stroke".into(),
from_defaults("stroke").unwrap_or_else(|| live("stroke", vars, opts)),
),
(
"stroke-width".into(),
from_defaults("stroke-width").unwrap_or_else(|| "2".into()),
),
(
"stroke-dasharray".into(),
from_defaults("stroke-dasharray").unwrap_or_else(|| "none".into()),
),
];
for (k, v) in &dp {
if !k.starts_with("font")
&& !matches!(k.as_str(), "stroke" | "stroke-width" | "stroke-dasharray")
{
props.push((k.clone(), v.clone()));
}
}
rules.push(Rule {
class: "lini-link".into(),
props,
});
let link_width = laid
.sheet
.link_defaults
.number("stroke-width")
.unwrap_or(0.0);
let mut link_styles: BTreeSet<&str> = BTreeSet::new();
for w in &laid.links {
if let Some(ResolvedValue::Ident(s)) = w.attrs.get("stroke-style")
&& (s == "dashed" || s == "dotted")
{
link_styles.insert(s.as_str());
}
}
for style in link_styles {
rules.push(Rule {
class: format!("lini-link-{style}"),
props: vec![("stroke-dasharray".into(), dash_pattern(style, link_width))],
});
}
}
}
pub(super) fn build_link_label_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
has_link_labels: bool,
has_seq_labels: bool,
has_labels: bool,
vars: &VarTable,
opts: &Options,
) {
if has_link_labels {
let wfs = laid.sheet.link_defaults.number("font-size").unwrap_or(11.0);
rules.push(Rule {
class: "lini-link-label".into(),
props: vec![
("fill".into(), "currentColor".into()),
("stroke".into(), "none".into()),
("text-anchor".into(), "middle".into()),
("font-size".into(), format!("{}px", num(wfs))),
("font-weight".into(), live("link-font-weight", vars, opts)),
],
});
}
if has_seq_labels {
rules.push(Rule {
class: "lini-sequence-message".into(),
props: vec![
("fill".into(), "currentColor".into()),
("stroke".into(), "none".into()),
("text-anchor".into(), "middle".into()),
(
"font-size".into(),
format!("{}px", num(crate::layout::sequence::messages::LABEL_SIZE)),
),
("font-weight".into(), "normal".into()),
],
});
}
if has_labels {
rules.push(Rule {
class: "lini-cut-bg".into(),
props: vec![
("fill".into(), "white".into()),
("stroke".into(), "none".into()),
],
});
rules.push(Rule {
class: "lini-cut".into(),
props: vec![
("fill".into(), "black".into()),
("stroke".into(), "none".into()),
],
});
}
}
pub(super) fn build_marker_rules(
rules: &mut Vec<Rule>,
present: &BTreeSet<&str>,
has_markers: bool,
vars: &VarTable,
opts: &Options,
) {
if has_markers || present.contains("marker") {
rules.push(Rule {
class: "lini-marker".into(),
props: vec![
("fill".into(), live("stroke", vars, opts)),
("stroke".into(), "none".into()),
],
});
}
for variant in ["marker-dim", "marker-datum"] {
if present.contains(variant) {
rules.push(Rule {
class: format!("lini-{variant}"),
props: vec![("fill".into(), live("stroke-dark", vars, opts))],
});
}
}
if present.contains("chart-label") {
rules.push(Rule {
class: "lini-chart-label".into(),
props: vec![("pointer-events".into(), "none".into())],
});
}
}
pub(super) fn build_style_class_rules(
rules: &mut Vec<Rule>,
laid: &LaidOut,
used_styles: &BTreeSet<&str>,
has_markers: bool,
vars: &VarTable,
opts: &Options,
) {
for (name, attrs) in &laid.sheet.class_rules {
if name.starts_with("lini-") || !used_styles.contains(name.as_str()) {
continue;
}
rules.push(Rule {
class: format!("lini-style-{}", name),
props: paint_props(attrs, vars, opts),
});
if has_markers && let Some(v) = attrs.get("stroke") {
rules.push(Rule {
class: format!("lini-style-{} .lini-marker", name),
props: vec![("fill".into(), format_value(v, vars, opts))],
});
}
}
}
pub(super) fn build_open_marker_rule(rules: &mut Vec<Rule>, has_open: bool) {
if has_open {
rules.push(Rule {
class: "lini-marker.lini-marker-open".into(),
props: vec![
("fill".into(), "none".into()),
("stroke".into(), "inherit".into()),
("stroke-linecap".into(), "round".into()),
("stroke-dasharray".into(), "none".into()),
],
});
}
}
fn live(name: &str, vars: &VarTable, opts: &Options) -> String {
format_value(
&ResolvedValue::LiveVar {
name: name.to_string(),
raw: false,
},
vars,
opts,
)
}