Use Case: Save-as-Copy Burn-in
Generate a baked PDF with annotations burned into the page contents — interoperable with any PDF reader.
When you need to deliver a PDF that anyone can open and see the annotations (not just users of DocumentViewer), use saveAsCopy(). It returns a fresh PDF binary with annotations baked into the page contents.
Pattern
const buffer = await viewer.value?.saveAsCopy()
const blob = new Blob([buffer], { type: 'application/pdf' })
// Option A: trigger a download
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'reviewed-doc.pdf'
a.click()
URL.revokeObjectURL(url)
// Option B: POST to your backend for archival
await fetch('/api/documents/reviewed', {
method: 'POST',
body: blob,
headers: { 'Content-Type': 'application/pdf' },
})
When to use this vs. exportAnnotations
| Need | Use |
|---|---|
Show the annotated doc to someone who doesn’t have DocumentViewer | saveAsCopy() |
Backup the annotations so the user can later edit them in DocumentViewer | exportAnnotations() (preserves editability) |
| Both (e.g. archive + future re-edit) | Do both — store the JSON for editing, ship the PDF for sharing |
saveAsCopy() burns annotations into the PDF contents in a way that’s spec-compliant for PDF 1.7 — most professional PDF readers (Adobe, Preview, Foxit) will show them. But the original “editable annotation” metadata may be lost, so subsequent rounds of editing should start from the original source + the exportAnnotations() JSON.
Compliance burn-in (Phase 2: redactions)
Today SaveAsCopyOptions exposes includeAnnotations and flatten:
const buffer = await viewer.value?.saveAsCopy({
includeAnnotations: true, // default — bake annotations into the copy
flatten: true, // flatten editable annotations to fixed content
})
When features.redaction ships in Phase 2, saveAsCopy will gain an option to permanently remove redacted content from the PDF (not just cover it with a black box) — the correct compliance workflow. The exact option name is not finalized yet, so don’t rely on a specific flag until redaction lands.
Threads are not embedded
Threads (replies + resolved state) live in the parallel CommentThread[] overlay, not in the PDF binary. If you need to ship both the baked PDF and the thread data:
const [buffer, threads] = await Promise.all([
viewer.value!.saveAsCopy(),
fetchAllThreadsFromAppState(),
])
// Bundle them in a multipart upload, or a zip, or two separate endpoints
Performance
saveAsCopy() re-encodes the PDF — for very large documents (1000+ pages with many annotations), this can take several seconds. Show a loading state during the call.
See also
- Programmatic API → saveAsCopy
- Use case: import / export annotations — the editable-form alternative