Decision: first-page rendering pipeline for pane v2 — pdf.js vs server raster, and Gotenberg's future #629

Closed
opened 2026-09-20 21:05:16 +00:00 by Cordy · 1 comment
Owner

Pane v2 (unified details pane, mockup pane-v2-unified-mockup.html) needs a PDF's first page as pixels. This issue is the decision record for HOW, and for what it means for Gotenberg. Decision pending Nikola.

The two-step truth that frames everything

First-page rendering is two separate problems, and no single option solves both:

  1. Office → PDF (docx/xlsx/pptx/odt need a layout engine). The only realistic engine is LibreOffice, and it is far too heavy to run in-process or client-side. It will ALWAYS be an external service. Today that service is Gotenberg (/api/v1/preview/pdf, #297).
  2. PDF → pixels (page 1 as an image). This is the new gap, and the A/B choice below.

So neither A nor B removes the need for a LibreOffice-engine service for office previews. What CAN change is which service (see "Gotenberg's future" below).

Option A — pdf.js in the client

Vendor Mozilla pdf.js (~1.5 MB, lazy-loaded static asset, separate file so index.html stays single-file). The client fetches the PDF (raw, or Gotenberg's conversion) and renders page 1 to a canvas in the hero; the page box gives the orientation.

  • License/cost: Apache-2.0. Free, forever, no strings.
  • Deployment surface added: zero. For us AND for every customer. A Cairn instance with no converter still gets PDF + image + text heroes; office heroes appear when a converter is configured. This is the elegance argument for others: the feature degrades gracefully instead of adding a deployment prerequisite.
  • Security: PDF parsing is the classic hostile-input RCE surface. Under A it happens inside the browser's JS sandbox, per-user, client-side. The server never parses PDF content at all. This is a genuinely strong property for a product that stores untrusted files.
  • Costs: ~1.5 MB asset (cached after first load); client CPU per render; the client downloads PDF bytes (pdf.js uses HTTP range requests, so for linearized PDFs it fetches only what page 1 needs — but a worst-case non-linearized giant PDF pulls a lot; the >30 MB hero cutoff in the mockup bounds this).
  • Maintenance: vendored asset to bump occasionally. pdf.js is the most battle-tested PDF renderer on earth (it IS Firefox's viewer).

Option B — server-side raster (PDF → PNG endpoint)

Three sub-flavours, with very different price tags:

  • B1 · MuPDF via go-fitz (cgo): technically the nicest renderer. License: AGPL — viral. Linking it into cairnd means Cairn itself must be AGPL, or we buy a commercial license from Artifex (negotiated per-product, typically thousands of USD per year — the exact figure is quote-only). This is the "does B cost money" answer: B1 does, real money, or it open-sources us. Also cgo breaks the pure-Go ko build (no more gcr.io/distroless/static, cross-compile pain for arm64).
  • B2 · pdfium via go-pdfium + wazero (WebAssembly): pdfium is Google's PDF engine, BSD-3, free, and go-pdfium can run it through wazero — no cgo, pure-Go build preserved, and the wasm sandbox contains the hostile-input parsing. This is the only server-side option with no licensing cost and no build damage. Costs: ~10–15 MB wasm blob in the image, meaningful RAM per render (the dogfood pods run at 256Mi limits — rendering big PDFs server-side on Pis competes with serving files), and we own cache invalidation (render once per content version, store beside… where? another state surface).
  • B3 · second container (pdftoppm/Stirling-style sidecar): free, but raises every customer's deployment floor by one more service for a thumbnail. Worst elegance-for-others score.

B's genuine advantages over A: rendered once and cached for ALL users (A renders per client); tiny payloads to slow clients; works for exotic clients with no JS canvas. None of these bite at our scale, and the cache is a new state surface (#138 world) we'd have to design.

Gotenberg's future (the "save space" question)

Gotenberg exists ONLY for step 1 (office→PDF). Two honest observations:

  • For instances that run Collabora anyway (the editing story), Gotenberg is redundant in principle: Collabora Online has a convert-to endpoint (docx→pdf, even →png) backed by the same LibreOffice core. Teaching our converter client a second driver ("Collabora convert-to" next to "Gotenberg") is a small, self-contained change — then a Collabora-equipped deployment needs no Gotenberg at all, and office.url + preview.converterUrl can point at one service. That IS the deployment-size win, and it is orthogonal to A/B.
  • For headless instances (no editor, still want office previews), Gotenberg remains the lighter choice — Collabora's image is much bigger. Keeping the Gotenberg-compatible driver costs nothing; it's ~100 lines that already exist.

So the recommended endgame: A + converter grows a Collabora convert-to driver as a follow-up — customers choose zero converters (PDF/image/text heroes only), Collabora only (editing + office heroes, one service), or Gotenberg (office heroes without an editor). Cairn itself never grows a PDF parser or a licensing bill.

Recommendation

A (pdf.js), with B2 (pdfium/wazero) as the recorded fallback if client-side rendering ever proves inadequate in the beta (e.g. low-power clients, huge non-linearized PDFs beyond the size cutoff mattering in practice). B1 (MuPDF/AGPL) is ruled out on licensing unless we deliberately choose to pay Artifex. The Collabora convert-to driver is filed as its own follow-up either way.

Deciding this closes the last open point on the pane-v2 mockup; slices 1–3 build after.

Pane v2 (unified details pane, mockup `pane-v2-unified-mockup.html`) needs a PDF's first page as pixels. This issue is the decision record for HOW, and for what it means for Gotenberg. Decision pending Nikola. ## The two-step truth that frames everything First-page rendering is **two separate problems**, and no single option solves both: 1. **Office → PDF** (docx/xlsx/pptx/odt need a layout engine). The only realistic engine is LibreOffice, and it is far too heavy to run in-process or client-side. It will ALWAYS be an external service. Today that service is Gotenberg (`/api/v1/preview/pdf`, #297). 2. **PDF → pixels** (page 1 as an image). This is the new gap, and the A/B choice below. So **neither A nor B removes the need for a LibreOffice-engine service for office previews.** What CAN change is *which* service (see "Gotenberg's future" below). ## Option A — pdf.js in the client Vendor Mozilla pdf.js (~1.5 MB, lazy-loaded static asset, separate file so index.html stays single-file). The client fetches the PDF (raw, or Gotenberg's conversion) and renders page 1 to a canvas in the hero; the page box gives the orientation. - **License/cost:** Apache-2.0. Free, forever, no strings. - **Deployment surface added: zero.** For us AND for every customer. A Cairn instance with no converter still gets PDF + image + text heroes; office heroes appear when a converter is configured. This is the elegance argument for *others*: the feature degrades gracefully instead of adding a deployment prerequisite. - **Security:** PDF parsing is the classic hostile-input RCE surface. Under A it happens inside the browser's JS sandbox, per-user, client-side. The server never parses PDF content at all. This is a genuinely strong property for a product that stores untrusted files. - **Costs:** ~1.5 MB asset (cached after first load); client CPU per render; the client downloads PDF bytes (pdf.js uses HTTP range requests, so for linearized PDFs it fetches only what page 1 needs — but a worst-case non-linearized giant PDF pulls a lot; the >30 MB hero cutoff in the mockup bounds this). - **Maintenance:** vendored asset to bump occasionally. pdf.js is the most battle-tested PDF renderer on earth (it IS Firefox's viewer). ## Option B — server-side raster (PDF → PNG endpoint) Three sub-flavours, with very different price tags: - **B1 · MuPDF via go-fitz (cgo):** technically the nicest renderer. **License: AGPL** — viral. Linking it into cairnd means Cairn itself must be AGPL, or we buy a commercial license from Artifex (negotiated per-product, typically thousands of USD per year — the exact figure is quote-only). **This is the "does B cost money" answer: B1 does, real money, or it open-sources us.** Also cgo breaks the pure-Go ko build (no more `gcr.io/distroless/static`, cross-compile pain for arm64). - **B2 · pdfium via go-pdfium + wazero (WebAssembly):** pdfium is Google's PDF engine, **BSD-3, free**, and go-pdfium can run it through wazero — **no cgo, pure-Go build preserved**, and the wasm sandbox contains the hostile-input parsing. This is the only server-side option with no licensing cost and no build damage. Costs: ~10–15 MB wasm blob in the image, meaningful RAM per render (the dogfood pods run at 256Mi limits — rendering big PDFs server-side on Pis competes with serving files), and we own cache invalidation (render once per content version, store beside… where? another state surface). - **B3 · second container** (pdftoppm/Stirling-style sidecar): free, but raises every customer's deployment floor by one more service for a thumbnail. Worst elegance-for-others score. **B's genuine advantages over A:** rendered once and cached for ALL users (A renders per client); tiny payloads to slow clients; works for exotic clients with no JS canvas. None of these bite at our scale, and the cache is a new state surface (#138 world) we'd have to design. ## Gotenberg's future (the "save space" question) Gotenberg exists ONLY for step 1 (office→PDF). Two honest observations: - **For instances that run Collabora anyway** (the editing story), Gotenberg is redundant in principle: Collabora Online has a `convert-to` endpoint (docx→pdf, even →png) backed by the same LibreOffice core. Teaching our converter client a second driver ("Collabora convert-to" next to "Gotenberg") is a small, self-contained change — then a Collabora-equipped deployment needs **no Gotenberg at all**, and `office.url` + `preview.converterUrl` can point at one service. That IS the deployment-size win, and it is orthogonal to A/B. - **For headless instances** (no editor, still want office previews), Gotenberg remains the lighter choice — Collabora's image is much bigger. Keeping the Gotenberg-compatible driver costs nothing; it's ~100 lines that already exist. So the recommended endgame: **A + converter grows a Collabora `convert-to` driver as a follow-up** — customers choose zero converters (PDF/image/text heroes only), Collabora only (editing + office heroes, one service), or Gotenberg (office heroes without an editor). Cairn itself never grows a PDF parser or a licensing bill. ## Recommendation **A (pdf.js), with B2 (pdfium/wazero) as the recorded fallback** if client-side rendering ever proves inadequate in the beta (e.g. low-power clients, huge non-linearized PDFs beyond the size cutoff mattering in practice). B1 (MuPDF/AGPL) is ruled out on licensing unless we deliberately choose to pay Artifex. The Collabora convert-to driver is filed as its own follow-up either way. Deciding this closes the last open point on the pane-v2 mockup; slices 1–3 build after.
Author
Owner

Decided by Nikola (2026-09-20): A — pdf.js in the client, with B2 (pdfium via wazero) as the recorded fallback. B1 (MuPDF/AGPL) ruled out on licensing. Adequacy is testable, not assumed: #630 carries the beta test cases whose structural failure would reopen this and activate B2. The Gotenberg-reduction half is filed as #631 (Collabora convert-to driver). Pane v2 slice 1 builds on this decision. Closing as decided.

**Decided by Nikola (2026-09-20): A — pdf.js in the client, with B2 (pdfium via wazero) as the recorded fallback.** B1 (MuPDF/AGPL) ruled out on licensing. Adequacy is testable, not assumed: #630 carries the beta test cases whose structural failure would reopen this and activate B2. The Gotenberg-reduction half is filed as #631 (Collabora convert-to driver). Pane v2 slice 1 builds on this decision. Closing as decided.
Cordy closed this issue 2026-09-20 21:21:20 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#629
No description provided.