katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use crate::export_surface_line::BODY_FONT_SIZE;
use crate::export_surface_math::SurfaceMathText;
use crate::export_surface_svg::SurfaceSvgRasterizer;
use crate::export_surface_text::SurfaceTextParser;
use crate::render_runtime::{KrrMathMode, KrrRenderRuntimeAdapter};
use crate::theme::KdvThemeSnapshot;
use katana_markdown_model::KmmNode;

use super::{INLINE_MATH_MAX_WIDTH, SurfaceTextSpan, SurfaceTextStyle};

pub(super) fn append_unknown_node(
    spans: &mut Vec<SurfaceTextSpan>,
    node: &KmmNode,
    style: SurfaceTextStyle,
    theme: &KdvThemeSnapshot,
) {
    if !node.children.is_empty() {
        for child in &node.children {
            super::SurfaceInlineSpans::append_node_without_fallback(spans, child, style, theme);
        }
        return;
    }
    push(
        spans,
        SurfaceTextParser::decode_basic_entities(&node.source.raw.text),
        style,
    )
}

pub(super) fn append_inline_html(
    spans: &mut Vec<SurfaceTextSpan>,
    html: &str,
    style: SurfaceTextStyle,
) {
    push(
        spans,
        SurfaceTextParser::html_fragment_text(html),
        html_style(html, style),
    )
}

pub(super) fn append_style_node(
    spans: &mut Vec<SurfaceTextSpan>,
    node: &KmmNode,
    text: &str,
    style: SurfaceTextStyle,
    theme: &KdvThemeSnapshot,
) {
    if node.children.is_empty() {
        push(spans, text, style);
        return;
    }
    for child in &node.children {
        super::SurfaceInlineSpans::append_node_without_fallback(spans, child, style, theme);
    }
}

pub(super) fn append_link(
    spans: &mut Vec<SurfaceTextSpan>,
    text: impl Into<String>,
    target: impl Into<String>,
    style: SurfaceTextStyle,
) {
    let text = text.into();
    if !text.is_empty() {
        spans.push(SurfaceTextSpan::linked(text, target, style.link()));
    }
}

pub(super) fn append_inline_math(
    spans: &mut Vec<SurfaceTextSpan>,
    expression: &str,
    style: SurfaceTextStyle,
    theme: &KdvThemeSnapshot,
) {
    let output = KrrRenderRuntimeAdapter::render_math_tex_with_theme(
        expression,
        KrrMathMode::Inline,
        Some(theme.krr_math_theme()),
    );
    if let Some(image) = output.svg_payload().and_then(|svg| {
        SurfaceSvgRasterizer::rasterize_with_root_font_size(
            svg,
            INLINE_MATH_MAX_WIDTH,
            Some(BODY_FONT_SIZE),
        )
    }) {
        spans.push(SurfaceTextSpan::inline_image(
            "math-svg:inline",
            image,
            style,
        ));
        return;
    }
    push(spans, SurfaceMathText::render(expression), style);
}

pub(super) fn push_plain(spans: &mut Vec<SurfaceTextSpan>, text: &str, style: SurfaceTextStyle) {
    push(spans, SurfaceTextParser::decode_basic_entities(text), style)
}

pub(super) fn push(
    spans: &mut Vec<SurfaceTextSpan>,
    text: impl Into<String>,
    style: SurfaceTextStyle,
) {
    let text = text.into();
    if !text.is_empty() {
        spans.push(SurfaceTextSpan::styled(text, style));
    }
}

fn html_style(html: &str, style: SurfaceTextStyle) -> SurfaceTextStyle {
    let lower = html.to_ascii_lowercase();
    if lower.contains("<code") {
        return style.inline_code();
    }
    if lower.contains("<strong") || lower.contains("<b") {
        return style.bold();
    }
    if lower.contains("<em") || lower.contains("<i") {
        return style.italic();
    }
    if lower.contains("<u") {
        return style.underline();
    }
    if lower.contains("<mark") {
        return style.highlight();
    }
    if lower.contains("<s") || lower.contains("<del") {
        return style.strikethrough();
    }
    style
}

#[cfg(test)]
#[path = "export_surface_span_nodes_helpers_tests.rs"]
mod tests;