use crate::layout::{Bbox, PlacedNode, approx_height, approx_width};
use crate::resolve::{AttrMap, MarkerKind, Markers, NodeKind, ResolvedValue};
use crate::span::Span;
use std::f64::consts::TAU;
fn node(kind: NodeKind, bbox: Bbox) -> PlacedNode {
PlacedNode {
id: None,
kind,
type_chain: Vec::new(),
applied_styles: Vec::new(),
label: None,
attrs: AttrMap::new(),
own_style: AttrMap::new(),
markers: Markers::default(),
cx: 0.0,
cy: 0.0,
bbox,
rotation: 0.0,
children: Vec::new(),
dividers: Vec::new(),
span: Span::empty(),
}
}
fn ident(s: &str) -> ResolvedValue {
ResolvedValue::Ident(s.to_string())
}
pub fn rect(cx: f64, cy: f64, w: f64, h: f64, fill: ResolvedValue, opacity: f64) -> PlacedNode {
let mut n = node(NodeKind::Block, Bbox::centered(w, h));
n.cx = cx;
n.cy = cy;
n.attrs.insert("fill", fill);
n.attrs.insert("stroke", ident("none"));
n.attrs.insert("stroke-width", ResolvedValue::Number(0.0));
if (opacity - 1.0).abs() > 1e-9 {
n.attrs.insert("opacity", ResolvedValue::Number(opacity));
}
n
}
fn bounds(points: &[(f64, f64)]) -> Bbox {
if points.is_empty() {
return Bbox::empty();
}
points.iter().fold(
Bbox {
min_x: f64::INFINITY,
min_y: f64::INFINITY,
max_x: f64::NEG_INFINITY,
max_y: f64::NEG_INFINITY,
},
|b, &(x, y)| Bbox {
min_x: b.min_x.min(x),
min_y: b.min_y.min(y),
max_x: b.max_x.max(x),
max_y: b.max_y.max(y),
},
)
}
fn filled(kind: NodeKind, cx: f64, cy: f64, w: f64, h: f64, fill: ResolvedValue) -> PlacedNode {
let mut n = node(kind, Bbox::centered(w, h));
n.cx = cx;
n.cy = cy;
n.attrs.insert("fill", fill);
n.attrs.insert("stroke", ident("none"));
n.attrs.insert("stroke-width", ResolvedValue::Number(0.0));
n
}
pub fn oval(cx: f64, cy: f64, w: f64, h: f64, fill: ResolvedValue) -> PlacedNode {
filled(NodeKind::Oval, cx, cy, w, h, fill)
}
pub fn marker(
kind: MarkerKind,
cx: f64,
cy: f64,
w: f64,
h: f64,
fill: ResolvedValue,
) -> PlacedNode {
let shape = if matches!(kind, MarkerKind::Diamond) {
NodeKind::Diamond
} else {
NodeKind::Oval
};
filled(shape, cx, cy, w, h, fill)
}
pub fn poly(points: Vec<(f64, f64)>, fill: ResolvedValue, opacity: f64) -> PlacedNode {
let bbox = bounds(&points);
let pts = points
.into_iter()
.map(|(x, y)| {
ResolvedValue::Tuple(vec![ResolvedValue::Number(x), ResolvedValue::Number(y)])
})
.collect();
let mut n = node(NodeKind::Poly, bbox);
n.attrs.insert("points", ResolvedValue::List(pts));
n.attrs.insert("fill", fill);
n.attrs.insert("stroke", ident("none"));
n.attrs.insert("stroke-width", ResolvedValue::Number(0.0));
n.attrs.insert("opacity", ResolvedValue::Number(opacity));
n
}
#[allow(clippy::too_many_arguments)]
pub fn wedge(
cx: f64,
cy: f64,
r0: f64,
r1: f64,
a_lo: f64,
a_hi: f64,
fill: ResolvedValue,
opacity: f64,
) -> PlacedNode {
let span = a_hi - a_lo;
let steps = (span.abs() / TAU * 64.0).ceil().max(2.0) as usize; let at = |r: f64, a: f64| (cx + r * a.sin(), cy - r * a.cos());
let mut pts: Vec<(f64, f64)> = (0..=steps)
.map(|k| at(r1, a_lo + span * k as f64 / steps as f64))
.collect();
if r0 <= 0.5 {
pts.push((cx, cy));
} else {
pts.extend((0..=steps).map(|k| at(r0, a_hi - span * k as f64 / steps as f64)));
}
poly(pts, fill, opacity)
}
pub fn line(points: Vec<(f64, f64)>, stroke: ResolvedValue, width: f64) -> PlacedNode {
let bbox = bounds(&points);
let pts = points
.into_iter()
.map(|(x, y)| {
ResolvedValue::Tuple(vec![ResolvedValue::Number(x), ResolvedValue::Number(y)])
})
.collect();
let mut n = node(NodeKind::Line, bbox);
n.attrs.insert("points", ResolvedValue::List(pts));
n.attrs.insert("fill", ident("none"));
n.attrs.insert("stroke", stroke);
n.attrs.insert("stroke-width", ResolvedValue::Number(width));
n
}
pub fn group(children: Vec<PlacedNode>, type_chain: Vec<String>, bbox: Bbox) -> PlacedNode {
let mut n = node(NodeKind::Block, bbox);
n.type_chain = type_chain;
n.children = children;
n
}
pub fn text(
content: &str,
cx: f64,
cy: f64,
size: f64,
color: Option<ResolvedValue>,
bold: bool,
) -> PlacedNode {
let bbox = Bbox::centered(
approx_width(content, size, 0.0),
approx_height(content, size, 0.0),
);
let mut n = node(NodeKind::Text, bbox);
n.cx = cx;
n.cy = cy;
n.label = Some(content.to_string());
set(&mut n, "font-size", ResolvedValue::Number(size));
set(
&mut n,
"font-weight",
ident(if bold { "bold" } else { "normal" }),
);
if let Some(c) = color {
set(&mut n, "color", c);
}
n
}
pub fn text_right(
content: &str,
right_x: f64,
cy: f64,
size: f64,
color: Option<ResolvedValue>,
) -> PlacedNode {
let cx = right_x - text_width(content, size) / 2.0;
text(content, cx, cy, size, color, false)
}
pub fn text_left(
content: &str,
left_x: f64,
cy: f64,
size: f64,
color: Option<ResolvedValue>,
) -> PlacedNode {
let cx = left_x + text_width(content, size) / 2.0;
text(content, cx, cy, size, color, false)
}
pub fn text_width(content: &str, size: f64) -> f64 {
approx_width(content, size, 0.0)
}
pub fn text_height(content: &str, size: f64) -> f64 {
approx_height(content, size, 0.0)
}
pub fn set_title(n: &mut PlacedNode, title: String) {
n.attrs.insert("title", ResolvedValue::String(title));
}
pub fn outline(n: &mut PlacedNode, color: ResolvedValue, width: f64) {
n.attrs.insert("stroke", color);
n.attrs.insert("stroke-width", ResolvedValue::Number(width));
}
pub fn round(n: &mut PlacedNode, radius: f64) {
if radius > 0.0 {
n.attrs.insert("radius", ResolvedValue::Number(radius));
}
}
fn set(n: &mut PlacedNode, name: &str, v: ResolvedValue) {
n.attrs.insert(name, v.clone());
n.own_style.insert(name, v);
}