feat: add private DOCX-to-PDF export - #707
Draft
TimurKr wants to merge 15 commits into
Draft
Conversation
Keep export sessions authoritative for resource ownership and geometry. Confine packaged asset reads to host-selected roots and byte limits. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
All contributors have signed the CLA ✍️ ✅ Posted by the CLA bot. |
TimurKr
marked this pull request as draft
September 2, 2026 20:19
jedrazb
reviewed
Sep 2, 2026
jedrazb
left a comment
Contributor
There was a problem hiding this comment.
Not ready to merge
I ran exportPdf over every document in e2e/fixtures (97 files). 34 of them fail to export, and the exported text is wrong for any script outside Windows-1252. Fonts are not embedded, which is the core ask of #657 and the P0 in #697. Details are inline; the summary is here.
Crashes (reproduced)
| Cause | Fixtures | Error |
|---|---|---|
| Underline or strike on any run | 10 (demo.docx, hyperlink-demo.docx, toc-hyperlink-tabs.docx, styled-content.docx, ...) |
Error: unsupported number: NaN |
Hard page break (w:br w:type="page") |
24 (typing-perf-521pp.docx, footer-page-number.docx, titlePg-header-footer.docx, ...) |
PdfPaintValidationError: box.width must be greater than zero |
Both reproduce with a one-paragraph document built from test/fixture.ts.
Fonts and text (reproduced)
- The writer paints every span with a PDF standard-14 font. Those fonts are WinAnsi only.
Zażółć gęślą jaźńexports asZaÏ1Brg‘[lR ja¡D, Cyrillic and Greek become byte salad, and a Symbol bullet becomesð·. No diagnostic names this. - Core lays out with the packaged metric-compatible faces, then the writer paints Helvetica. On
issue-68-large.docxHelvetica is 8.7% wider in aggregate (Discussion82.3 pt vs 101.0 pt). Spans overlap their neighbors and overrun the right margin. - The session already publishes
admittedFontFaceandshapeLaidOutText(base PR #706). This PR callsshapeLaidOutTextfor every span, discards the result, and uses it only to pick a diagnostic string. That costs 834 ms on the 307-page fixture and makesfidelityPolicy: 'strict'throw for every document that contains text.
Contract drift from #651 and #657
- #651 and #657 put this package under the EigenPal Pro License, following
editor-api. The PR shipsApache-2.0inpackage.jsonandLICENSE, with nolicense-check.json. - #657 asks to remove the unused
pdf-liboptional peer frompackages/core/package.json. It is still there. Deferring is fine, but say so intasks.md. - #657 lists tables, images, headers and footers, and note areas as MVP scope. This slice paints body, header, and footer text only. The README states this, so I read it as an intentional first slice, not an MVP. Rename the PR or the task list so the two agree.
Stacking
The base branch is PR #706, which has its own open blockers. Rebase after #706 lands. The packaged-font trustedRoot symlink finding from that review applies to pdf-export.ts too (inline).
What passes
bun test ./packages/docx-to-pdf/test: 84 pass.bun run typecheck: pass.bun run lint: 0 errors.- The paint-command model, bounds, cancellation, and diagnostics collector look solid. The planner and writer split is the right shape.
Suggested order
- Fix the two crash classes and add both to
export-pdf.test.ts. - Embed admitted face bytes through
doc.font(buffer)so widths and code points come from the same face Core measured. Gate strict mode on that, not on a per-span diagnostic. - Settle the license before anything publishes.
- Add a fixture sweep test over
e2e/fixturesso the export never regresses to throwing on a supported document.
Co-authored-by: Cursor <cursoragent@cursor.com>
Build PDF bytes from Core semantic pages with bounded planning, encoding, and asset reads. Report unsupported fidelity explicitly. Co-authored-by: Cursor <cursoragent@cursor.com>
TimurKr
force-pushed
the
feat/docx-to-pdf
branch
from
September 3, 2026 11:22
6050f68 to
74c6723
Compare
Embed admitted fonts safely, preserve Unicode, and refuse unsupported font encodings without corrupting output. Co-authored-by: Cursor <cursoragent@cursor.com>
Build PDF bytes from Core semantic pages with bounded planning, encoding, and asset reads. Report unsupported fidelity explicitly. Co-authored-by: Cursor <cursoragent@cursor.com>
Embed admitted fonts safely, preserve Unicode, and refuse unsupported font encodings without corrupting output. Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Paint published paragraph borders, embed selected TTC faces, and keep planner traversal cancellable. Remaining: Compare EP_ZMVZ_MULTI_v4 against Word with macOS Garamond and fix confirmed residual layout differences. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Treat the paragraph text start as the first-line implicit tab for non-list hanging indents. Remaining: Resolve the first fixture footer, italic wrap, centered title, and border extent differences. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Hang collapsible boundary spaces during wrapping and center/right alignment; shrink justified spaces to a metric floor; apply Word's paragraph-border side gutter. Remaining: Regenerate and compare only EP_ZMVZ_MULTI_v4 page 1, then trace residual differences. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Apply the same hanging-space and justified-shrink test when one word spans multiple runs. Remaining: Regenerate and compare only EP_ZMVZ_MULTI_v4 page 1. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Derive underline thickness and offset from font post metrics; merge compatible adjacent underline fills across spaces. Remaining: Restore the cross-run hanging-tail fix and add the opt-in macOS Word grid profile. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Derive justified used width from the full trailing U+0020 chain and exclude that chain from slack distribution. Remaining: Add the opt-in macOS Word 300 dpi compatibility profile and recompare EP_ZMVZ_MULTI_v4 page 1. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
[gstack-context] Decisions: Keep default export twip-exact; expose an opt-in PDF profile backed by a neutral Core grid policy; apply role-aware grid phases to page, furniture, border, text, links, and underline geometry. Remaining: Rebase the branch, run final PR-wide verification, and update the comparison corpus only after EP_ZMVZ_MULTI_v4 remains stable. Skill: investigate [/gstack-context] Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve the remote review commits while keeping the newer local implementation. Co-authored-by: Cursor <cursoragent@cursor.com>
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
exportPdfAPI over Core semantic layouts.Test plan
bun run typecheckbun run lintbun test ./packages/docx-to-pdf/testbun run api:checkopenspec validate docx-to-pdf-export --strictMade with Cursor
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.