Skip to repository content105 lines · 3.3 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T03:34:41.662Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
postprocess.rs
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