Skip to content

feat(docs): generate PDF previews in the browser instead of committing them - #16

Open
avdoseferovic wants to merge 1 commit into
mainfrom
feature/wasm-pdf-previews
Open

feat(docs): generate PDF previews in the browser instead of committing them#16
avdoseferovic wants to merge 1 commit into
mainfrom
feature/wasm-pdf-previews

Conversation

@avdoseferovic

Copy link
Copy Markdown
Owner

Description

Feature and example pages now render their PDF preview client-side from paper.wasm, using the same GetPaper builder the page displays as its code sample — so a preview can no longer drift from the code beside it. 29 of the 35 committed PDFs leave the repository.

How it works

Each directory under docs/assets/examples/ is split into paper.go (the existing GetPaper builder) plus a thin cmd/main.go that does the file I/O, which makes the builders importable. A registry maps 29 example names to a builder and the images it reads, and the wasm binary gains paperGenerateExample(name) and paperExampleAssets(name).

The tricky part is that wasm has no filesystem, and eight examples load images via image.NewFromFileCol. Rather than rewrite those examples to a byte-based API — which would have made the documented code less idiomatic than what users should actually write — docs/assets/js/paper-fs.js provides a read-only in-memory globalThis.fs. wasm_exec.js only installs its own stub when globalThis.fs is absent, so defining ours first means the library's ordinary os.ReadFile just works. Neither the library nor the example code changed how it loads images.

Tricky details worth knowing

  • syscall.Open always follows open with fstat + isDirectory(), and setStat calls .Int() on 13 numeric fields — .Int() on undefined panics and takes the whole instance down. read must keep a per-fd cursor (position === null advances it, a numeric position must not) and report EOF as 0 bytes, or os.ReadFile spins forever.
  • A failed image read is not a generation error: the provider records an image.load issue, draws an error box, and still returns a valid %PDF-. So a "did a PDF come out?" check passes with the shim completely broken. ExampleToBase64 fails on any recorded image.load issue, and that is what makes the tests meaningful.
  • compression.GetPaper called os.Exit(1) on a missing image. os.Exit bypasses recover(), so under wasm it would have killed every export on the page. It returns an error now — the one example whose signature changed.
  • paper-preview.js replaces docsify-pdf-embed-plugin + PDFObject, which round-tripped embeds through localStorage, required $docsify.executeScript, and built absolute URLs from location.hostname alone (dropping the /paper/ project-pages prefix). The wasm loads lazily on the first preview and is cached for the whole SPA session.
  • Every preview ships an unconditional download link: iOS Safari and Android Chrome render a blob <embed> as a blank box and raise no error, so there is nothing to catch and fall back from.

Cost, stated plainly

The module is ~19.6MB raw / 4.7MB gzipped. A visitor's first preview costs that, where it previously cost 4–36KB for a static PDF. It is cached for the session and pages without a preview never fetch it (verified), but the real win here is repository history, not visitor bandwidth.

What stays committed

background, customfont, disablepagebreak, mergepdf and showcase keep static embeds because their inputs are impractical to ship (792KB PNG, 23MB TTF, an existing PDF). paper.pdf stays because mergepdf reads it as input. make examples still generates everything locally.

Evidence

  • The 31 pre-existing golden structure fixtures are byte-identical — the proof that repackaging 33 directories altered no output.
  • 29 examples generate real PDFs in host tests, each gated on no image.load issue.
  • paper-fs.test.mjs (11 tests, wired into make test and CI) pins the syscall contract. Mutation-tested: dropping a stat field and ignoring the read cursor each fail the suite.
  • Browser-verified against a /paper/-prefixed replica of the Pages artifact: previews render (including image-dependent ones), static fixtures still embed, preview-free pages fetch zero wasm, and SPA navigation across four routes instantiates it exactly once.

Drive-by fix: make site deleted every *.go while pages.yml deliberately keeps them, so the locally served site had been rendering empty code samples. It now removes only *_test.go.

Related Issue

Checklist

check with "x", ONLY IF APPLIED to your change

  • All methods associated with structs has func (<first letter of struct> *struct) method() {} name style.
  • Wrote unit tests for new/changed features.
  • Followed the unit test when,should naming pattern.
  • All mocks created with m := mocks.NewConstructor(t).
  • All mocks using m.EXPECT().MethodName() method to mock methods.
  • Updated docs/*
  • Updated example_test.go.
  • Updated README.md
  • New public methods/structs/interfaces has comments upside them explaining they responsibilities
  • Executed make dod with no issues

🤖 Generated with Claude Code

…g them

Feature and example pages now render their PDF preview client-side from
paper.wasm, using the very same GetPaper builder the page displays as its code
sample, so a preview can no longer drift from the code beside it. 29 of the 35
committed PDFs are gone from the repository.

Examples become importable
- Each directory under docs/assets/examples/ is split into paper.go (the
  existing GetPaper builder) and a thin cmd/main.go that does the file I/O.
  unittests is unchanged.
- bookmark and watermark gain builders and structure tests.
- compression.GetPaper returned by calling os.Exit(1) on a missing image.
  os.Exit bypasses recover(), so under wasm it would have killed every export
  on the page. It returns an error now.
- The 31 pre-existing golden structure fixtures are byte-identical, which is
  the evidence that repackaging altered no output.

Rendering in the browser
- exampleregistry maps 29 names to a builder and the images it reads.
- paperGenerateExample / paperExampleAssets join the existing wasm exports.
- paper-fs.js provides a read-only in-memory globalThis.fs so the library's
  ordinary os.ReadFile works, leaving both library and example code untouched.
  It is pinned by paper-fs.test.mjs, run from make test and CI, because a
  break there draws an error box into a valid PDF rather than failing.
- ExampleToBase64 treats an unreadable asset as an error for the same reason.
- paper-preview.js replaces docsify-pdf-embed-plugin and PDFObject, which
  needed localStorage, executeScript, and dropped the project-pages prefix.
  The wasm loads lazily on the first preview and is reused for the session:
  pages without a preview never fetch it.

What stays committed
- background, customfont, disablepagebreak, mergepdf and showcase keep static
  embeds because their inputs are impractical to ship to a browser, and
  paper.pdf remains because mergepdf reads it. make examples still generates
  everything locally.

Also fixes make site deleting every *.go, which had been quietly emptying the
code samples on the locally served site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant