use core::fmt;
use base64::Engine;
use reflexo_vec2svg::{ExportFeature, SvgExporter, SvgText};
use tinymist_query::{FramePosition, LocalContext};
use tinymist_std::typst::TypstDocument;
struct PeriscopeExportFeature {}
impl ExportFeature for PeriscopeExportFeature {
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 = true;
const WITH_RESPONSIVE_JS: bool = false;
const AWARE_HTML_ENTITY: bool = false;
}
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PeriscopeArgs {
pub y_above: f32,
pub y_below: f32,
pub scale: f32,
pub invert_color: String,
}
impl Default for PeriscopeArgs {
fn default() -> Self {
Self {
y_above: 55.,
y_below: 55.,
scale: 1.5,
invert_color: "never".to_owned(),
}
}
}
#[derive(Debug, Clone)]
pub struct PeriscopeRenderer {
p: PeriscopeArgs,
}
impl Default for PeriscopeRenderer {
fn default() -> Self {
Self::new(PeriscopeArgs::default())
}
}
impl PeriscopeRenderer {
pub fn new(args: PeriscopeArgs) -> Self {
Self { p: args }
}
pub fn render_marked(
&self,
ctx: &mut LocalContext,
doc: &TypstDocument,
pos: FramePosition,
) -> Option<String> {
let (svg_payload, w, h) = self.render(ctx, doc, pos)?;
let sw = w * self.p.scale;
let sh = h * self.p.scale;
log::debug!("periscope image: {sw}x{sh}, {svg_payload}");
let base64 = base64::engine::general_purpose::STANDARD.encode(svg_payload);
Some(enlarge_image(format_args!(
""
)))
}
pub fn render(
&self,
_ctx: &mut LocalContext,
doc: &TypstDocument,
pos: FramePosition,
) -> Option<(String, f32, f32)> {
match doc {
TypstDocument::Paged(paged_doc) => {
type UsingExporter = SvgExporter<PeriscopeExportFeature>;
let mut doc = UsingExporter::svg_doc(paged_doc);
doc.module.prepare_glyphs();
let page0 = doc.pages.get(pos.page.get() - 1)?.clone();
let mut svg_text = UsingExporter::render(&doc.module, &[page0.clone()], None);
let svg_header = svg_text.get_mut(0)?;
let y_center = pos.point.y.to_pt() as f32;
let y_lo = y_center - self.p.y_above;
let y_hi = y_center + self.p.y_below;
let width = page0.size.x.0;
let height = y_hi - y_lo;
*svg_header = SvgText::Plain(header_inner(
page0.size.x.0,
y_lo,
y_hi,
self.p.scale,
self.p.invert_color == "always",
));
Some((SvgText::join(svg_text), width, height))
}
_ => None,
}
}
}
fn enlarge_image(md: fmt::Arguments) -> String {
format!("```\n```\n{md}\n```\n```")
}
fn header_inner(w: f32, y_lo: f32, y_hi: f32, scale: f32, invert_color: bool) -> String {
let h = y_hi - y_lo;
let sw = w * scale;
let sh = h * scale;
let invert_style = if invert_color {
r#"-webkit-filter: invert(0.933333) hue-rotate(180deg); filter: invert(0.933333) hue-rotate(180deg);"#
} else {
""
};
format!(
r#"<svg style="{invert_style}" class="typst-doc" width="{sw:.3}px" height="{sh:.3}px" data-width="{w:.3}" data-height="{h:.3}" viewBox="0 {y_lo:.3} {w:.3} {h:.3}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:h5="http://www.w3.org/1999/xhtml">"#,
)
}