Basic Usage

The library processes documents in three stages: parse, resolve, and render.

use markdown_academic::{parse, resolve, render_html, ResolveConfig, HtmlConfig};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = r#"
# Introduction {#sec:intro}

The equation $E = mc^2$ is famous. See @sec:intro for more.

::: theorem {#thm:main}
Every natural number is interesting.
:::
"#;

    // Stage 1: Parse the document into an AST
    let doc = parse(input)?;
    
    // Stage 2: Resolve references, citations, and numbering
    let resolved = resolve(doc, &ResolveConfig::default())?;
    
    // Stage 3: Render to HTML
    let html = render_html(&resolved, &HtmlConfig::default())?;
    
    println!("{}", html);
    Ok(())
}

Convenience Functions

For simple use cases, the render function combines all three stages:

use markdown_academic::render;

// Minimal usage
let html = render("# Hello *world*", None, None)?;

// With configuration
let html = render(
    input,
    Some(&ResolveConfig::default()),
    Some(&HtmlConfig { standalone: true, ..Default::default() })
)?;

Configuration

ResolveConfig

Controls reference resolution and citation handling:

use markdown_academic::ResolveConfig;

let config = ResolveConfig {
    // Base path for resolving relative bibliography paths
    base_path: Some("/path/to/document".to_string()),
    
    // Error on unknown citations (default: false)
    // When false, unknown citations render as [?key]
    strict_citations: true,
    
    // Error on unknown references (default: false)
    // When false, unknown refs render as ??
    strict_references: true,
};

HtmlConfig

Controls HTML output generation:

use markdown_academic::{HtmlConfig, MathBackend};

let config = HtmlConfig {
    // Math rendering backend
    math_backend: MathBackend::KaTeX,  // or MathJax, MathML
    
    // Generate complete HTML document vs fragment
    standalone: true,
    
    // Document title (used in standalone mode)
    title: Some("My Document".to_string()),
    
    // Custom CSS to include (standalone mode)
    custom_css: Some("body { max-width: 800px; }".to_string()),
    
    // Include table of contents
    include_toc: true,
    
    // CSS class prefix (default: "mda")
    class_prefix: "mda".to_string(),
};

MathBackend Options

BackendDescription
MathBackend::KaTeX Fast client-side rendering (default)
MathBackend::MathJax Comprehensive LaTeX support
MathBackend::MathML Native browser rendering (requires mathml feature)

Core Types

Document Structure

pub struct Document {
    pub metadata: Metadata,
    pub blocks: Vec<Block>,
}

pub struct Metadata {
    pub title: Option<String>,
    pub subtitle: Option<String>,
    pub authors: Vec<String>,
    pub date: Option<String>,
    pub keywords: Vec<String>,
    pub institution: Option<String>,
    pub macros: HashMap<String, Macro>,
    pub bibliography_path: Option<String>,
}

pub struct ResolvedDocument {
    pub document: Document,
    pub labels: HashMap<String, LabelInfo>,
    pub bibliography: Vec<BibEntry>,
    pub footnotes: Vec<Footnote>,
}

Block Elements

pub enum Block {
    Paragraph(Vec<Inline>),
    
    Heading {
        level: u8,
        content: Vec<Inline>,
        label: Option<String>,
        number: Option<String>,
    },
    
    CodeBlock {
        language: Option<String>,
        content: String,
    },
    
    BlockQuote(Vec<Block>),
    
    List {
        ordered: bool,
        start: Option<u32>,
        items: Vec<ListItem>,
    },
    
    DisplayMath {
        content: String,
        label: Option<String>,
        number: Option<u32>,
    },
    
    Environment {
        kind: EnvironmentKind,
        label: Option<String>,
        content: Vec<Block>,
        caption: Option<Vec<Inline>>,
        number: Option<u32>,
    },
    
    Table {
        headers: Vec<Vec<Inline>>,
        alignments: Vec<Alignment>,
        rows: Vec<Vec<Vec<Inline>>>,
        caption: Option<Vec<Inline>>,
        label: Option<String>,
    },
    
    TableOfContents,
    ThematicBreak,
    PageBreak,
    AppendixMarker,
}

Inline Elements

pub enum Inline {
    Text(String),
    Emphasis(Vec<Inline>),
    Strong(Vec<Inline>),
    Code(String),
    
    Link {
        url: String,
        title: Option<String>,
        content: Vec<Inline>,
    },
    
    Image {
        url: String,
        alt: String,
        title: Option<String>,
    },
    
    InlineMath(String),
    
    Citation(Citation),
    
    Reference {
        label: String,
        resolved: Option<String>,  // Filled after resolution
    },
    
    Footnote(FootnoteKind),
    
    Subscript(Vec<Inline>),
    Superscript(Vec<Inline>),
    SmallCaps(Vec<Inline>),
    
    SoftBreak,
    HardBreak,
}

Environment Kinds

pub enum EnvironmentKind {
    Theorem,
    Lemma,
    Proposition,
    Corollary,
    Definition,
    Example,
    Remark,
    Proof,
    Figure,
    Table,
    Algorithm,
    Abstract,
    Note,
    Warning,
}

Error Handling

use markdown_academic::{Error, ParseError, ResolutionError, RenderError};

fn process(input: &str) -> Result<String, Error> {
    let doc = parse(input)?;
    let resolved = resolve(doc, &ResolveConfig::default())?;
    render_html(&resolved, &HtmlConfig::default())
}

match process(input) {
    Ok(html) => println!("{}", html),
    
    Err(Error::Parse(ParseError::FrontMatter(msg))) => {
        eprintln!("Invalid TOML front matter: {}", msg);
    }
    
    Err(Error::Parse(ParseError::Syntax { line, message })) => {
        eprintln!("Syntax error on line {}: {}", line, message);
    }
    
    Err(Error::Resolution(ResolutionError::UnknownReference(label))) => {
        eprintln!("Unknown reference: @{}", label);
    }
    
    Err(Error::Resolution(ResolutionError::UnknownCitation(key))) => {
        eprintln!("Unknown citation: [@{}]", key);
    }
    
    Err(Error::Resolution(ResolutionError::BibliographyLoad(path, msg))) => {
        eprintln!("Failed to load {}: {}", path, msg);
    }
    
    Err(Error::Render(RenderError::Io(e))) => {
        eprintln!("I/O error: {}", e);
    }
    
    Err(e) => eprintln!("Error: {}", e),
}

PDF Generation

Enable the pdf feature for PDF output:

# Cargo.toml
[dependencies]
markdown-academic = { version = "0.1", features = ["pdf"] }

Basic PDF Usage

use markdown_academic::{parse, resolve, render_pdf, ResolveConfig, PdfConfig};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = "# My Document\n\nContent with math: $E=mc^2$";
    
    let doc = parse(input)?;
    let resolved = resolve(doc, &ResolveConfig::default())?;
    
    // Render to PDF bytes
    let pdf_bytes = render_pdf(&resolved, &PdfConfig::default())?;
    std::fs::write("output.pdf", pdf_bytes)?;
    
    Ok(())
}

PdfConfig Options

use markdown_academic::{PdfConfig, PaperSize, PageMargins};

let config = PdfConfig {
    // Paper size: Letter (default) or A4
    paper_size: PaperSize::A4,
    
    // Font size in points
    font_size: 11,
    
    // Page margins
    margins: PageMargins {
        top: 72.0,    // 1 inch
        bottom: 72.0,
        left: 72.0,
        right: 72.0,
    },
    
    // Include title page
    title_page: true,
    
    // Include page numbers
    page_numbers: true,
    
    // Document title (overrides metadata)
    title: Some("My Document".to_string()),
    
    ..Default::default()
};

Convenience Functions

use markdown_academic::{render_to_pdf, render_to_pdf_file};

// Get PDF bytes directly (all-in-one)
let pdf = render_to_pdf(input, None, None)?;

// Write to file directly
render_to_pdf_file(input, None, None, "output.pdf")?;