feat(doc-codec): add MS-DOC structure writer (partial write support) - #876
Merged
Conversation
Mearman
marked this pull request as ready for review
September 3, 2026 12:13
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Implements writeDocContent, taking a single-section wordprocessing ContentDocument and producing real Word Binary File bytes wrapped in a real MS-CFB compound file: a piece table (always one uncompressed 16-bit piece, sidestepping the compressed-character mapping table entirely), Sprm-encoded grpprls for direct character and paragraph formatting, ChpxFkp/PapxFkp pages that split across as many 512-byte pages as the content needs, a spec-conformant style sheet carrying zero styles (FibRgFcLcb97.lcbStshf must be nonzero even though this package's own reader tolerates a missing one), and a font table (SttbfFfn/FFN) when a run names a font. Character formatting: bold, italic, strike, underline, sizePt, color (via sprmCCv's exact COLORREF rather than the lossy Ico palette), and fontFamily. Paragraph formatting: alignment, indentLeftPt, indentFirstLinePt, spacingBeforePt, spacingAfterPt, lineSpacing (multiplier form), and pageBreakBefore. The font table is new on the read side too: chp.ts now resolves sprmCRgFtc0 through a document's own SttbfFfn into ContentRun's fontFamily field, since round-tripping a run's font needed the reader to understand the same structure the writer produces. Refuses, rather than approximates, a document with more than one section, a non-paragraph block (table, image, embedded object, construct marker), and any property this package's own reader does not itself convert.
Round-trips every property the writer supports through readDocContent: plain and formatted runs, every paragraph property, an empty section, a paragraph with no runs, non-Latin-1 and non-BMP text, and enough distinct formatting exceptions and paragraphs to force ChpxFkp/PapxFkp page-splitting rather than exercising only the common one-page case. Also asserts writeDocContent refuses a non-wordprocessing document, a multi-section document, and an unsupported block kind. Adds a workers-runtime write+read round trip alongside the existing read-only workers test, so the writer's own Worker-isomorphism is a runtime-checked fact rather than an assertion, matching the reader.
Updates the Status section's read-only claim, adds a Writing section covering exactly what writeDocContent converts and what it refuses, documents the shared font-table module (SttbfFfn/FFN) and why the writer always emits uncompressed 16-bit text, extends the module and specification-citation tables, and adds a writeDocContent usage example alongside the existing read one.
lint-staged's prettier pass wasn't run before the writer PR's last commit, leaving the "Not built, and not approximated" table's column widths out of step with its own content.
Mearman
force-pushed
the
worktree-agent-a4a5c5b3fc9f3a3f3
branch
from
September 3, 2026 12:16
3516232 to
b57c3df
Compare
This was referenced Sep 3, 2026
Contributor
|
🎉 This PR is included in version 1.1.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
writeDocContenttodoc-codec: a genuine, spec-grounded [MS-DOC] writer for a single-section wordprocessingContentDocument, producing real Word Binary File bytes wrapped in a real [MS-CFB] compound file viaarchive-codec'swriteCompoundFile. This is progress on #816 but does not claim full read+write parity — see Scope below.What's written, all inverting real structures the package's own reader (
readDocContent) already consumes:Clx), always one uncompressed 16-bit piece.Sprm-encodedChpxFkp/PapxFkpformatting exceptions, packed and split across as many 512-byte pages as the content needs (not just the common one-page case).FibRgFcLcb97.lcbStshf"MUST be a nonzero value").SttbfFfn/FFN) when a run names a font — this needed a small reader-side addition too (sprmCRgFtc0resolution intoContentRun.fontFamily), since round-tripping a font needed the reader to understand the same table the writer produces.Character formatting:
bold,italic,strike,underline,sizePt,color(exactsprmCCvCOLORREF, not the lossy 17-entrysprmCIcopalette),fontFamily. Paragraph formatting:alignment,indentLeftPt,indentFirstLinePt,spacingBeforePt,spacingAfterPt,lineSpacing(multiplier form),pageBreakBefore.Scope — what this PR does NOT cover
Refused with
DocUnsupportedErrorrather than approximated:styleId/headingLevelare not written; every paragraph isistd0).ContentParagraph's right-margin indent doesn't exist as a schema field at all (a pre-existing gap, not new to this PR).SttbfAssoc,Dop, printer-driver structures, ~140 others) are left zero — conformant for this package's own round trip, not a certification that every third-party reader accepts the result.Full details and rationale are in
packages/doc-codec/README.md's new "Writing" section and "Not built, and not approximated" table.Verification
src/write.test.ts, 18 tests): every supported property, an empty section, a paragraph with no runs, non-Latin-1/non-BMP text, and forced multi-pageChpxFkp/PapxFkpsplitting (150 distinctly-formatted runs, 60 distinctly-indented paragraphs).writeDocContentsample exercising every supported property was opened, rendered, and re-exported by LibreOffice (soffice --headless --convert-to txtand--convert-to pdf) without error or content loss — bold/italic/underline/strike/colour/font-family runs, centred alignment, indentation, and Unicode (café, 中文, an emoji surrogate pair) all rendered correctly in the PDF output. This is a real, independent [MS-DOC] implementation, not this package's own reader.pnpm exec turbo run _lint _typecheck _test _test:workers --filter=doc-codec— all green (127 unit tests, 4 workerd tests).pnpm exec turbo run _lint _typecheck— all 51 tasks green.Test plan
pnpm exec turbo run _lint _typecheck _test _test:workers --filter=doc-codecpnpm exec turbo run _lint _typechecksoffice --headless --convert-to pdf) on a sample exercising every writer feature