katana-document-viewer 0.1.4

KatanA document viewer artifact, render evaluation, and export foundation.
Documentation
use crate::export_surface_text::SurfaceTextParser;
use crate::theme::KdvThemeSnapshot;
use katana_markdown_model::{KmmNode, KmmNodeKind};

use super::export_surface_span_nodes_helpers::{
    append_inline_html, append_inline_math, append_link, append_style_node, append_unknown_node,
    push, push_plain,
};
use super::{SurfaceInlineSpans, SurfaceTextSpan, SurfaceTextStyle};

impl SurfaceInlineSpans {
    pub(crate) fn from_markdown(markdown: &str, theme: &KdvThemeSnapshot) -> Vec<SurfaceTextSpan> {
        let fragment = crate::export_semantics::EvaluatedMarkdownFragment::evaluate(
            "surface-inline.md",
            markdown,
        );
        if !fragment.has_nodes() {
            return vec![SurfaceTextSpan::plain(
                SurfaceTextParser::decode_basic_entities(markdown),
            )];
        }
        Self::from_nodes_without_fallback(fragment.nodes(), theme)
    }

    pub(crate) fn from_node(node: &KmmNode, theme: &KdvThemeSnapshot) -> Vec<SurfaceTextSpan> {
        let mut spans = Vec::new();
        Self::append_node(&mut spans, node, SurfaceTextStyle::default(), theme);
        spans
    }

    pub(crate) fn from_nodes(nodes: &[KmmNode], theme: &KdvThemeSnapshot) -> Vec<SurfaceTextSpan> {
        let mut spans = Vec::new();
        let mut raw_text = String::new();
        for node in nodes {
            Self::append_node_without_fallback(
                &mut spans,
                node,
                SurfaceTextStyle::default(),
                theme,
            );
            append_node_source_text(&mut raw_text, node);
        }
        let fragment = crate::export_semantics::EvaluatedMarkdownFragment::evaluate(
            "surface-raw-inline.md",
            &raw_text,
        );
        if spans.iter().all(SurfaceTextSpan::is_plain) && fragment.contains_inline_markdown() {
            return Self::from_markdown(&raw_text, theme);
        }
        spans
    }

    fn from_nodes_without_fallback(
        nodes: &[KmmNode],
        theme: &KdvThemeSnapshot,
    ) -> Vec<SurfaceTextSpan> {
        let mut spans = Vec::new();
        for node in nodes {
            Self::append_node_without_fallback(
                &mut spans,
                node,
                SurfaceTextStyle::default(),
                theme,
            );
        }
        spans
    }

    fn append_node(
        spans: &mut Vec<SurfaceTextSpan>,
        node: &KmmNode,
        style: SurfaceTextStyle,
        theme: &KdvThemeSnapshot,
    ) {
        Self::append_node_without_fallback(spans, node, style, theme);
    }

    pub(crate) fn append_node_without_fallback(
        spans: &mut Vec<SurfaceTextSpan>,
        node: &KmmNode,
        style: SurfaceTextStyle,
        theme: &KdvThemeSnapshot,
    ) {
        if Self::append_styled_inline_node(spans, node, style, theme) {
            return;
        }
        if Self::append_semantic_inline_node(spans, node, style, theme) {
            return;
        }
        append_unknown_node(spans, node, style, theme);
    }

    fn append_styled_inline_node(
        spans: &mut Vec<SurfaceTextSpan>,
        node: &KmmNode,
        style: SurfaceTextStyle,
        theme: &KdvThemeSnapshot,
    ) -> bool {
        match &node.kind {
            KmmNodeKind::Text(text) => {
                push_plain(spans, &text.text, style);
            }
            KmmNodeKind::Strong(span) => {
                append_style_node(spans, node, &span.text, style.bold(), theme);
            }
            KmmNodeKind::Emphasis(span) => {
                append_style_node(spans, node, &span.text, style.italic(), theme);
            }
            KmmNodeKind::Strikethrough(span) => {
                append_style_node(spans, node, &span.text, style.strikethrough(), theme);
            }
            KmmNodeKind::InlineCode(code) => {
                push(spans, &code.code, style.inline_code());
            }
            _ => return false,
        }
        true
    }

    fn append_semantic_inline_node(
        spans: &mut Vec<SurfaceTextSpan>,
        node: &KmmNode,
        style: SurfaceTextStyle,
        theme: &KdvThemeSnapshot,
    ) -> bool {
        match &node.kind {
            KmmNodeKind::InlineHtml(html) => {
                append_inline_html(spans, &html.html, style);
            }
            KmmNodeKind::Link(link) => {
                append_link(spans, link.label.as_str(), &link.destination, style);
            }
            KmmNodeKind::Image(image) => push(spans, image.alt.as_str(), style),
            KmmNodeKind::FootnoteReference(reference) => {
                append_link(
                    spans,
                    format!("[{}]", reference.label),
                    format!("#fn-{}", reference.label),
                    style,
                );
            }
            KmmNodeKind::InlineMath(math) => {
                append_inline_math(spans, &math.expression, style, theme);
            }
            KmmNodeKind::Emoji(emoji) => push(spans, emoji.value.as_str(), style),
            _ => return false,
        }
        true
    }
}

fn append_node_source_text(output: &mut String, node: &KmmNode) {
    if !node.source.raw.text.is_empty() {
        output.push_str(&node.source.raw.text);
        return;
    }
    for child in &node.children {
        append_node_source_text(output, child);
    }
}

#[cfg(test)]
#[path = "export_surface_span_nodes_variant_tests.rs"]
mod variant_tests;

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