Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:34:41.662Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

postprocess.rs

105 lines · 3.3 KB · rust
1//! Zed-specific post-processing of [`merman`]-produced SVGs.
2//!
3//! Each submodule is a specific pass that tweaks the SVG event iterator in a particular way.
4//!
5//! We always produce and consume [`Event`]s with a short lifetime.
6//! [`Event<'a>`] is backed internally by a [`Cow<'a, [u8]>`](std::borrow::Cow),
7//! so we don't have lifetime issues when we need to mutate the text in an
8//! [`Event`], but also don't force allocating a new [`String`] each time.
9//!
10//! Many modules contain internal structs that implement [`Iterator`] to make
11//! reasoning about lifetimes simpler, but these are private implementation
12//! details.
13
14mod accent_colors;
15mod element_fixup;
16mod inject_css;
17mod strip_foreignobject;
18pub(crate) mod util;
19
20use anyhow::{Context as _, Result};
21use quick_xml::Reader;
22use quick_xml::events::Event;
23
24use crate::MermaidTheme;
25
26pub(super) fn postprocess(svg: &str, theme: &MermaidTheme) -> Result<String> {
27    // merman 0.6 already applies the generic resvg-safe cleanup before this point.
28    // The remaining passes are Zed-specific theme and accent adjustments.
29    let svg_id = extract_svg_id(svg);
30
31    let mut reader = Reader::from_str(svg);
32    reader.config_mut().check_end_names = false;
33    let events = ReaderIter::new(reader);
34    // merman's resvg-safe pipeline already removes foreignObject elements and
35    // replaces their labels with native <text> fallback groups. This pass keeps
36    // those fallback labels, but drops any that merely duplicate a native
37    // <text> (e.g. user journey renders some labels both ways).
38    let events = strip_foreignobject::process(events, svg);
39    let events = element_fixup::process(events, theme);
40
41    let events = accent_colors::process(events, theme);
42    let events = inject_css::process(events, theme, &svg_id);
43
44    let mut writer = quick_xml::Writer::new(Vec::with_capacity(svg.len()));
45    for event in events {
46        writer.write_event(event?)?;
47    }
48    String::from_utf8(writer.into_inner()).context("SVG output is not valid UTF-8")
49}
50
51fn extract_svg_id(svg: &str) -> String {
52    let mut reader = Reader::from_str(svg);
53    reader.config_mut().check_end_names = false;
54    for event in ReaderIter::new(reader) {
55        let Ok(Event::Start(e) | Event::Empty(e)) = event else {
56            continue;
57        };
58        if e.name().as_ref() == b"svg" {
59            return e
60                .try_get_attribute("id")
61                .ok()
62                .flatten()
63                .and_then(|a| a.unescape_value().ok())
64                .map(|v| v.into_owned())
65                .unwrap_or_default();
66        }
67    }
68    String::new()
69}
70
71struct ReaderIter<'a> {
72    reader: Reader<&'a [u8]>,
73    done: bool,
74}
75
76impl<'a> ReaderIter<'a> {
77    fn new(reader: Reader<&'a [u8]>) -> Self {
78        Self {
79            reader,
80            done: false,
81        }
82    }
83}
84
85impl<'a> Iterator for ReaderIter<'a> {
86    type Item = Result<Event<'a>>;
87
88    fn next(&mut self) -> Option<Self::Item> {
89        if self.done {
90            return None;
91        }
92        match self.reader.read_event() {
93            Ok(Event::Eof) => {
94                self.done = true;
95                None
96            }
97            Ok(event) => Some(Ok(event)),
98            Err(e) => {
99                self.done = true;
100                Some(Err(e.into()))
101            }
102        }
103    }
104}
105
Served at tenant.openagents/omega Member data and write actions are omitted.