surf_parse/lib.rs
1//! `surf-parse` — parser for the SurfDoc format.
2//!
3//! SurfDoc is a typed document format with block directives, embedded data,
4//! and presentation hints. Backward-compatible with Markdown. This crate
5//! provides the foundational parser that turns `.surf` source text into a
6//! structured `SurfDoc` tree.
7//!
8//! # Quick start
9//!
10//! ```
11//! let result = surf_parse::parse("# Hello\n\n::callout[type=info]\nHi!\n::\n");
12//! assert!(result.diagnostics.is_empty());
13//! assert_eq!(result.doc.blocks.len(), 2);
14//! ```
15
16pub mod attrs;
17pub mod blocks;
18pub mod builder;
19pub(crate) mod chart;
20pub mod citation;
21pub(crate) mod diagram;
22pub mod error;
23pub mod icons;
24pub mod inline;
25pub mod lint;
26pub mod parse;
27pub mod render_html;
28pub mod render_latex;
29pub mod render_md;
30pub mod resolve;
31pub mod slots;
32#[cfg(feature = "pdf")]
33pub mod render_pdf;
34pub mod render_typst;
35#[cfg(feature = "slides")]
36pub mod render_slides;
37#[cfg(feature = "terminal")]
38pub mod render_term;
39#[cfg(feature = "native")]
40pub mod render_native;
41#[cfg(feature = "uniffi")]
42pub mod ffi;
43// UniFFI scaffolding must be generated at the crate root (it defines the
44// crate-wide `UniFfiTag` used by every exported type and function).
45#[cfg(feature = "uniffi")]
46uniffi::setup_scaffolding!();
47#[cfg(feature = "wasm")]
48pub mod wasm;
49pub mod template;
50pub mod types;
51pub mod validate;
52
53/// Unified CSS for app chrome and SurfDoc content rendering.
54///
55/// Contains theme variables, reset, navigation, buttons, cards, forms,
56/// and all 29 SurfDoc block type styles. Dark-first with light mode support
57/// via `data-theme="light"` or `prefers-color-scheme`.
58///
59/// Override `:root` variables (`--accent`, `--background`, `--surface`,
60/// `--font-heading`, `--font-body`) for site-level theming.
61pub const SURFDOC_CSS: &str = include_str!("../assets/surfdoc.css");
62
63pub use blocks::{parse_schema_field_type, parse_schema_constraint};
64pub use builder::SurfDocBuilder;
65pub use citation::{
66 active_style, bibliography_heading, build_context, format_in_text, format_reference,
67 parse_author, parse_authors, reference_list, reference_list_keyed, Author, CiteContext,
68 CiteItem, CiteRef, Reference, RefType,
69};
70pub use error::*;
71pub use lint::{
72 AppliedFix, CheckReport, FixOutcome, LintConfig, LintRule, SkippedFix, apply_fixes,
73 apply_fixes_once, check, check_with,
74};
75pub use parse::parse;
76pub use template::TemplateContext;
77pub use types::*;
78
79pub use render_html::{
80 PageConfig, SiteConfig, PageEntry, extract_site, humanize_route, render_site_page,
81 render_site_single_file,
82 accent_ink_color, contrast_ratio, to_shell_page, HeadIcon, HeadScript,
83};
84pub use slots::{resolve_slot_markers, IMG_SLOT_PLACEHOLDER_URI};
85#[cfg(feature = "slides")]
86pub use render_slides::{DeckConfig, SlideEntry, extract_deck, render_deck_html};
87
88#[cfg(feature = "pdf")]
89pub use render_pdf::{collect_image_srcs, PdfConfig, PdfError};
90
91impl SurfDoc {
92 /// Render this document as standard CommonMark markdown (no `::` markers).
93 pub fn to_markdown(&self) -> String {
94 render_md::to_markdown(self)
95 }
96
97 /// Render this document as an HTML fragment with `surfdoc-*` CSS classes.
98 pub fn to_html(&self) -> String {
99 render_html::to_html(self)
100 }
101
102 /// Render this document's blocks as bare HTML without page chrome.
103 ///
104 /// Unlike [`to_html()`], this does not scan for `::site`/`::style` overrides,
105 /// extract nav blocks, or apply auto-section wrapping. Each block is rendered
106 /// individually and joined with newlines.
107 ///
108 /// Use this for streaming, chat rendering, or embedding individual blocks
109 /// where the caller controls the CSS context.
110 pub fn to_html_fragment(&self) -> String {
111 render_html::to_html_fragment(&self.blocks)
112 }
113
114 /// Render this document's blocks as bare HTML with template variable interpolation.
115 pub fn to_html_fragment_with_context(&self, ctx: &TemplateContext) -> String {
116 let html = self.to_html_fragment();
117 ctx.resolve(&html)
118 }
119
120 /// Render this document as a complete HTML page with SurfDoc discovery metadata.
121 pub fn to_html_page(&self, config: &PageConfig) -> String {
122 render_html::to_html_page(self, config)
123 }
124
125 /// Render this document as an HTML fragment with template variable interpolation.
126 pub fn to_html_with_context(&self, ctx: &TemplateContext) -> String {
127 let html = self.to_html();
128 ctx.resolve(&html)
129 }
130
131 /// Render this document as a complete HTML page with template variable interpolation.
132 pub fn to_html_page_with_context(&self, config: &PageConfig, ctx: &TemplateContext) -> String {
133 let html = self.to_html_page(config);
134 ctx.resolve(&html)
135 }
136
137 /// Render this document to PDF bytes using the Typst engine.
138 ///
139 /// This is a synchronous, pure-Rust operation — no Chrome or external
140 /// tools required. Requires the `pdf` feature.
141 #[cfg(feature = "pdf")]
142 pub fn to_pdf(
143 &self,
144 config: &render_pdf::PdfConfig,
145 ) -> Result<Vec<u8>, render_pdf::PdfError> {
146 render_pdf::to_pdf(self, config)
147 }
148
149 /// Render this document as Typst markup text.
150 ///
151 /// The output is a valid `.typ` file that can be compiled by Typst.
152 /// `paper`/`report` documents use academic templates (IEEE/ACM/article and
153 /// MLA/APA/Chicago); all other documents use the generic SurfDoc layout.
154 pub fn to_typst(&self) -> String {
155 render_typst::to_typst(self)
156 }
157
158 /// Render this document as a complete LaTeX (`.tex`) document string.
159 ///
160 /// The output target is selected by the document's [`RenderProfile`]:
161 /// `paper` → `IEEEtran`/two-column `article`/`article`; `report` →
162 /// `article` configured for MLA/APA/Chicago; everything else → a plain
163 /// `article`. Citations and the reference list are produced by the citation
164 /// engine in the active style. The `.tex` is deterministic and compilable.
165 pub fn to_latex(&self) -> String {
166 render_latex::to_latex(self)
167 }
168
169 /// Render this document as ANSI-colored terminal text.
170 #[cfg(feature = "terminal")]
171 pub fn to_terminal(&self) -> String {
172 render_term::to_terminal(self)
173 }
174
175 /// Convert this document into a list of native blocks for mobile rendering.
176 #[cfg(feature = "native")]
177 pub fn to_native_blocks(&self) -> Vec<render_native::NativeBlock> {
178 render_native::to_native_blocks(self)
179 }
180
181 /// The [`RenderProfile`] for this document, resolved from its front-matter
182 /// `type:` and `format:` fields (see [`render_profile`]).
183 pub fn render_profile(&self) -> RenderProfile {
184 let (doc_type, format) = self
185 .front_matter
186 .as_ref()
187 .map(|fm| (fm.doc_type, fm.format))
188 .unwrap_or((None, None));
189 render_profile(doc_type, format)
190 }
191
192 /// Whether this document should render as a presentation deck — true for
193 /// `type: deck`, `type: slides`, and `type: presentation`. Callers that
194 /// auto-dispatch output should route these documents to [`Self::to_slides_html`].
195 pub fn is_presentation(&self) -> bool {
196 matches!(self.render_profile(), RenderProfile::Presentation)
197 }
198
199 /// Render this document as a complete, self-contained HTML presentation deck.
200 ///
201 /// Slides are first-class the same way Website is: a `::deck`/`::slide`
202 /// block family rendered via a dedicated renderer. If the document has no
203 /// explicit deck/slides, every `#`/`##` heading becomes a slide
204 /// (Presentation Mode). `type: presentation` (alongside `deck`/`slides`) is
205 /// recognized as this slides path via [`Self::is_presentation`]. Requires
206 /// the `slides` feature.
207 #[cfg(feature = "slides")]
208 pub fn to_slides_html(&self) -> String {
209 render_slides::to_slides_html(self)
210 }
211
212 /// Serialize this document back to valid `.surf` format text.
213 ///
214 /// The output can be parsed again with [`parse`] to produce an equivalent
215 /// document (round-trip).
216 pub fn to_surf_source(&self) -> String {
217 builder::to_surf_source(self)
218 }
219
220 /// Validate this document and return any diagnostics.
221 pub fn validate(&self) -> Vec<crate::error::Diagnostic> {
222 validate::validate(self)
223 }
224}