Skip to main content

merman_render/svg/pipeline/builtin/
foreign_object.rs

1use crate::Result;
2use crate::svg::foreign_object_label_fallback_svg_text;
3use std::borrow::Cow;
4
5use super::util::find_tag_end;
6use crate::svg::pipeline::{SvgPostprocessContext, SvgPostprocessor};
7
8#[derive(Debug, Clone, Copy, Default)]
9pub struct ForeignObjectFallbackPostprocessor;
10
11impl SvgPostprocessor for ForeignObjectFallbackPostprocessor {
12    fn name(&self) -> &'static str {
13        "foreign-object-fallback"
14    }
15
16    fn process<'a>(
17        &self,
18        svg: Cow<'a, str>,
19        _ctx: &SvgPostprocessContext<'_>,
20    ) -> Result<Cow<'a, str>> {
21        if !svg.contains("<foreignObject") {
22            return Ok(svg);
23        }
24        Ok(Cow::Owned(foreign_object_fallback_svg(&svg)))
25    }
26}
27
28#[derive(Debug, Clone, Copy, Default)]
29pub struct StripForeignObjectPostprocessor;
30
31impl SvgPostprocessor for StripForeignObjectPostprocessor {
32    fn name(&self) -> &'static str {
33        "strip-foreign-object"
34    }
35
36    fn process<'a>(
37        &self,
38        svg: Cow<'a, str>,
39        _ctx: &SvgPostprocessContext<'_>,
40    ) -> Result<Cow<'a, str>> {
41        if !svg.contains("<foreignObject") {
42            return Ok(svg);
43        }
44        Ok(Cow::Owned(strip_foreign_objects(&svg)))
45    }
46}
47
48pub(crate) fn foreign_object_fallback_svg(svg: &str) -> String {
49    foreign_object_label_fallback_svg_text(svg)
50}
51
52pub(crate) fn strip_foreign_objects(svg: &str) -> String {
53    let mut out = String::with_capacity(svg.len());
54    let mut cursor = 0;
55
56    while let Some(rel_start) = svg[cursor..].find("<foreignObject") {
57        let start = cursor + rel_start;
58        out.push_str(&svg[cursor..start]);
59
60        let Some(open_end) = find_tag_end(svg, start) else {
61            out.push_str(&svg[start..]);
62            return out;
63        };
64
65        if svg[start..=open_end].trim_end().ends_with("/>") {
66            cursor = open_end + 1;
67            continue;
68        }
69
70        let close_start = open_end + 1;
71        let Some(rel_close) = svg[close_start..].find("</foreignObject>") else {
72            cursor = open_end + 1;
73            continue;
74        };
75        cursor = close_start + rel_close + "</foreignObject>".len();
76    }
77
78    out.push_str(&svg[cursor..]);
79    out
80}