Skip to content

LensPDFViewer (one-line viewer)

The default composition. Drop it into any React 19 project and you have a working PDF viewer — no host-context wiring, no per-page mounting, no worrying about pdf.js workers.

import { LensPDFViewer } from "@printwithsynergy/lens-pdf";
export function MyViewer() {
return <LensPDFViewer pdfUrl="https://example.com/file.pdf" />;
}
  • Builds a pdf.js fallback adapter from pdfUrl and configures the worker URL.
  • Wraps ViewerHostContext and (optionally) ViewerServicesContext.
  • Auto-discovers page count, page dimensions, and OCG layers from the PDF.
  • Renders every page in a scrollable, lazy-loaded virtual list (or one at a time with mode="single").
  • Seeds initial layer state from the PDF’s defaults (default-on layers enabled, default-off off).
  • Ships a responsive default toolbar with zoom, layers, color picker, and measure tool.
  • Reflows to a bottom-drawer layout under 768 px wide.
PropTypeDefaultNotes
pdfUrlstringrequiredPDF URL fetched by the user’s browser. Sign / scope upstream — see Security.
workerSrcstringdefaultPdfWorkerSrc (unpkg, pinned to bundled pdfjs-dist version)Override to self-host the pdf.js worker.
servicesViewerServices(unset)Pass when your host has a backend with separations / densitometer / TAC / annotations / reports. Components for unwired services hide silently.
tokensThemeTokensdefaultThemeTokensBrand palette.
classNamestring""Class hook on the outer shell.
mode"scroll" | "single""scroll"Page rendering mode.
toolsReadonlyArray<"zoom"|"layers"|"color-picker"|"measure">all fourToolbar contents. Pass [] for no toolbar.
initialZoomnumber100Starting zoom percent.
brandstring(none)Optional brand label rendered in the top-left of the toolbar.
header(state: LensPDFViewerState) => ReactNode(built-in toolbar)Replace the default toolbar. Receives viewer state.
sidebar(state: LensPDFViewerState) => ReactNode(none)Inject a sidebar next to the page list.
footerReactNode(none)Static footer content rendered below the viewer stage.

What gets hidden when no services are wired

Section titled “What gets hidden when no services are wired”

The composition relies on the host-agnostic capability-detection contract from fallback.md. With only pdfUrl set:

ComponentVisibleNotes
Page rendering, navigation, zoompdf.js fallback.
Layer panel✅ if PDF has OCGspdf.js fallback.
Color pickerRGB only (no TAC).
Measure toolPage dimensions from PDF.
Separations❌ hiddenNeeds a backend; pdf.js renders RGB only.
Densitometer❌ hiddenSame.
TAC heatmap❌ hiddenSame.
Annotations❌ hiddenNeeds persistence.
Report links❌ hiddenNeeds a backend.

Pass services to wire the backend-dependent ones. The reference server in server/ is a turnkey option — see server.md.

Slot props let you replace individual regions without reimplementing the entire viewer. Each slot accepts a static ReactNode or a render function that receives the current LensPDFViewerState:

import { LensPDFViewer } from "@printwithsynergy/lens-pdf/components";
<LensPDFViewer
pdfUrl={url}
header={(state) => (
<div>
<span>Page {state.currentPage}</span>
<button onClick={() => state.setZoom(state.zoom + 25)}>Zoom in</button>
</div>
)}
footer={<p>Powered by LensPDF</p>}
/>

LensPDFViewerState exposes: zoom, setZoom, currentPage, setCurrentPage, pageCount, activeTool, setActiveTool, enabledLayers, toggleLayer, setAllLayers, hasLayers, layersOpen, setLayersOpen.

<LensPDFViewer> is purely additive. The lower-level surface stays exactly as before — for bespoke layouts compose PageCanvas, LayerPanel, MeasureTool, ColorPickerTool, etc. with your own context providers. See components.md for the per- component reference.

  • Less boilerplate<LensPDFDemo> bakes in upload, URL paste, drag-drop, and validation. ~5 lines.
  • More controluseLensPDF() + <LensPDFProvider> manage state while you build the layout.
  • Full custom — wire ViewerHostContext + ViewerServicesContext yourself. See architecture.md.

The pdfUrl you pass is fetched verbatim by the user’s browser. If a user shouldn’t be able to read that PDF, the host must enforce that upstream — sign the URL, scope it, expire it. The viewer is a pure renderer and doesn’t authenticate anything.

The pdf.js worker is loaded from unpkg by default. For deployments that can’t reach unpkg (intranet, air-gapped CI, strict CSPs), set workerSrc to a self-hosted URL or import the worker into your app’s build and pass its bundler-resolved URL.