From 8d04dd126722ca6b166e4e24a577122b88b49aec Mon Sep 17 00:00:00 2001 From: Latias94 Date: Thu, 28 May 2026 22:21:29 +0800 Subject: [PATCH] markdown: use merman svg pipeline --- Cargo.lock | 24 +- crates/mermaid_render/Cargo.toml | 2 +- crates/mermaid_render/src/mermaid_render.rs | 21 +- crates/mermaid_render/src/postprocess.rs | 49 +--- .../src/postprocess/accent_colors.rs | 44 +++- .../accent_colors/class_diagram.rs | 26 +- .../src/postprocess/accent_colors/mindmap.rs | 25 +- .../src/postprocess/fallback_fixup.rs | 223 ------------------ .../src/postprocess/foreignobject_wrap.rs | 96 -------- .../src/postprocess/strip_foreignobject.rs | 9 +- .../src/postprocess/strip_invalid_css.rs | 161 ------------- crates/mermaid_render/src/render.rs | 45 +++- 12 files changed, 161 insertions(+), 564 deletions(-) delete mode 100644 crates/mermaid_render/src/postprocess/fallback_fixup.rs delete mode 100644 crates/mermaid_render/src/postprocess/foreignobject_wrap.rs delete mode 100644 crates/mermaid_render/src/postprocess/strip_invalid_css.rs diff --git a/Cargo.lock b/Cargo.lock index 0411aa04340..f734c933449 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5453,9 +5453,9 @@ dependencies = [ [[package]] name = "dugong" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f5b0a9f36306eb29685e6e27b82df4d0bb5af64261324f7d5f7716d7c39ba1b" +checksum = "d91e3c577a3882067a324ad82c17e4298d9bfe27ea00c91e779aa11b06ed147b" dependencies = [ "dugong-graphlib", "rustc-hash 2.1.1", @@ -5465,9 +5465,9 @@ dependencies = [ [[package]] name = "dugong-graphlib" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75aca4df30a85b3ba8cead498f4e38e9de4aff630155ce47515d11dfd729c6ea" +checksum = "953d90d1fb6aaf7bbb0620817d1ab0df21f942f05b5daef002a837eea79dd89d" dependencies = [ "hashbrown 0.16.1", "rustc-hash 2.1.1", @@ -10982,9 +10982,9 @@ dependencies = [ [[package]] name = "manatee" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5ed3cc0bf5f911d242bed4b4cdf12c00186e471e9fdf57d9dd0a033bbbdc87" +checksum = "5bbf81c24c22f1431376011564efe5af26956a569af09c3569e56f245741d01d" dependencies = [ "indexmap 2.11.4", "nalgebra", @@ -11279,9 +11279,9 @@ dependencies = [ [[package]] name = "merman" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3209bcfe9c8e9787a7534f8d97f1d27f7d2fdd54d3c49d9f59bb693aedabbf95" +checksum = "cbd4316c1491c7f442c583422ca332d4bcc04e40564a6b3987de726fc13d41d0" dependencies = [ "merman-core", "merman-render", @@ -11290,9 +11290,9 @@ dependencies = [ [[package]] name = "merman-core" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fc438439ca428b449486f8eaf9ce2f8ab76cea159181d0b1b454e1192e4649" +checksum = "2daac03445212c750c25a78b56fc3e44f64f2a277bd6e156357a702804b6dba1" dependencies = [ "chrono", "euclid", @@ -11317,9 +11317,9 @@ dependencies = [ [[package]] name = "merman-render" -version = "0.4.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5698e2681196051479ae8bc5153ed7eda6490f56240fd8d92da7c3932a00735" +checksum = "870f785361c55f33f8a3b73c4f1582c6054dcf8eff1380e578f9da53f927d239" dependencies = [ "base64 0.22.1", "chrono", diff --git a/crates/mermaid_render/Cargo.toml b/crates/mermaid_render/Cargo.toml index 73a32ba81fa..5a36966577c 100644 --- a/crates/mermaid_render/Cargo.toml +++ b/crates/mermaid_render/Cargo.toml @@ -18,7 +18,7 @@ test-support = [] [dependencies] anyhow.workspace = true gpui.workspace = true -merman = { version = "0.4", features = ["render"] } +merman = { version = "0.6", features = ["render"] } quick-xml.workspace = true serde_json.workspace = true diff --git a/crates/mermaid_render/src/mermaid_render.rs b/crates/mermaid_render/src/mermaid_render.rs index 1e17d8d780b..eaf752ceff3 100644 --- a/crates/mermaid_render/src/mermaid_render.rs +++ b/crates/mermaid_render/src/mermaid_render.rs @@ -16,20 +16,21 @@ //! //! This module uses the [`merman`] crate for rendering, rather than //! `mermaid-rs`, which was used in the previous implementation of mermaid -//! rendering in Zed. Merman provides significantly more accurate rendering, and -//! seems to be somewhat faster, but by default has poor CSS, making diagrams -//! look weird without significant cleanup. This is made worse by the fact that -//! `usvg`/`resvg` doesn't support some features that [`merman`] relies on. +//! rendering in Zed. //! -//! As such, this crate is quite large. But the code is very self-contained, and -//! has few dependencies. In fact, the [`gpui`] dependency is only needed for -//! the [`Hsla`] and [`Rgba`] color types. +//! Historically, this crate also carried generic `usvg`/`resvg` cleanup for SVG +//! constructs that merman's parity output could emit, such as HTML labels in +//! `` and CSS/attribute forms that rasterizers do not handle. +//! Since merman 0.6, that generic cleanup is exposed as merman's raster-safe SVG +//! pipeline. Zed opts into that pipeline during rendering, then keeps +//! editor-specific theme and accent color rules in this crate. The [`gpui`] +//! dependency is only needed for the [`Hsla`] and [`Rgba`] color types. //! //! The [`render_to_svg`] function operates in two stages: -//! - [`render`] the mermaid text to SVG using [`merman`]. -//! - [`postprocess`] the SVG to clean incorrect output and add styling. +//! - [`render`] the mermaid text to raster-safe SVG using [`merman`]. +//! - [`postprocess`] the SVG to add Zed theme and accent styling. //! -//! The postprocessing is also split up into stages. We parse the generated SVG +//! Zed's postprocessing is split up into stages. We parse the generated SVG //! using [`quick_xml`], which produces an iterator of //! [`Event<'_>`](quick_xml::events::Event)s. This iterator is then repeatedly //! transformed, and finally collected back into an SVG string. diff --git a/crates/mermaid_render/src/postprocess.rs b/crates/mermaid_render/src/postprocess.rs index af1e61f3367..36b6087d606 100644 --- a/crates/mermaid_render/src/postprocess.rs +++ b/crates/mermaid_render/src/postprocess.rs @@ -1,4 +1,4 @@ -//! Post-processing of [`merman`]-produced SVGs for rasterization with `usvg`/`resvg`. +//! Zed-specific post-processing of [`merman`]-produced SVGs. //! //! Each submodule is a specific pass that tweaks the SVG event iterator in a particular way. //! @@ -13,11 +13,8 @@ mod accent_colors; mod element_fixup; -mod fallback_fixup; -mod foreignobject_wrap; mod inject_css; mod strip_foreignobject; -mod strip_invalid_css; pub(crate) mod util; use anyhow::{Context as _, Result}; @@ -27,27 +24,20 @@ use quick_xml::events::Event; use crate::MermaidTheme; pub(super) fn postprocess(svg: &str, theme: &MermaidTheme) -> Result { - // Pass 1: foreignObject preparation (\n fix + word wrapping) - let svg = foreignobject_wrap::process(svg)?; + // merman 0.6 already applies the generic resvg-safe cleanup before this point. + // The remaining passes are Zed-specific theme and accent adjustments. + let svg_id = extract_svg_id(svg); - // Add fallbacks alongside elements - let svg = merman::render::foreign_object_label_fallback_svg_text(&svg); - - // Extract SVG id for CSS scoping (quick scan of the first element) - let svg_id = extract_svg_id(&svg); - - // Pass 2: themed post-processing pipeline. - // Each adapter takes an iterator of events and returns an iterator of events. - // Events borrow from the `svg` string — no .into_owned() per event. - let mut reader = Reader::from_str(&svg); + let mut reader = Reader::from_str(svg); reader.config_mut().check_end_names = false; let events = ReaderIter::new(reader); + // merman's resvg-safe pipeline removes foreignObject elements. Keep this + // Zed pass because it also drops fallback overlay groups when native SVG + // text is already present, avoiding duplicate rasterized labels. let events = strip_foreignobject::process(events); - let events = fallback_fixup::process(events, theme); let events = element_fixup::process(events, theme); let events = accent_colors::process(events, theme); - let events = strip_invalid_css::process(events); let events = inject_css::process(events, theme, &svg_id); let mut writer = quick_xml::Writer::new(Vec::with_capacity(svg.len())); @@ -111,26 +101,3 @@ impl<'a> Iterator for ReaderIter<'a> { } } } - -#[cfg(test)] -mod tests { - use super::*; - - fn default_theme() -> MermaidTheme { - MermaidTheme::default() - } - - #[test] - fn strip_css_handles_style_element_with_attributes() { - let svg = r#""#; - let result = postprocess(svg, &default_theme()).unwrap(); - assert!( - !result.contains("@keyframes"), - "Unsupported @keyframes should be stripped from