Skip to main content

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}