docboss.dev

Rust

Word documents read, rendered and written in pure Rust.

docboss is a workspace of crates sharing one version: readers for WordprocessingML and the Word binary format into one document model, text, Markdown, HTML and JSON output, a layout engine and rasterizer, a deterministic DOCX writer and an async reader for remote files. Safe Rust, its own ZIP, XML, compound-file and font code, no C dependencies.

Install

Add only what you use.

docboss-core opens a document and re-exports the model, so most programs start from docboss_core::open and add one consumer after it.

shell
$ cargo add docboss-core docboss-output docboss-layout docboss-render docboss-write
$ cargo add docboss-aio --features http   # async and remote reads

Quickstart

Open, extract, lay out, render, write.

open reads a path, read bytes, and detect reports the format from the leading bytes. Everything after reading works on the model, so it behaves the same for DOCX and DOC.

main.rs
use docboss_output::{to_markdown, to_text, MarkdownOptions, TextOptions};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = docboss_core::open("report.docx")?;   // DOCX or DOC, detected from the bytes
    println!("{}", to_text(&doc, &TextOptions::default()));
    println!("{}", to_markdown(&doc, &MarkdownOptions::default()));

    let layout = docboss_layout::layout_document(&doc);
    println!("{} pages", layout.pages.len());
    docboss_render::render_page(&layout, 0, 2.0)?.save("page.png")?;

    docboss_write::save(&doc, "copy.docx")?;        // deterministic DOCX
    Ok(())
}

Layout

Pages as positioned items, then pixels.

Each laid-out page holds positioned glyph runs, rectangles, lines and images in points, so a program can inspect geometry without rasterizing. render_pages uses all cores, and docboss_layout::layout takes an explicit FontDatabase when several documents should share one.

render.rs
use docboss_render::Format;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = docboss_core::open("report.docx")?;
    let layout = docboss_layout::layout_document(&doc);
    for (index, page) in docboss_render::render_pages(&layout, 2.0).into_iter().enumerate() {
        std::fs::write(format!("page-{}.png", index + 1), page?.encode(Format::Png)?)?;
    }
    Ok(())
}

Passwords and diagnostics

Errors as values, damage as diagnostics.

Errors are thiserror enums per crate; docboss_core::Error wraps the readers’ errors and adds Encrypted and Unsupported. No library function panics on input bytes: damaged input gives an error or a document with diagnostics.

locked.rs
use docboss_output::{to_html, HtmlOptions};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let options = docboss_core::Options { password: Some("secret".into()) };
    let doc = docboss_core::open_with("locked.docx", &options)?;
    for diagnostic in &doc.diagnostics {
        eprintln!("{:?} {}: {}", diagnostic.severity, diagnostic.location, diagnostic.message);
    }
    let html = to_html(&doc, &HtmlOptions { standalone: true, ..HtmlOptions::default() });
    std::fs::write("locked.html", html)?;
    Ok(())
}

Async

Remote reads with docboss-aio.

With the http feature, AsyncDocument::open_url fetches only the byte ranges the reader needs. ReadOptions { media: true, .. } fetches images too; open, from_bytes and from_backend give the same interface over files, memory and any other byte source.

remote.rs
use docboss_aio::{AsyncDocument, ReadOptions};

let remote = AsyncDocument::open_url("https://example.com/report.docx").await?;
let doc = remote.read(&ReadOptions::default()).await?;
println!("{} of {} bytes fetched", remote.bytes_fetched(), remote.len());

The workspace

One crate per concern.

Design

What the crates promise.

  • Lenient reading: a broken ZIP central directory, a looping sector chain or malformed XML is worked around, and every approximated or dropped item is a Diagnostic.
  • No panics on input bytes: offsets are bounds-checked, and recursion, allocation sizes and loop counts derived from the file are capped.
  • Independent DOCX parts are parsed on separate threads, and pages render on all cores.
  • Deterministic DOCX output: the same input gives identical bytes, with a fixed ZIP timestamp.
  • Specification citations in the code and tests, held to the conformance ledger by a Lean 4 gate.