Print, export, and watermarks
Use the current annotations to export a PDF or Excel workbook, prepare an ordinary or password-protected PDF for printing, and control where a document watermark appears.
Use the Watermark demo for the focused interactive example, or the Redaction demo for image-only sensitive-term output.
TIP
Core generates the output; your application decides whether to download it, upload it, or open the system print dialog.
Export an annotated PDF
import { downloadBlob } from '@inklayer-dev/core'
import { buildAnnotatedPdf } from '@inklayer-dev/core/export/pdf'
const annotations = core.annotations.repository.getAll()
const watermark = core.viewer.getWatermark()
const pdfBytes = await buildAnnotatedPdf(sourceBytes, annotations, {
strategy: 'strict',
annotationTypes: core.annotationTypes,
...(watermark === null ? {} : { watermark })
})
downloadBlob({
content: pdfBytes,
filename: 'review.pdf',
mimeType: 'application/pdf'
})sourceBytes must contain the original PDF bytes, which the application retains or retrieves when needed. The exporter writes the current annotations into a new PDF while preserving the original document's vector content. Pass core.annotationTypes so custom annotation types can also be exported.
External PDF applications show the annotation /T title. Core formats it as Author · #N when a referenceNumber is available, while /NM keeps the stable annotation ID used for relationships. Override only the visible title when your product needs another convention:
const pdfBytes = await buildAnnotatedPdf(sourceBytes, annotations, {
annotationTitle: annotation => `Review ${annotation.referenceNumber ?? '—'}`
})InkLayer exports also retain the original author, reference number, and structured references as round-trip metadata. Changing annotationTitle does not change permissions or reference targets.
buildAnnotatedPdf() only returns bytes. Downloading, naming, or uploading the result is a separate application decision. The default strict strategy rejects an invalid or unsupported annotation; lenient skips that entry and reports it through the optional onWarning callback. Password-protected source bytes cannot be exported by this client-side vector path.
Print an ordinary PDF
Use buildPrintablePdf() for an unencrypted PDF when you want to preserve text, links, forms, and vector content:
import { printPdfBlob } from '@inklayer-dev/core'
import { buildPrintablePdf } from '@inklayer-dev/core/export/pdf'
const watermark = core.viewer.getWatermark()
const printable = await buildPrintablePdf(
sourceBytes,
core.annotations.repository.getAll(),
{
annotationTypes: core.annotationTypes,
...(watermark === null ? {} : { watermark })
}
)
await printPdfBlob({ content: printable })buildPrintablePdf() applies the watermark's print target, while buildAnnotatedPdf() applies its export target. printPdfBlob() receives an object containing the generated bytes and owns the temporary iframe and object URL used to open the browser's print dialog.
Print a password-protected PDF
After PDF.js has opened a password-protected document, generate a temporary rasterized PDF from the current Viewer:
import { buildSecureRasterPrintPdf, printPdfBlob } from '@inklayer-dev/core'
const printable = await buildSecureRasterPrintPdf({
viewer: core.viewer,
annotations: core.annotations,
pixelRatio: 2,
onProgress: (completed, total) => updateProgress(completed, total)
})
await printPdfBlob({ content: printable })This browser-only path includes the current annotations and print watermark. It rejects documents that disallow printing and limits the pixel ratio to 1 when the document permits low-resolution printing only.
The resulting PDF is temporary, unencrypted, and made entirely of page images. Selectable text, links, forms, and vector detail are flattened. Use it only to open the print dialog; do not offer it as a replacement download or export for the protected document. A protected vector export requires a trusted backend that can decrypt, process, and re-encrypt the document.
Export a secure redacted PDF
Use reviewed source ranges rather than annotation rectangles:
import { buildSecureRedactedPdf, downloadBlob } from '@inklayer-dev/core'
const matches = highlighter.getSnapshot().matches
.filter(match => match.reviewState === 'included')
const redacted = await buildSecureRedactedPdf({
viewer: core.viewer,
ranges: matches.map(match => match.range),
pixelRatio: 2,
margin: 1
})
downloadBlob({
content: redacted,
filename: 'review-redacted.pdf',
mimeType: 'application/pdf'
})Core first rasterizes each page, then paints opaque black boxes over resolved text geometry. The new PDF embeds only page images and never embeds the source bytes, source text, keyword rules, or match metadata. Consequently, all page text—not only covered text—loses selection, search, copy, and accessibility.
The on-screen Highlighter preview may keep each rule's ordinary translucent color so users can review the underlying text. Preview color does not affect output: printing and buildSecureRedactedPdf() always paint accepted ranges as opaque black boxes.
Do not substitute a black Rectangle annotation or an opaque PDF drawing command. Those approaches can leave the original text in the content stream. Core rejects empty ranges and documents that prohibit raster output. It cannot automatically detect sensitive pixels inside images, modify the original file, or erase copies stored elsewhere. Start with Create your first keyword redaction, then use Secure keyword redaction for the production workflow and complete security boundary.
Configure a watermark
For a focused setup guide, start with Document watermarks.
import type { PdfWatermarkSpec } from '@inklayer-dev/core'
const watermark: PdfWatermarkSpec = {
text: `${currentUser.name} · ${documentId}`,
layout: 'repeated',
opacity: 0.12,
rotation: -28,
targets: {
viewer: true,
print: true,
export: true,
thumbnails: false
}
}
core.viewer.setWatermark(watermark)Configure the watermark before loading the document so it appears when Page Flow first renders the pages. viewer, print, and export independently control the Viewer, printed PDF, and exported PDF. By default, Viewer and print watermarks are enabled, while export and thumbnail watermarks are disabled.
targets.thumbnails is part of the watermark configuration, but the current renderThumbnail() implementation does not apply watermarks. Setting this flag to true does not currently watermark generated thumbnails.
For Chinese or other characters unsupported by the default PDF font, provide TrueType or OpenType font bytes when generating a vector PDF:
const response = await fetch('/fonts/NotoSansSC-Regular.ttf')
const watermarkFontBytes = new Uint8Array(await response.arrayBuffer())
const pdfBytes = await buildAnnotatedPdf(sourceBytes, annotations, {
watermark,
watermarkFontBytes,
watermarkFontSubset: false,
annotationTypes: core.annotationTypes
})The same watermarkFontBytes option is supported by buildPrintablePdf(). Custom fonts are subset by default. Use watermarkFontSubset: false only when the selected font cannot be subset reliably; embedding the complete font can substantially increase the output size. Viewer and raster-print watermarks are drawn by the browser and use its available fonts instead. Watermarks discourage casual redistribution, but they are not tamper-resistant access control; enforce sensitive-document policy on a trusted backend as well.
Export annotations to Excel
import { downloadBlob } from '@inklayer-dev/core'
import { buildAnnotationWorkbook } from '@inklayer-dev/core/export/excel'
const workbook = await buildAnnotationWorkbook(
core.annotations.repository.getAll()
)
downloadBlob({
content: workbook,
filename: 'annotations.xlsx',
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})The workbook contains separate annotation and comment sheets. Worksheet names and column labels can be localized through buildAnnotationWorkbook() options; annotation types, review statuses, and reference identifiers retain their original values.
printPdfBlob() and downloadBlob() are browser-specific helpers. For Electron, mobile WebViews, server rendering, or custom file services, provide your own print or download implementation through createPrintCapability() or createDownloadCapability().