glyphweaveforge 0.1.4

Convert Markdown into PDF through an explicit Rust pipeline with minimal and Typst backends.
Documentation

GlyphWeaveForge logo

GlyphWeaveForge

GlyphWeaveForge converts Markdown into PDF through a small Rust pipeline with explicit boundaries:

api -> pipeline -> core -> adapters

The crate ships a lightweight built-in renderer by default and can optionally use Typst without changing the public builder API.

What the crate provides today

  • A fluent Forge builder for Markdown-to-PDF conversion.
  • Three input modes: UTF-8 text, UTF-8 bytes, and filesystem paths (fs feature).
  • Three output modes: in-memory bytes, explicit file path, and output directory (fs feature).
  • Built-in page sizes (A4, Letter, Legal, custom millimeter sizes).
  • Built-in layout modes (Paged, SinglePage).
  • Built-in themes with optional JSON overrides.
  • Resource resolution through filesystem lookup (fs) or caller-provided resolvers.
  • Renderer extension points through public RenderBackend and ResourceResolver traits.
  • Two backend selections behind a stable builder API: Minimal by default and Typst when the feature is enabled.

Installation

[dependencies]
glyphweaveforge = "0.1.3"

Enable optional features when you need them:

[dependencies]
glyphweaveforge = { version = "0.1.3", features = ["renderer-typst"] }

Main builder flow

use glyphweaveforge::Forge;

let pdf = Forge::new()
    .from_text("# Hello\n\nWorld")
    .to_memory()
    .convert()
    .expect("conversion should succeed");

let bytes = pdf.bytes.expect("memory output should contain PDF bytes");
assert!(bytes.starts_with(b"%PDF"));

Forge::new() starts with these defaults:

  • source: not set
  • output: not set
  • backend: RenderBackendSelection::Minimal
  • page size: PageSize::A4
  • layout mode: LayoutMode::Paged
  • theme: BuiltInTheme::Professional

Supported inputs and outputs

Always available:

  • from_text
  • from_bytes
  • to_memory
  • injected resource resolvers through with_resource_resolver or with_resource_adapter
  • explicit backend selection through with_backend
  • custom renderer injection through with_renderer
  • full option replacement through with_options

Available with the default fs feature:

  • from_path
  • to_file
  • to_directory
  • with_output_file_name for directory outputs
  • filesystem-based resource lookup for local assets relative to the markdown file

Output behavior

  • to_memory() returns PdfOutput { bytes: Some(...), written_path: None }.
  • to_file(...) writes the PDF and returns PdfOutput { bytes: None, written_path: Some(...) }.
  • to_directory(...) creates the directory if needed and derives the file name from the source name unless with_output_file_name(...) overrides it.

When deriving a directory output name, the crate appends .pdf if it is missing.

Page sizes, layout modes, and themes

Page sizes

  • PageSize::A4
  • PageSize::Letter
  • PageSize::Legal
  • PageSize::Custom { width_mm, height_mm }

Custom page sizes must be strictly positive.

Layout modes

  • LayoutMode::Paged: paginates rendered lines across pages.
  • LayoutMode::SinglePage: keeps the rendered content in a single page payload.

Built-in themes

  • BuiltInTheme::Invoice
  • BuiltInTheme::ScientificArticle
  • BuiltInTheme::Professional (default)
  • BuiltInTheme::Engineering
  • BuiltInTheme::Informational

with_theme(...) selects a built-in preset. with_theme_config(...) lets you combine an optional built-in theme with JSON overrides such as:

  • name
  • body_font_size_pt
  • code_font_size_pt
  • heading_scale
  • margin_mm

Example:

use glyphweaveforge::{BuiltInTheme, Forge, LayoutMode, PageSize, ThemeConfig};
use serde_json::json;

let pdf = Forge::new()
    .from_text("# Report\n\nBody")
    .to_memory()
    .with_page_size(PageSize::Letter)
    .with_layout_mode(LayoutMode::SinglePage)
    .with_theme_config(ThemeConfig {
        built_in: Some(BuiltInTheme::Engineering),
        custom_theme_json: Some(json!({
            "name": "custom-engineering",
            "margin_mm": 14.0
        })),
    })
    .convert()
    .expect("conversion should succeed");

assert!(pdf.bytes.expect("bytes should exist").starts_with(b"%PDF"));

Backend selection

The default build uses the minimal built-in renderer. To opt into the Typst backend, enable renderer-typst and select it explicitly:

# #[cfg(feature = "renderer-typst")]
# {
use glyphweaveforge::{Forge, RenderBackendSelection};

let pdf = Forge::new()
    .from_text("# Hello from Typst")
    .to_memory()
    .with_backend(RenderBackendSelection::Typst)
    .convert()
    .expect("typst backend should succeed");

assert!(pdf.bytes.expect("bytes should exist").starts_with(b"%PDF"));
# }

Backends exposed by the public API:

  • RenderBackendSelection::Minimal: always available in the default feature set.
  • RenderBackendSelection::Typst: available when renderer-typst is enabled.

The typst Cargo feature is a compatibility alias for renderer-typst.

If you need complete control, with_renderer(...) accepts any type implementing the public RenderBackend trait and overrides the built-in backend selection.

Feature matrix

  • fs (default): enables path-based source/output helpers and filesystem resource loading.
  • renderer-minimal (default): keeps the built-in lightweight renderer enabled and addressable in tests/documentation.
  • renderer-typst: enables the Typst-backed renderer.
  • typst: compatibility alias for renderer-typst.
  • mermaid: currently does not add real Mermaid rendering; fenced mermaid blocks remain visible as explicit unsupported fallbacks.
  • math: currently does not add real math layout; fenced math blocks remain visible as explicit unsupported fallbacks.

Current Markdown behavior

Supported today:

  • headings
  • paragraphs
  • emphasis/strong text
  • inline code
  • links
  • unordered and ordered lists
  • block quotes
  • thematic breaks
  • fenced code blocks
  • images, including injected resolvers and memory-backed assets
  • basic HTML <img> tags with src/alt
  • tables

Resource handling behavior:

  • For path-based markdown sources with the fs feature, local assets are resolved relative to the markdown file directory.
  • For text/bytes sources, you can inject assets with with_resource_resolver(...) or with_resource_adapter(...).
  • Missing assets remain visible in the output as explicit fallback text.

Unsupported advanced Markdown stays visible instead of silently disappearing. Examples include:

  • footnotes
  • Mermaid diagrams
  • math fences
  • unsupported raw HTML beyond basic <img> extraction

These paths render deterministic fallback labels with the original content preserved in the output when possible.

Extending the crate

Custom resource resolver

use std::io;

use glyphweaveforge::Forge;

let pdf = Forge::new()
    .from_text("![Logo](logo.png)")
    .to_memory()
    .with_resource_resolver(|href| {
        if href == "logo.png" {
            Ok(vec![1, 2, 3])
        } else {
            Err(io::Error::new(io::ErrorKind::NotFound, "missing"))
        }
    })
    .convert()
    .expect("conversion should succeed");

assert!(pdf.bytes.expect("bytes should exist").starts_with(b"%PDF"));

Custom renderer

use glyphweaveforge::{Document, Forge, RenderBackend, RenderRequest, Result};

struct StubRenderer;

impl RenderBackend for StubRenderer {
    fn render(&self, _document: &Document, _request: &RenderRequest) -> Result<Vec<u8>> {
        Ok(b"stub-pdf".to_vec())
    }
}

let pdf = Forge::new()
    .from_text("hello")
    .to_memory()
    .with_renderer(StubRenderer)
    .convert()
    .expect("custom renderer should be used");

assert_eq!(pdf.bytes, Some(b"stub-pdf".to_vec()));

The public extension traits are:

  • RenderBackend
  • ResourceResolver

The rendering request/asset helper types exposed for adapters are:

  • RenderRequest
  • ResolvedAsset
  • ResourceStatus

Limitations

  • This crate does not claim real Mermaid diagram rendering.
  • This crate does not claim real TeX/LaTeX or advanced math typesetting.
  • Footnotes are exposed through visible fallback text rather than full layout support.
  • General raw HTML is not a full rendering target; basic <img> extraction is supported, but broader HTML layout is not.
  • The minimal renderer writes a compact PDF text stream; it is designed for deterministic output and testability rather than rich page design.
  • renderer-typst is optional; docs.rs for the default documentation build does not require Typst support to use the crate.

Public error and result types

  • Result<T> is an alias for std::result::Result<T, ForgeError>.
  • ForgeError::MissingSource and ForgeError::MissingOutput protect incomplete builder usage.
  • ForgeError::InputRead and ForgeError::InvalidUtf8 cover source loading.
  • ForgeError::Resource covers resolver failures.
  • ForgeError::InvalidConfiguration covers invalid runtime options such as non-positive custom page sizes.
  • ForgeError::Render, ForgeError::TypstCompile, ForgeError::TypstExport, and ForgeError::TypstAsset cover backend/rendering failures.
  • ForgeError::OutputWrite, ForgeError::OutputDirectory, and ForgeError::InvalidOutputFileName cover output persistence.

Architecture note

Release work should preserve the internal boundary:

api -> pipeline -> core -> adapters