Building the Shared Library

cd rust
cargo build --release

# Output locations:
# Linux:   target/release/libmarkdown_academic.so
# macOS:   target/release/libmarkdown_academic.dylib
# Windows: target/release/markdown_academic.dll

For PDF support, add the feature flag:

cargo build --release --features pdf

Python Package

The recommended way to use markdown-academic from Python:

Installation

# Build the Rust library first
cd rust && cargo build --release --features pdf

# Install the Python package
cd ../python && pip install -e .

Basic Usage

import markdown_academic as mda

# Render to HTML fragment
html = mda.render("# Hello\n\nThe equation $E=mc^2$ is famous.")
print(html)

# Render complete HTML document
html = mda.render("# Hello", standalone=True)

# Choose math backend
html = mda.render("$x^2$", math_backend=mda.MathBackend.MATHJAX)

PDF Generation

import markdown_academic as mda

# Check if PDF support is available
if mda.has_pdf_support():
    # Render to PDF bytes
    pdf_bytes = mda.render_pdf("# My Document\n\n$E=mc^2$")
    with open("output.pdf", "wb") as f:
        f.write(pdf_bytes)
    
    # Or write directly to file with options
    mda.render_pdf_to_file(
        "# My Document",
        "output.pdf",
        paper_size=mda.PaperSize.A4,
        title="My Document"
    )

Document Class

Parse once, render multiple times:

import markdown_academic as mda

# Using context manager for automatic cleanup
with mda.Document("# Hello $E=mc^2$") as doc:
    html_fragment = doc.render()
    html_full = doc.render(standalone=True)
    
    # Access metadata
    print(doc.title)
    print(doc.authors)

Configuration Options

import markdown_academic as mda

# Math backends
mda.MathBackend.KATEX    # Fast, client-side (default)
mda.MathBackend.MATHJAX  # Full LaTeX support
mda.MathBackend.MATHML   # Native browser rendering

# Paper sizes (for PDF)
mda.PaperSize.LETTER     # US Letter (default)
mda.PaperSize.A4         # ISO A4

# Full render options
html = mda.render(
    source,
    standalone=True,
    math_backend=mda.MathBackend.KATEX,
    title="Document Title",
    custom_css="body { font-family: Georgia; }",
    strict_citations=False,
    strict_references=False,
    base_path="/path/to/document"
)

Error Handling

import markdown_academic as mda

try:
    html = mda.render(source, strict_citations=True)
except mda.ParseError as e:
    print(f"Parse error: {e}")
except mda.ResolutionError as e:
    print(f"Resolution error: {e}")
except mda.RenderError as e:
    print(f"Render error: {e}")

C API

Header File

// markdown_academic.h
#ifndef MARKDOWN_ACADEMIC_H
#define MARKDOWN_ACADEMIC_H

#include <stdint.h>

// Opaque document handle
typedef struct MdAcademicDocument MdAcademicDocument;

// Configuration options
typedef struct {
    int math_backend;     // 0 = KaTeX, 1 = MathJax, 2 = MathML
    int standalone;       // 0 = fragment, 1 = full HTML document
    const char* base_path;
    const char* title;
    int strict_citations;
    int strict_references;
} MdAcademicConfig;

// Result type (check error field first)
typedef struct {
    char* data;           // Result string (NULL on error)
    char* error;          // Error message (NULL on success)
} MdAcademicResult;

// === One-shot API ===

// Parse and render in one step
MdAcademicResult mdacademic_parse_and_render(
    const char* input,
    const MdAcademicConfig* config  // NULL for defaults
);

// === Two-step API ===

// Parse input into a document
MdAcademicDocument* mdacademic_parse(const char* input);

// Render document to HTML
MdAcademicResult mdacademic_render_html(
    const MdAcademicDocument* doc,
    const MdAcademicConfig* config
);

// === Memory Management ===

void mdacademic_free_string(char* s);
void mdacademic_free_document(MdAcademicDocument* doc);
void mdacademic_free_result(MdAcademicResult result);

// === Utility ===

const char* mdacademic_version(void);
int mdacademic_has_pdf_support(void);

#endif

Usage Example (C)

#include "markdown_academic.h"
#include <stdio.h>

int main() {
    const char* input = "# Hello\n\nThe equation $E=mc^2$ is famous.";
    
    MdAcademicConfig config = {
        .math_backend = 0,  // KaTeX
        .standalone = 1,    // Full HTML document
        .base_path = NULL,
        .title = "My Document",
        .strict_citations = 0,
        .strict_references = 0
    };
    
    MdAcademicResult result = mdacademic_parse_and_render(input, &config);
    
    if (result.error != NULL) {
        fprintf(stderr, "Error: %s\n", result.error);
        mdacademic_free_result(result);
        return 1;
    }
    
    printf("%s\n", result.data);
    mdacademic_free_result(result);  // Always free!
    
    return 0;
}

Low-level ctypes (Python)

For reference, here's how to use the C API directly with ctypes:

import ctypes
from ctypes import c_char_p, c_int, POINTER, Structure

# Load the library
lib = ctypes.CDLL("./libmarkdown_academic.so")

class MdAcademicConfig(Structure):
    _fields_ = [
        ("math_backend", c_int),
        ("standalone", c_int),
        ("base_path", c_char_p),
        ("title", c_char_p),
        ("strict_citations", c_int),
        ("strict_references", c_int),
    ]

class MdAcademicResult(Structure):
    _fields_ = [
        ("data", c_char_p),
        ("error", c_char_p),
    ]

# Set up function signatures
lib.mdacademic_parse_and_render.argtypes = [c_char_p, POINTER(MdAcademicConfig)]
lib.mdacademic_parse_and_render.restype = MdAcademicResult
lib.mdacademic_free_result.argtypes = [MdAcademicResult]
lib.mdacademic_version.restype = c_char_p

def render_markdown(text: str, standalone: bool = False) -> str:
    config = MdAcademicConfig(
        math_backend=0,
        standalone=1 if standalone else 0,
        base_path=None,
        title=None,
        strict_citations=0,
        strict_references=0
    )
    
    result = lib.mdacademic_parse_and_render(
        text.encode('utf-8'),
        ctypes.byref(config)
    )
    
    if result.error:
        error_msg = result.error.decode('utf-8')
        lib.mdacademic_free_result(result)
        raise ValueError(error_msg)
    
    html = result.data.decode('utf-8')
    lib.mdacademic_free_result(result)
    return html

# Usage
html = render_markdown("# Hello\n\n$E=mc^2$", standalone=True)
print(html)

WebAssembly

Build with the wasm feature for browser/Node.js usage:

# Install wasm-pack if needed
cargo install wasm-pack

# Build the WASM package
cd rust
wasm-pack build --target web --features wasm

JavaScript Usage

import init, { renderMarkdown, RenderOptions } from './pkg/markdown_academic.js';

async function main() {
    // Initialize the WASM module
    await init();
    
    // Simple usage
    const html = renderMarkdown('# Hello $x^2$');
    console.log(html);
    
    // With options
    const options = new RenderOptions();
    options.setMathBackend('katex');
    options.setStandalone(true);
    options.setTitle('My Document');
    
    const fullHtml = renderMarkdown('# Hello', options);
    document.body.innerHTML = fullHtml;
}

main();

Node.js Usage

const { renderMarkdown, RenderOptions } = require('./pkg/markdown_academic.js');

const input = `
# Introduction {#sec:intro}

The equation $E = mc^2$ is famous.

See @sec:intro for more details.
`;

const options = new RenderOptions();
options.setStandalone(true);

const html = renderMarkdown(input, options);
console.log(html);

Browser Script Tag

<script type="module">
import init, { renderMarkdown } from './pkg/markdown_academic.js';

init().then(() => {
    const input = document.getElementById('editor').value;
    const html = renderMarkdown(input);
    document.getElementById('preview').innerHTML = html;
});
</script>