#![allow(dead_code)]
use reflexo::hash::Fingerprint;
use reflexo::vector::ir::{self, Module, Page, TransformedRef, VecItem};
use reflexo::vector::{incr::IncrDocClient, vm::RenderVm};
use reflexo_vec2canvas::BBoxAt;
use reflexo_vec2svg::ir::LabelledRef;
use reflexo_vec2svg::{SvgExporter, SvgTask, SvgText};
use web_sys::{wasm_bindgen::JsCast, Element, SvgGraphicsElement};
use crate::{dom::*, factory::XmlFactory, DomContext};
pub struct IncrementalSvgExportFeature;
impl reflexo_vec2svg::ExportFeature for IncrementalSvgExportFeature {
const ENABLE_INLINED_SVG: bool = false;
const ENABLE_TRACING: bool = false;
const SHOULD_ATTACH_DEBUG_INFO: bool = false;
const SHOULD_RENDER_TEXT_ELEMENT: bool = false;
const USE_STABLE_GLYPH_ID: bool = true;
const SHOULD_RASTERIZE_TEXT: bool = false;
const WITH_BUILTIN_CSS: bool = false;
const WITH_RESPONSIVE_JS: bool = false;
const AWARE_HTML_ENTITY: bool = true;
}
type Exporter = SvgExporter<IncrementalSvgExportFeature>;
pub type Vec2SvgPass = SvgTask<'static, IncrementalSvgExportFeature>;
#[derive(Default)]
pub struct SvgBackend {
vec2svg: Vec2SvgPass,
pub glyph_window: usize,
factory: XmlFactory,
}
impl SvgBackend {
pub fn reset(&mut self) {
self.glyph_window = 0;
}
fn create_element(&self, html: &str) -> Element {
self.factory.create_element(html)
}
pub(crate) fn populate_glyphs(&mut self, ctx: &mut IncrDocClient) -> Option<String> {
if ctx.glyphs.len() <= self.glyph_window {
return None;
}
let mut svg = Vec::<SvgText>::new();
svg.push(r#"<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml"><defs class="glyph">"#.into());
let glyphs = ctx.glyphs.iter();
let new_glyphs = glyphs.skip(self.glyph_window);
let glyph_defs = self.vec2svg.render_glyphs(new_glyphs.map(|(x, y)| (*x, y)));
svg.extend(glyph_defs);
svg.push("</defs></svg>".into());
self.glyph_window = ctx.glyphs.len();
Some(SvgText::join(svg))
}
pub(crate) fn render_page(
&mut self,
module: &Module,
page: &Page,
parent: &Element,
) -> Element {
let mut render_task = self.vec2svg.get_render_context(module);
let mut g = vec!["<g>".into()];
g.push(SvgText::Content(render_task.render_item(&page.content)));
g.push(r#"<defs class="clip-path">"#.into());
let patterns = self.vec2svg.render_patterns(module);
let gradients = std::mem::take(&mut self.vec2svg.gradients);
let gradients = gradients.iter().filter_map(|id| match module.get_item(id) {
Some(VecItem::Gradient(g)) => Some((id, g.as_ref())),
_ => {
panic!("Invalid gradient reference: {}", id.as_svg_id("g"));
#[allow(unreachable_code)]
None
}
});
Exporter::gradients(gradients, &mut g);
Exporter::patterns(patterns.into_iter(), &mut g);
g.push("</defs>".into());
Exporter::style_defs(std::mem::take(&mut self.vec2svg.style_defs), &mut g);
g.push(r#"</g>"#.into());
parent.set_inner_html(&SvgText::join(g));
parent.first_element_child().unwrap()
}
}
impl TypstPageElem {
pub fn attach_html(
ctx: &mut DomContext<'_, '_>,
g: SvgGraphicsElement,
data: Fingerprint,
) -> TypstElem {
let item = ctx.get_item(&data).unwrap();
let stub = ctx.create_stub();
let extra = match item {
VecItem::Group(gr) => {
let mut ch = g.first_element_child();
let mut children = vec![];
for (p, fg) in gr.0.iter() {
#[cfg(feature = "debug_attach")]
web_sys::console::log_3(
&format!(
"attach {a:?} -> {b:?} {c:?}",
a = data.as_svg_id("g"),
b = fg.as_svg_id("g"),
c = p
)
.into(),
ch.as_ref()
.map(|e| e.dyn_ref().unwrap())
.unwrap_or(&JsValue::UNDEFINED),
&g,
);
let Some(should_ch) = ch else {
web_sys::console::log_2(&"Invalid group reference".into(), &g);
continue;
};
let child = Self::attach_html(
ctx,
should_ch
.first_element_child()
.ok_or_else(|| {
web_sys::console::log_2(
&should_ch,
&format!("Invalid group translate: {item:?}").into(),
);
panic!("Invalid group translate: {}", fg.as_svg_id("g"));
})
.unwrap()
.dyn_into()
.unwrap(),
*fg,
);
children.push((*p, child));
ch = should_ch.next_element_sibling();
}
TypstDomExtra::Group(GroupElem { children })
}
VecItem::Labelled(LabelledRef(label, fg)) => {
let ch = g
.last_element_child()
.ok_or_else(|| {
web_sys::console::log_2(
&g,
&format!("Invalid item reference: {item:?}").into(),
);
panic!("Invalid item reference: {}", fg.as_svg_id("g"));
})
.unwrap();
#[cfg(feature = "debug_attach")]
web_sys::console::log_3(
&format!(
"attach {a:?} -> {b:?} {c:?}",
a = data.as_svg_id("g"),
b = fg.as_svg_id("g"),
c = label
)
.into(),
&ch,
&g,
);
let child = Self::attach_html(
ctx,
ch.first_element_child()
.ok_or_else(|| {
web_sys::console::log_2(
&g,
&format!("Invalid item translate: {item:?}").into(),
);
panic!("Invalid item translate: {}", fg.as_svg_id("g"));
})
.unwrap()
.dyn_into()
.unwrap(),
*fg,
);
TypstDomExtra::Label(LabelElem {
label: label.clone(),
child: Box::new(child),
})
}
VecItem::Item(TransformedRef(trans, fg)) => {
let ch = g
.last_element_child()
.ok_or_else(|| {
web_sys::console::log_2(
&g,
&format!("Invalid item reference: {item:?}").into(),
);
panic!("Invalid item reference: {}", fg.as_svg_id("g"));
})
.unwrap();
#[cfg(feature = "debug_attach")]
web_sys::console::log_3(
&format!(
"attach {a:?} -> {b:?} {c:?}",
a = data.as_svg_id("g"),
b = fg.as_svg_id("g"),
c = trans
)
.into(),
&ch,
&g,
);
let child = Self::attach_html(
ctx,
ch.first_element_child()
.ok_or_else(|| {
web_sys::console::log_2(
&g,
&format!("Invalid item translate: {item:?}").into(),
);
panic!("Invalid item translate: {}", fg.as_svg_id("g"));
})
.unwrap()
.dyn_into()
.unwrap(),
*fg,
);
TypstDomExtra::Item(TransformElem {
trans: trans.clone(),
child: Box::new(child),
})
}
VecItem::Image(img) => TypstDomExtra::Image(ImageElem { size: img.size }),
VecItem::Text(text) => TypstDomExtra::Text(TextElem {
meta: text.clone(),
upem: ctx.get_font(&text.shape.font).unwrap().units_per_em,
}),
VecItem::ContentHint(_) => TypstDomExtra::ContentHint(ContentHintElem { hint: ' ' }),
VecItem::Link(_) => TypstDomExtra::Link(LinkElem {}),
VecItem::Path(_) => TypstDomExtra::Path(PathElem {}),
VecItem::SizedRawHtml(_) => TypstDomExtra::RawHtml(HtmlElem {}),
VecItem::None
| VecItem::ColorTransform(_)
| VecItem::Color32(_)
| VecItem::Gradient(_)
| VecItem::Pattern(_)
| VecItem::Html(_) => {
todo!()
}
};
TypstElem {
is_svg_visible: true,
is_canvas_painted: false,
stub,
g,
f: data,
extra,
canvas: None,
}
}
pub fn repaint_svg(
&mut self,
_ctx: &mut DomContext<'_, '_>,
ts: tiny_skia::Transform,
viewport: ir::Rect,
) {
self.g.repaint_svg(ts, viewport);
}
}
pub static FETCH_BBOX_TIMES: std::sync::atomic::AtomicUsize =
std::sync::atomic::AtomicUsize::new(0);
static BBOX_SANITIZER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
impl TypstElem {
fn repaint_svg(
&mut self,
ts: tiny_skia::Transform,
mut viewport: ir::Rect,
) -> Option<ir::Rect> {
use TypstDomExtra::*;
if matches!(self.extra, ContentHint(_)) {
return None; }
let bbox = self.canvas.as_ref().unwrap().bbox_at(ts);
let should_visible = bbox
.map(|new_rect| new_rect.intersect(&viewport).is_intersected())
.unwrap_or(true);
if should_visible != self.is_svg_visible {
let (x, y) = (&self.stub, &self.g);
if should_visible {
x.replace_with_with_node_1(y).unwrap();
} else {
y.replace_with_with_node_1(x).unwrap();
};
self.is_svg_visible = should_visible;
}
if !should_visible {
return None;
}
match &mut self.extra {
Group(g) => {
for (p, child) in g.children.iter_mut() {
let ts = ts.pre_translate(p.x.0, p.y.0);
if let Some(updated) = child.repaint_svg(ts, viewport) {
viewport = updated;
}
}
Some(viewport)
}
Item(g) => {
let trans = g.trans.clone();
let trans: ir::Transform = trans.into();
let ts = ts.pre_concat(trans.into());
g.child.repaint_svg(ts, viewport)
}
_ => bbox.map(|bbox| viewport.union(&bbox)),
}
}
}