feat: upgrade mermaid-rs-renderer to v0.2.0 with edge label support
Adds font-family sanitization to fix nested double quotes that break usvg XML parsing. Edge labels (-->|Yes|) now render correctly as interactive DrawingElements.
This commit is contained in:
@@ -33,7 +33,7 @@ tokio-tungstenite = "0.24"
|
||||
futures-util = "0.3"
|
||||
|
||||
# Mermaid diagram rendering
|
||||
mermaid-rs-renderer = { version = "0.1.2", default-features = false }
|
||||
mermaid-rs-renderer = { git = "https://github.com/1jehuang/mermaid-rs-renderer", tag = "v0.2.0", default-features = false }
|
||||
|
||||
# Logging
|
||||
tracing = "0.1"
|
||||
|
||||
@@ -1,9 +1,67 @@
|
||||
use std::panic;
|
||||
|
||||
use anyhow::Result;
|
||||
use mermaid_rs_renderer::RenderOptions;
|
||||
|
||||
pub fn render_mermaid_to_svg(mermaid_source: &str) -> Result<String> {
|
||||
let svg = mermaid_rs_renderer::render(mermaid_source)
|
||||
.map_err(|e| anyhow::anyhow!("Mermaid render failed: {}", e))?;
|
||||
Ok(svg)
|
||||
let source = mermaid_source.to_string();
|
||||
|
||||
let mut options = RenderOptions::modern();
|
||||
options.theme.font_family =
|
||||
"Inter, ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif".to_string();
|
||||
|
||||
let result = panic::catch_unwind(|| mermaid_rs_renderer::render_with_options(&source, options));
|
||||
match result {
|
||||
Ok(Ok(svg)) => Ok(sanitize_svg_font_families(&svg)),
|
||||
Ok(Err(e)) => Err(anyhow::anyhow!("Mermaid render failed: {}", e)),
|
||||
Err(_) => Err(anyhow::anyhow!(
|
||||
"Mermaid renderer panicked (unsupported syntax)"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips nested double quotes inside `font-family` XML attributes.
|
||||
///
|
||||
/// mermaid-rs-renderer v0.2.0 emits `font-family="..., "Segoe UI", ..."`
|
||||
/// which is invalid XML — usvg rejects it. This rewrites those attributes
|
||||
/// so the inner quotes are removed.
|
||||
fn sanitize_svg_font_families(svg: &str) -> String {
|
||||
let mut result = String::with_capacity(svg.len());
|
||||
let mut chars = svg.chars().peekable();
|
||||
|
||||
while let Some(ch) = chars.next() {
|
||||
result.push(ch);
|
||||
|
||||
if ch == 'f' && result.ends_with("font-family=\"") {
|
||||
let mut value = String::new();
|
||||
loop {
|
||||
match chars.next() {
|
||||
Some('"') => {
|
||||
if let Some(&next) = chars.peek() {
|
||||
if next == ' ' || next == '/' || next == '>' || next == '\n' {
|
||||
result.push_str(&value);
|
||||
result.push('"');
|
||||
break;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
result.push_str(&value);
|
||||
result.push('"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some(c) => value.push(c),
|
||||
None => {
|
||||
result.push_str(&value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -21,4 +79,23 @@ mod tests {
|
||||
let svg = render_mermaid_to_svg("sequenceDiagram\n Alice->>Bob: Hello").unwrap();
|
||||
assert!(svg.contains("<svg"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_edge_labels() {
|
||||
let svg = render_mermaid_to_svg(
|
||||
"flowchart LR\n A{Decision} -->|Yes| B[OK]\n A -->|No| C[Cancel]",
|
||||
)
|
||||
.unwrap();
|
||||
assert!(svg.contains("<svg"));
|
||||
assert!(svg.contains("Yes"));
|
||||
assert!(svg.contains("No"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sanitized_svg_is_valid_xml() {
|
||||
let svg = render_mermaid_to_svg("flowchart LR\n A-->B").unwrap();
|
||||
let mut options = usvg::Options::default();
|
||||
options.fontdb_mut().load_system_fonts();
|
||||
usvg::Tree::from_str(&svg, &options).expect("sanitized SVG should parse with usvg");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user