use dagre_dgl_rs::graph::Graph;
use std::cell::RefCell;
pub trait Measurer {
fn measure(&self, text: &str, font_size: f64, bold: bool) -> (f64, f64);
}
pub trait Layouter {
fn layout(&self, graph: &mut Graph);
}
struct AbGlyphMeasurer;
impl Measurer for AbGlyphMeasurer {
fn measure(&self, text: &str, font_size: f64, bold: bool) -> (f64, f64) {
let (w, h) = crate::text_browser_metrics::measure_browser(text, font_size);
if bold {
(w * 1.082, h)
} else {
(w, h)
}
}
}
struct DagreDglRsLayouter;
impl Layouter for DagreDglRsLayouter {
fn layout(&self, graph: &mut Graph) {
dagre_dgl_rs::layout::layout(graph);
}
}
thread_local! {
static MEASURER: RefCell<Box<dyn Measurer>> =
RefCell::new(Box::new(AbGlyphMeasurer));
static LAYOUTER: RefCell<Box<dyn Layouter>> =
RefCell::new(Box::new(DagreDglRsLayouter));
}
pub fn measure(text: &str, font_size: f64) -> (f64, f64) {
MEASURER.with(|m| m.borrow().measure(text, font_size, false))
}
#[allow(dead_code)]
pub fn measure_bold(text: &str, font_size: f64) -> (f64, f64) {
MEASURER.with(|m| m.borrow().measure(text, font_size, true))
}
pub fn layout(graph: &mut Graph) {
LAYOUTER.with(|l| l.borrow().layout(graph));
}
#[cfg(feature = "fidelity")]
#[allow(dead_code)]
pub fn set_measurer(m: Box<dyn Measurer>) {
MEASURER.with(|s| *s.borrow_mut() = m);
}
#[cfg(feature = "fidelity")]
#[allow(dead_code)]
pub fn set_layouter(l: Box<dyn Layouter>) {
LAYOUTER.with(|s| *s.borrow_mut() = l);
}
#[cfg(feature = "fidelity")]
#[allow(dead_code)]
pub mod fidelity {
use super::{Layouter, Measurer};
use dagre_dgl_rs::graph::{Edge, Graph, Point};
use std::cell::RefCell;
use std::io::{BufRead, BufReader, Write};
use std::process::{Child, ChildStdin, Stdio};
struct Oracle {
_child: Child,
stdin: ChildStdin,
stdout: BufReader<std::process::ChildStdout>,
}
impl Oracle {
fn spawn(script_path: &str) -> Self {
let mut child = std::process::Command::new("node")
.arg(script_path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap_or_else(|e| panic!("failed to spawn node {script_path}: {e}"));
let stdin = child.stdin.take().unwrap();
let stdout = BufReader::new(child.stdout.take().unwrap());
Oracle {
_child: child,
stdin,
stdout,
}
}
fn call(&mut self, request: &str) -> String {
writeln!(self.stdin, "{request}").expect("oracle stdin write");
let mut line = String::new();
self.stdout
.read_line(&mut line)
.expect("oracle stdout read");
line.trim_end().to_owned()
}
}
pub struct BrowserMeasurer {
oracle: RefCell<Oracle>,
}
impl BrowserMeasurer {
pub fn spawn(script_path: &str) -> Self {
BrowserMeasurer {
oracle: RefCell::new(Oracle::spawn(script_path)),
}
}
}
impl Measurer for BrowserMeasurer {
fn measure(&self, text: &str, font_size: f64, bold: bool) -> (f64, f64) {
let req = format!(
r#"{{"text":{},"fontSize":{},"bold":{}}}"#,
serde_json::to_string(text).unwrap(),
font_size,
bold,
);
let resp = self.oracle.borrow_mut().call(&req);
let v: serde_json::Value = serde_json::from_str(&resp)
.unwrap_or_else(|e| panic!("measure oracle bad JSON: {e}\nresp={resp}"));
(
v["width"].as_f64().unwrap_or(0.0),
v["height"].as_f64().unwrap_or(0.0),
)
}
}
pub struct DagreJsLayouter {
oracle: RefCell<Oracle>,
}
impl DagreJsLayouter {
pub fn spawn(script_path: &str) -> Self {
DagreJsLayouter {
oracle: RefCell::new(Oracle::spawn(script_path)),
}
}
}
impl Layouter for DagreJsLayouter {
fn layout(&self, graph: &mut Graph) {
let req = graph_to_json(graph);
let resp = self.oracle.borrow_mut().call(&req);
let v: serde_json::Value = serde_json::from_str(&resp)
.unwrap_or_else(|e| panic!("dagre oracle bad JSON: {e}\nresp={resp}"));
apply_layout_result(graph, &v);
}
}
fn graph_to_json(g: &Graph) -> String {
use serde_json::{json, Value};
let gl = g.graph();
let graph_label = json!({
"rankdir": gl.rankdir.as_deref().unwrap_or("LR"),
"nodesep": gl.nodesep.unwrap_or(50.0),
"ranksep": gl.ranksep.unwrap_or(50.0),
"marginx": gl.marginx.unwrap_or(8.0),
"marginy": gl.marginy.unwrap_or(8.0),
});
let nodes: Vec<Value> = g
.nodes()
.iter()
.map(|id| {
let n = g.node(id);
let mut obj = json!({
"id": id,
"width": n.width,
"height": n.height,
});
if let Some(p) = g.parent(id) {
obj["parent"] = Value::String(p.to_owned());
}
if let Some(it) = n.intersect_type {
obj["intersect_type"] = Value::String(it.to_string());
}
obj
})
.collect();
let edges: Vec<Value> = g
.edges()
.iter()
.map(|e| {
let mut obj = json!({"v": e.v, "w": e.w});
if let Some(name) = &e.name {
obj["name"] = Value::String(name.clone());
}
let lbl_opt = if let Some(name) = &e.name {
g.edge(&Edge::named(&e.v, &e.w, name))
} else {
g.edge(&Edge::new(&e.v, &e.w))
};
if let Some(lbl) = lbl_opt {
obj["width"] = json!(lbl.width.unwrap_or(0.0));
obj["height"] = json!(lbl.height.unwrap_or(0.0));
obj["minlen"] = json!(lbl.minlen.unwrap_or(1));
obj["weight"] = json!(lbl.weight.unwrap_or(1.0));
if let Some(lp) = &lbl.labelpos {
obj["labelpos"] = Value::String(lp.clone());
}
if let Some(lo) = lbl.labeloffset {
obj["labeloffset"] = json!(lo);
}
}
obj
})
.collect();
serde_json::to_string(&json!({
"graph": graph_label,
"nodes": nodes,
"edges": edges,
}))
.unwrap()
}
fn apply_layout_result(g: &mut Graph, v: &serde_json::Value) {
if let Some(nodes) = v["nodes"].as_array() {
for node in nodes {
let id = node["id"].as_str().unwrap_or_default();
if g.has_node(id) {
let n = g.node_mut(id);
n.x = node["x"].as_f64();
n.y = node["y"].as_f64();
if let Some(w) = node["width"].as_f64() {
n.width = w;
}
if let Some(h) = node["height"].as_f64() {
n.height = h;
}
}
}
}
if let Some(edges) = v["edges"].as_array() {
for edge in edges {
let v_id = edge["v"].as_str().unwrap_or_default();
let w_id = edge["w"].as_str().unwrap_or_default();
let e = match edge["name"].as_str() {
Some(name) => Edge::named(v_id, w_id, name),
None => Edge::new(v_id, w_id),
};
if let Some(lbl) = g.edge_mut(&e) {
lbl.x = edge["x"].as_f64();
lbl.y = edge["y"].as_f64();
if let Some(pts) = edge["points"].as_array() {
lbl.points = Some(
pts.iter()
.map(|p| Point {
x: p["x"].as_f64().unwrap_or(0.0),
y: p["y"].as_f64().unwrap_or(0.0),
})
.collect(),
);
}
}
}
}
if let (Some(gw), Some(gh)) = (v["graph"]["width"].as_f64(), v["graph"]["height"].as_f64())
{
g.graph_mut().width = Some(gw);
g.graph_mut().height = Some(gh);
}
}
}