import 'package:flutter/services.dart' show rootBundle; /// Builds a single, self-contained HTML file from a deck's Marp Markdown. /// /// The output embeds (inlines) `marked` for Markdown, `highlight.js` for code, /// `MathJax` (tex-svg, so no external font files are needed) for math, and /// `mermaid` for diagrams — so the resulting `.html` renders fully offline. /// /// Note: this is a Marp-*flavoured* deck rendered with `marked`, not Marp Core, /// so theme fidelity differs from the in-app preview / PDF / PPTX. The strength /// here is a portable, dependency-free presentation that opens in any browser. class MarpHtmlService { /// Reads a bundled asset (defaults to the Flutter asset bundle). Injectable so /// the builder can be unit-tested against the on-disk asset files. final Future Function(String asset) loadAsset; MarpHtmlService({Future Function(String asset)? loadAsset}) : loadAsset = loadAsset ?? rootBundle.loadString; static const _assetDir = 'assets/web_export'; Future build(String deckMarkdown) async { final marked = await loadAsset('$_assetDir/marked.min.js'); final hljs = await loadAsset('$_assetDir/highlight.min.js'); final hljsCss = await loadAsset('$_assetDir/highlight.css'); final mathjax = await loadAsset('$_assetDir/tex-svg.js'); final mermaid = await loadAsset('$_assetDir/mermaid.min.js'); final sections = StringBuffer(); for (final slide in marpSlides(deckMarkdown)) { sections ..write('
'); } String inline(String code) => ''; return '\n' '' '' 'OciDeck export' '' '' '${inline(marked)}' '${inline(hljs)}' '${inline(mathjax)}' '${inline(mermaid)}' '' '$sections' '${inline(_renderScript)}' ''; } /// Split Marp Markdown into per-slide Markdown chunks: drop the leading YAML /// front-matter, then break on lines that contain only `---`. static List marpSlides(String markdown) { var text = markdown.replaceAll('\r\n', '\n'); // Strip a leading YAML front-matter block: ---\n ... \n---\n if (text.startsWith('---\n')) { final close = text.indexOf('\n---', 4); if (close != -1) { final nl = text.indexOf('\n', close + 1); text = nl == -1 ? '' : text.substring(nl + 1); } } final slides = []; final buf = StringBuffer(); for (final line in text.split('\n')) { if (line.trim() == '---') { slides.add(buf.toString().trim()); buf.clear(); } else { buf.writeln(line); } } slides.add(buf.toString().trim()); return slides.where((s) => s.isNotEmpty).toList(); } /// Neutralise any ` element. Safe for both JS (string contexts) and /// the embedded Markdown payloads. static String _guard(String s) => s .replaceAll('