# tdoc > Open, edit, paginate and write `.docx` — in a browser and in a Cloudflare Worker — over a document model you own. The page breaks on screen are the page breaks in the exported PDF, because one layout decided both. TypeScript, no Node built-ins below the editor layer, no headless browser and no office suite anywhere. Measured continuously against nine real Word contracts. ## What surprises people - **Fonts are a runtime choice, not a build-time one.** `loadFontSet` produces the policy, the measurer, the `@font-face` CSS and the PDF faces from ONE declaration. Self-host `.ttf`: woff2, CFF `.otf` and variable fonts are refused by name, with the reason. - **A picture is one character.** It occupies a text position (U+FFFC), so offsets count it and typing over one is refused, the way a tab is. - **Undo of a suggestion is a REJECTION, not an inverse edit.** Replaying the inverse re-enters the suggest path and proposes the deleted text back in. `applyToModel` reports the change it made so a host can reject it; `mountEditor` binds ⌘Z to that. - **The markup view is an input to PAGINATION.** A document with a suggestion in it has two legitimate page counts, and switching views repaginates. That is the design, not a defect. - **An edit the model cannot express is REFUSED, with a reason.** Nothing is silently dropped, and `onRefused` firing is a contract a host can rely on. - **Relationship ids are per-part.** `rId1` of `document.xml` and `rId1` of `header1.xml` are different files; resolving against the wrong part returns the wrong one rather than failing. - **Fidelity is the default.** A node nobody touched serializes as its original bytes, so an element tdoc has never heard of still round-trips. Never rebuild a tree you only meant to read. ## Entry points Each is a subpath export, depending only on the ones below it. ### `tdoc/xml` 36 exports: `ancestors`, `appendChild`, `attr`, `childElements`, `childrenNamed`, `cloneElement`, `cloneNode`, `cloneShell`, `createElement`, `createText`, `decodeEntities`, `descendants`, `detachFromSource`, `ensureChild`, `escapeText`, `firstChild`, `getAttr`, `insertAt`, `insertBefore`, `insertInSchemaOrder`, `invalidate`, `isElement`, `nextNumericId`, `owningRun`, `parseXml`, `removeAttr`, `removeChild`, `replaceChild`, `serializeElement`, `serializeXml`, `setAttr`, `setText`, `textContent`, `touch`, `walk`, `XmlParseError` ### `tdoc/opc` 14 exports: `CONTENT_TYPES`, `crc32`, `deflate`, `DOCUMENT_PART`, `inflate`, `makeEntry`, `OpcPackage`, `PACKAGE_RELS`, `readZip`, `Relationship`, `relsPathFor`, `writeZip`, `ZipEntry`, `ZipError` ### `tdoc/model` 194 exports: `acceptChange`, `addComment`, `applyBorders`, `applyList`, `APPROVED_FONTS`, `assemble`, `Authoring`, `baseFamily`, `Block`, `blockControlsIn`, `blocksIn`, `BorderPreset`, `bulletGlyph`, `Cell`, `CellAddress`, `cellFill`, `CellMargins`, `changeKind`, `ChangeKind`, `charactersOf`, `classify`, `clearList`, `columnAt`, `columnsIn`, `Comment`, `COMMENT_MARKERS`, `CommentAnchor`, `commentAnchorsIn`, `CommentResult`, `Comments`, `commonAffixes`, `conditionalTypes`, `ContentControl`, `contextualSpace`, `ControlLevel`, `controlName`, `ControlResult`, `controlsIn`, `ControlSpec`, `createControl`, `createTable`, `decideAll`, `DecideResult`, `DEFAULT_CELL_MARGINS`, `DEFAULT_LOOK`, `DEFAULT_POLICY`, `DEFAULT_TABLE_WIDTH`, `deleteColumn`, `deleteComment`, `deleteRow`, `deleteTable`, `directParagraphs`, `DocImage`, `documentImages`, `EffectiveFonts`, `effectiveParagraph`, `EffectiveParagraph`, `effectiveParagraphMark`, `effectiveRun`, `EffectiveRun`, `EMU_PER_INCH`, `EMU_PER_TWIP`, `ensureFurniture`, `evaluateField`, `fieldOverrides`, `FieldOverrides`, `FieldValues`, `FontClass`, `FontPolicy`, `formatCounter`, `formatParagraph`, `formatRange`, `formatRun`, `Furniture`, `furnitureFor`, `FurnitureKind`, `furnitureKindFor`, `FurnitureParts`, `FurnitureResult`, `FurnitureSide`, `HeaderFooterRef`, `imageBytes`, `imageDimensions`, `imagesFor`, `imagesIn`, `imageSize`, `ImageStore`, `insertColumn`, `insertImage`, `InsertOptions`, `InsertResult`, `insertRow`, `isImageElement`, `isMetricCompatible`, `isModelled`, `isParagraph`, `isTable`, `isToggleProperty`, `joinParagraphs`, `JoinResult`, `leadTextOf`, `LETTER`, `ListKind`, `ListMarker`, `listMarkers`, `mapFamily`, `markerFont`, `MarkupView`, `mergeCells`, `METRIC_COMPATIBLE`, `MODELLED`, `NewComment`, `Numbering`, `NumberingLevel`, `OBJECT_REPLACEMENT`, `P_ORDER`, `P_PR_ORDER`, `PAGE_SIZES`, `PageMargins`, `PageSetup`, `PageSize`, `pageSizeNamed`, `Paragraph`, `ParagraphFormat`, `paragraphMarkChange`, `R_PR_ORDER`, `readPageSetup`, `readTableLook`, `readTheme`, `rejectChange`, `removeFurniture`, `replaceImageBytes`, `replyToComment`, `resizeImage`, `resolveComment`, `ResolveContext`, `resolveParagraph`, `resolveRun`, `resolveThemeFont`, `RetextResult`, `retextSegments`, `Row`, `Run`, `RunFormat`, `runsIn`, `SECT_PR_ORDER`, `Section`, `sectionBreakAfter`, `sectionsOf`, `setCellMargins`, `setCellShading`, `setControlText`, `setEvenAndOddHeaders`, `setHeaderRow`, `setListLevel`, `setPageMargins`, `setPageSize`, `setRowHeight`, `setTitlePage`, `shadingOf`, `SHAPE_ONLY`, `SpacingNeighbour`, `splitCell`, `splitParagraph`, `SplitResult`, `splitRunAt`, `StyleDef`, `StyleSheet`, `StyleType`, `Suggestion`, `SuggestResult`, `suggestText`, `Table`, `tableAt`, `TableContext`, `TableLook`, `tableParagraphLevels`, `TableResult`, `tableRunLevels`, `TabStop`, `TextOptions`, `TextSegment`, `textSegments`, `Theme`, `tidyRun`, `TOGGLES`, `TrackedChange`, `trackedChangesIn`, `trackParagraphMark`, `unwrapControl`, `visibleText`, `WordDocument`, `writeCharacters`, `writeUnder` ### `tdoc/docx` 4 exports: `blankDocx`, `BlankOptions`, `FontNormalisation`, `normaliseFonts` ### `tdoc/layout` 56 exports: `alignBlocksToPages`, `Alignment`, `AlignmentOptions`, `BlockId`, `breakIntoLines`, `BreakOptions`, `comparePagination`, `Comparison`, `DEFAULT_TEXT_STYLE`, `Disagreement`, `DocumentPagination`, `drawnRuns`, `FaceSpec`, `FillOptions`, `fillPages`, `firstStyleOf`, `Font`, `FontLibrary`, `FontMetrics`, `FontParseError`, `FontProblem`, `FontSetSpec`, `LaidBlock`, `layOutBlock`, `LayoutOptions`, `layOutParagraph`, `layOutTable`, `Line`, `LoadedFontSet`, `loadFontSet`, `LoadOptions`, `markerStyle`, `markerTextStart`, `Measurer`, `NO_METRIC_CLONE`, `normalise`, `Page`, `paginateDocument`, `PaginateOptions`, `Pagination`, `paginationFrom`, `paragraphLineHeight`, `policyFor`, `probeFor`, `sheetAt`, `SheetInput`, `sheetLayout`, `SheetLayout`, `SMALL_CAPS_RATIO`, `snapToPixel`, `spansOf`, `Split`, `standardFaces`, `StyledSpan`, `SUBSTITUTIONS`, `TextStyle` ### `tdoc/pdf` 11 exports: `EmbeddedImage`, `embedImage`, `encodeWinAnsi`, `FaceSource`, `formatOf`, `ImageRefusal`, `PdfWriter`, `RenderOptions`, `renderPdf`, `RenderResult`, `winAnsi` ### `tdoc/crdt` 22 exports: `ApplyReport`, `applyTo`, `bindRoom`, `BlockKind`, `BLOCKS`, `DocumentRoom`, `isSeeded`, `joinBlock`, `LOCAL`, `META`, `Pid`, `readBlocks`, `Room`, `RoomOpenOptions`, `RoomOptions`, `RoomState`, `seed`, `seedPid`, `SharedBlock`, `splice`, `splitBlock`, `writeText` ### `tdoc/editor` 48 exports: `AppliedTransaction`, `applyPageStyle`, `applyParagraphFormat`, `applyPastedMarks`, `applyPendingAfterInsert`, `applyRunFormat`, `applyToModel`, `CaretTarget`, `clearPendingFormat`, `controlAt`, `controlAtSelection`, `controlGuard`, `controlGuardKey`, `ControlSpan`, `controlSpans`, `deletionAt`, `docFromBlocks`, `editorPlugins`, `editPoints`, `FormatState`, `installFontCss`, `modelOffset`, `MountedEditor`, `mountEditor`, `MountOptions`, `nodeOffset`, `PaginationDecorations`, `paginationKey`, `PaginationOptions`, `paginationPlugin`, `ParagraphSnapshot`, `pendingFormat`, `readFormatState`, `reconcile`, `ReconcileResult`, `Registry`, `schema`, `SelectedParagraph`, `selectedParagraphs`, `SHEET_GAP`, `sizeSheets`, `snapshot`, `SuggestionHistory`, `tableAtSelection`, `textOf`, `Tri`, `unwrapControlAtSelection`, `wrapSelectionAsControl` `tdoc/document.css` ships with the editor and a host MUST include it: the classes it styles are what make the browser agree with the paginator. ## Documentation - [Architecture](https://github.com/saltbark/tdoc/blob/main/docs/architecture.md): Layers, bottom up. Each one is a subpath export, depends only on the ones below it, and is - [Audit: the editor stack, 2026-09-02](https://github.com/saltbark/tdoc/blob/main/docs/audit-2026-09-02.md): Scope: everything landed between `c4fd6d1` and `82b3d8f` -- the consumer seams, content controls, - [The harnesses](https://github.com/saltbark/tdoc/blob/main/docs/harnesses.md): Everything in tdoc is measured against nine real Word contracts rather than fixtures. This is the - [OOXML notes](https://github.com/saltbark/tdoc/blob/main/docs/ooxml.md): Working notes on ECMA-376 Part 1 (WordprocessingML, §17), kept as the model learns each area. - [Pagination, and how it is measured](https://github.com/saltbark/tdoc/blob/main/docs/pagination.md): R1 says the page breaks on screen and the page breaks in the PDF are the same. This is how that - [Plan and status](https://github.com/saltbark/tdoc/blob/main/docs/plan.md): **This is the one status file.** Phases, the road to a first real consumer, and the defects that - [What tdoc has to do](https://github.com/saltbark/tdoc/blob/main/docs/requirements.md): Four requirements. They are not a wishlist; they are the reason this exists rather than a - [Rendering the PDF without a browser](https://github.com/saltbark/tdoc/blob/main/docs/server-pdf.md): The renderer that draws pages straight from the model, with no browser and no office suite. It is - [README](https://github.com/saltbark/tdoc/blob/main/README.md): what it is, how to run it, and the site. - [CLAUDE.md](https://github.com/saltbark/tdoc/blob/main/CLAUDE.md): the rules an agent working IN this repo follows. - [/llms-full.txt](/llms-full.txt): every document above, in full, in one file. ## Status Measured, `pnpm render-check` over the nine corpus contracts (2026-09-03): ``` document pages ovfl screen=model screen=pdf screen=server glyphs screen=libre CA_Courts_Agreement 26/26 0 100% 100% 100% 94% 13% Employment_Agreement 2/2 0 100% 100% 100% 99% 100% MITP_KF_2026 37/37 0 100% 100% 100% 31% 76% NDA_Template 2/2 0 100% 100% 100% 100% 100% NY_SERDA_Agreement 56/56 1 100% 100% 100% 93% 84% OECS_SOW_2026 2/2 0 100% 100% 100% 27% 100% Oregon_MSA 44/44 0 100% 100% 100% 93% 45% SOW_Template 2/2 0 100% 100% 100% 56% 100% VA_SLA 15/15 0 100% 100% 100% 37% 100% ``` Phases 0-3 are done. **R1 holds on all nine documents**, on both PDF paths -- the one printed from Chromium and the one drawn from the model with no browser anywhere. Over the twenty generated page-setup variants it holds on nineteen; `zvar-VA_SLA-mixed-size` is at 75% and is the known mixed-section defect, and `zvar-VA_SLA-narrow` at 98% is one block. The ordered list of what is left is in [plan.md](https://github.com/saltbark/tdoc/blob/main/docs/plan.md#what-is-left-in-order).