Skip to content
Docxcelerate

Reference

Package entrypoints

Every importable path and what lives behind it.

The package ships compiled output with type declarations. Every entrypoint below is declared in exports, so they resolve in Node and in bundlers without deep imports into dist/.

docxcelerate

The main surface: the document builder, artifact helpers, derivers, runtime types, and every type in the document domain.

import {
  buildDocument,
  buildProjectPreviewDocument,
  cleanMinimalDocumentStyle,
  dataRef,
  derive,
  type DocumentModel,
  type ParagraphNode,
} from "docxcelerate";

docxcelerate/document

Document project definition — the entrypoint a scaffolded document.project.ts uses. The CLI and the scaffolded filenames still say document; the API does not.

import { defineDocumentProject } from "docxcelerate/document";

docxcelerate/template

The authoring surface: the elements, the hooks, and template itself. This is what a component file imports.

import {
  Document,
  Graph,
  Image,
  Paragraph,
  Section,
  TableOfContents,
  template,
  useAvailableTokens,
  useFormat,
  usePlaceholderData,
  useSetPlaceholders,
  useSetPrompts,
  useShared,
  useState,
} from "docxcelerate/template";

Each element name is also a component type, so const Greeting: Paragraph = () => <Paragraph … /> names what a component returns and rejects returning the wrong kind.

Its JSX runtime is exposed at docxcelerate/template/jsx-runtime (and .../jsx-dev-runtime), which is what jsxImportSource resolves to.

docxcelerate/docx

DOCX construction.

import { createDocxBlob, createDocxDocument, renderDocxBytes } from "docxcelerate/docx";

There is one renderer, and it writes Word files. To show a document on a screen, pack it and read the file back with a DOCX viewer — the preview app your workspace is scaffolded with does that over docx-preview, and so do the previews on this site. A second renderer laying the model out in HTML would be a second answer to what a document looks like, and the one nobody opens in Word is the one that drifts.

docxcelerate/preview

Finishing a preview that docx-preview has laid out.

import { readPackedParagraphs, readPackedTables, settleDocxPreview } from "docxcelerate/preview";

const packed = new Uint8Array(await blob.arrayBuffer());
await renderAsync(packed, body, head, { breakPages: true });
settleDocxPreview(
  body,
  model,
  await readPackedParagraphs(packed),
  await readPackedTables(packed),
);

docx-preview does not read everything a .docx says. It drops a field run, so a footer Word prints as “1 / 2” arrives as a bare slash. It looks for a table’s indent under an attribute that never carries it. It reads w:spacing only when the parent is w:pPr, so a run’s letter spacing never reaches the page. It writes a paragraph border without the gap the border holds its text off by. It draws every row of a table as a plain row, so nothing says which of them is the heading that repeats at the top of each page. And it puts a picture in an element a paragraph may not contain, which survives in a DOM and does not survive being written down.

settleDocxPreview puts each of those back. Every one of them is a fact the packed file already declares and Word already draws — nothing here is a stylesheet, and nothing invents a layout. A preview patched with CSS that Word cannot reproduce is a preview that lies, which is the one thing reading the file back exists to prevent.

Three of them can only be recovered from the file, so readPackedParagraphs and readPackedTables read the bytes and settle is handed the result. Recomputing them from the style that produced the document would put a second copy of the packer’s arithmetic beside the first, and the copy nobody opens in Word is the one free to drift. Reading is asynchronous because inflating is; settle stays synchronous, because it mutates a DOM somebody is about to measure.

readPackedTables reads one fact per table: how many rows it opens with that repeat. Settle moves those into a <thead>, which is what the element already means, and the paginator copies them onto each new sheet — so a table running over three pages keeps its column headings on all three, the way Word prints it.

Call it after renderAsync and before the container is measured or serialised. It works the same in a browser as in a jsdom, and the scaffolded preview app already does this for you.

Charts

import { readPackedCharts, settleDocxPreviewCharts } from "docxcelerate/preview";

settleDocxPreviewCharts(body, await readPackedCharts(packed), draw);

A chart is packed as a real Word chart — a c:chartSpace part beside document.xml, with every value cached in it — and docx-preview has no reading of one at all: the string “chart” does not occur in its bundle. What it leaves is an empty inline-block span at exactly the size the file gave the frame, which is the half that matters most, because it is what the page is laid out around and it is what keeps the preview paginating like Word.

readPackedCharts reads the rest back out of the same bytes — the type, the categories, every series with its numbers and its colour, the key’s place, the format on the value axis. Read out of the file rather than out of the model that produced it, for the reason everything else here is: those are decisions the packer made from the theme, and a preview that made them again would be a second copy of the packer’s arithmetic.

settleDocxPreviewCharts puts what draw returns into each frame, in order. The drawer is yours — a chart renderer is a large, well-solved problem, and bundling one into a package whose only dependency is its packer would be the wrong trade for everyone who never previews a chart. A scaffolded workspace ships one over ECharts in preview/charts.ts; copy it, or write one over whatever your app already has. A drawer that returns undefined leaves the frame empty, which is the right answer for a chart it cannot plot: an empty frame of the right size is a gap, and a wrong plot is a lie.

What the preview then shows is honest about what it is. The frame, the numbers, the colours and the type are the file’s; the plot inside is another renderer’s drawing of them rather than Word’s. Word is the only thing that draws what Word draws — conformance/cases/charts/column-series holds the frames of the two against each other and leaves their pixels alone, deliberately.

Pages

docx-preview breaks where the file says to — an explicit break, or a paragraph marked to start one — and nowhere else. It does not lay text against a page height and start a new sheet when the old one is full, so a document Word prints on five pages arrives as one very long sheet. Everything read off that is wrong with it: which page a paragraph is on, whether a header repeats, what a footer’s “1 of 5” should say.

import { paginateDocxPreview } from "docxcelerate/preview";

paginateDocxPreview(body);

It flows the body’s blocks into boxes the height of the page, carries the running furniture onto each new sheet, and recounts the page numbers. It is a separate call from settleDocxPreview because it needs something to have laid the page out — a browser, or headless Chrome. In a jsdom every height is zero, which would read as “it all fits”, so it declines to answer instead and tells you why.

It breaks between blocks, with one exception: a table is broken between two of its rows, which is the seam Word breaks at too, and the rows that did not fit are carried onto the next sheet with the columns and the heading intact. A paragraph Word would split across a sheet still moves whole.

Both are measured rather than claimed. On documents made of ordinary paragraphs the two engines agree exactly — conformance/cases/preview/content-pagination. On a table sixty rows long they take the same number of pages and put the same rows on them, with the seam itself within one row of Word’s — conformance/cases/preview/table-pagination.

Tab stops

import { applyTabStops, readPackedParagraphs } from "docxcelerate/preview";

applyTabStops(body, await readPackedParagraphs(packed));

A tab stop is the one paragraph property whose effect cannot be written as a style: where the text after a tab lands depends on how wide the text before it drew. So like pagination it needs a layout, and runs in the same place — before paginating, since a line that has not found its stop is the wrong height to measure a page against.

Put together, a preview is finished in three steps, and the order is the order:

const packed = new Uint8Array(await blob.arrayBuffer());
const paragraphs = await readPackedParagraphs(packed);
const tables = await readPackedTables(packed);

await renderAsync(packed, body, head, { breakPages: true });
settleDocxPreview(body, model, paragraphs, tables);   // no layout needed
applyTabStops(body, paragraphs);                      // needs layout
paginateDocxPreview(body);                            // needs layout, and settled heights

The scaffolded preview app already does all of this.

docxcelerate/scaffold and /cli

The workspace generator and its command-line front end, for building your own tooling on top.

import { scaffoldWorkspace } from "docxcelerate/scaffold";

Edit this page on GitHub ↗ Read this page as Markdown