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"
|
futures-util = "0.3"
|
||||||
|
|
||||||
# Mermaid diagram rendering
|
# 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
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
|||||||
@@ -1,9 +1,67 @@
|
|||||||
|
use std::panic;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use mermaid_rs_renderer::RenderOptions;
|
||||||
|
|
||||||
pub fn render_mermaid_to_svg(mermaid_source: &str) -> Result<String> {
|
pub fn render_mermaid_to_svg(mermaid_source: &str) -> Result<String> {
|
||||||
let svg = mermaid_rs_renderer::render(mermaid_source)
|
let source = mermaid_source.to_string();
|
||||||
.map_err(|e| anyhow::anyhow!("Mermaid render failed: {}", e))?;
|
|
||||||
Ok(svg)
|
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)]
|
#[cfg(test)]
|
||||||
@@ -21,4 +79,23 @@ mod tests {
|
|||||||
let svg = render_mermaid_to_svg("sequenceDiagram\n Alice->>Bob: Hello").unwrap();
|
let svg = render_mermaid_to_svg("sequenceDiagram\n Alice->>Bob: Hello").unwrap();
|
||||||
assert!(svg.contains("<svg"));
|
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