use super::values::num;
use crate::layout::PlacedNode;
use crate::resolve::MarkerKind;
use std::fmt::Write;
#[derive(Clone, Copy)]
pub struct MarkerPaint<'a> {
pub color: &'a str,
pub inline: bool,
pub thickness: f64,
}
pub fn emit_inline_markers(
out: &mut String,
indent: &str,
n: &PlacedNode,
from: (f64, f64),
to: (f64, f64),
paint: &MarkerPaint,
) {
if n.markers.start != MarkerKind::None
&& let Some((tip, dir)) = marker_anchor(from, to, true)
{
emit_marker(out, indent, n.markers.start, tip, dir, paint);
}
if n.markers.end != MarkerKind::None
&& let Some((tip, dir)) = marker_anchor(from, to, false)
{
emit_marker(out, indent, n.markers.end, tip, dir, paint);
}
}
pub fn marker_anchor(
from: (f64, f64),
to: (f64, f64),
at_start: bool,
) -> Option<((f64, f64), (f64, f64))> {
let (anchor, neighbor) = if at_start { (from, to) } else { (to, from) };
let dx = anchor.0 - neighbor.0;
let dy = anchor.1 - neighbor.1;
let len = (dx * dx + dy * dy).sqrt();
if len < 1e-9 {
return Some((anchor, (1.0, 0.0)));
}
Some((anchor, (dx / len, dy / len)))
}
pub fn marker_size(thickness: f64) -> f64 {
5.0_f64.max(thickness * 4.0)
}
pub const STUB_INSET: f64 = 4.0;
pub const MARKER_OVERLAP: f64 = 0.5;
pub fn line_inset(kind: MarkerKind, thickness: f64) -> f64 {
match kind {
MarkerKind::None => 0.0,
MarkerKind::Dot => marker_size(thickness) * 2.0 / 3.0,
_ => STUB_INSET,
}
}
pub fn dot_center(tip: (f64, f64), direction: (f64, f64), size: f64) -> (f64, f64) {
let r = size / 3.0;
(tip.0 - direction.0 * r, tip.1 - direction.1 * r)
}
pub fn emit_marker(
out: &mut String,
indent: &str,
kind: MarkerKind,
tip: (f64, f64),
direction: (f64, f64),
paint: &MarkerPaint,
) {
let MarkerPaint {
color,
inline,
thickness,
} = *paint;
let size = marker_size(thickness);
let ux = direction.0;
let uy = direction.1;
let px = -uy;
let py = ux;
let fill = if inline {
format!(r#" style="fill: {color}""#)
} else {
String::new()
};
match kind {
MarkerKind::Arrow => {
let bx = tip.0 - ux * size;
let by = tip.1 - uy * size;
let lx = bx + px * size * 0.5;
let ly = by + py * size * 0.5;
let rx = bx - px * size * 0.5;
let ry = by - py * size * 0.5;
writeln!(
out,
r#"{}<polygon class="lini-marker lini-marker-arrow" points="{},{} {},{} {},{}"{}/>"#,
indent,
num(tip.0), num(tip.1),
num(lx), num(ly),
num(rx), num(ry),
fill,
).unwrap();
}
MarkerKind::Dot => {
let (cx, cy) = dot_center(tip, direction, size);
writeln!(
out,
r#"{}<circle class="lini-marker lini-marker-dot" cx="{}" cy="{}" r="{}"{}/>"#,
indent,
num(cx),
num(cy),
num(size / 3.0),
fill,
)
.unwrap();
}
MarkerKind::Diamond => {
let bx = tip.0 - ux * size;
let by = tip.1 - uy * size;
let mx = (tip.0 + bx) / 2.0;
let my = (tip.1 + by) / 2.0;
let lx = mx + px * size * 0.4;
let ly = my + py * size * 0.4;
let rx = mx - px * size * 0.4;
let ry = my - py * size * 0.4;
writeln!(
out,
r#"{}<polygon class="lini-marker lini-marker-diamond" points="{},{} {},{} {},{} {},{}"{}/>"#,
indent,
num(tip.0), num(tip.1),
num(lx), num(ly),
num(bx), num(by),
num(rx), num(ry),
fill,
).unwrap();
}
MarkerKind::Crow => {
let bx = tip.0 - ux * size;
let by = tip.1 - uy * size;
let lx = bx + px * size * 0.5;
let ly = by + py * size * 0.5;
let rx = bx - px * size * 0.5;
let ry = by - py * size * 0.5;
writeln!(
out,
r#"{}<path class="lini-marker lini-marker-crow" d="M {} {} L {} {} M {} {} L {} {} M {} {} L {} {}" style="fill: none; stroke: {}; stroke-width: {}; stroke-dasharray: none"/>"#,
indent,
num(tip.0), num(tip.1), num(bx), num(by),
num(tip.0), num(tip.1), num(lx), num(ly),
num(tip.0), num(tip.1), num(rx), num(ry),
color, num(thickness),
).unwrap();
}
MarkerKind::None => {}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dot_sits_tangent_to_the_edge_on_the_wire_side() {
let size = marker_size(1.0);
let r = size / 3.0;
let (cx, cy) = dot_center((100.0, 50.0), (1.0, 0.0), size);
assert!((cx - (100.0 - r)).abs() < 1e-9, "centre pulled back by r");
assert!((cy - 50.0).abs() < 1e-9);
assert!(
(cx + r - 100.0).abs() < 1e-9,
"leading edge tangent to the shape edge, not past it"
);
}
#[test]
fn line_stops_at_the_dot_back_edge_but_a_stub_for_pointed_markers() {
let size = marker_size(1.0);
assert!((line_inset(MarkerKind::Dot, 1.0) - 2.0 * size / 3.0).abs() < 1e-9);
assert_eq!(line_inset(MarkerKind::Arrow, 1.0), STUB_INSET);
assert_eq!(line_inset(MarkerKind::None, 1.0), 0.0);
}
}