0

PDF Capture Button

Capture a region of a page as an image.

"use client";

import { createPluginRegistration } from "@embedpdf/core";

About

<PdfCaptureButton /> arms marquee capture. Dragging a box on the page is what actually produces the image — the button only decides whether that drag captures or scrolls.

The plugin emits a Blob and stops there; it has no opinion about where the image goes. This component fills that in, downloading the capture by default and handing it to you instead when you pass onCapture.

Capture is a heavy-tier plugin, so <PdfViewer /> does not register it for you. The plugin and the marquee layer both have to be added — see Usage.

Installation

pnpm dlx shadcn@latest add pdfcn/capture-button

Usage

Register the capture plugin.

import { createPluginRegistration } from "@embedpdf/core";
import { CapturePluginPackage } from "@embedpdf/plugin-capture/react";
 
// Outside the component — a new array identity on every render tears the
// engine down and rebuilds it, losing scroll and zoom state.
const plugins = [
  createPluginRegistration(CapturePluginPackage, {
    scale: 2,
    imageType: "image/png",
  }),
];

scale is the resolution the region is rendered at, independent of the zoom on screen — 2 captures at twice the page's natural size, which is what keeps a capture legible when it's pasted somewhere else. imageType accepts image/png, image/jpeg, image/webp, or image/bmp, and also decides the downloaded file's extension. See embedpdf's Capture plugin for its full configuration.

Render the marquee layer and the button.

<PdfViewer
  documents={[{ url: "/sample.pdf" }]}
  plugins={plugins}
  className="h-[720px]"
>
  <PdfToolbar>
    <PdfCaptureButton />
  </PdfToolbar>
  <PdfViewerContent
    pageLayers={({ documentId, pageIndex }) => (
      <MarqueeCapture documentId={documentId} pageIndex={pageIndex} />
    )}
  />
</PdfViewer>

Without <MarqueeCapture /> the drag still captures, but nothing draws while the pointer moves — so the user is selecting a region they can't see. Treat the layer as required, not decorative.

Examples

Default

"use client";

import { createPluginRegistration } from "@embedpdf/core";

Arm the button, drag a box over the page, and the capture downloads as a PNG.

Handling the capture yourself

Pass onCapture to replace the download — the image goes wherever you send it instead:

<PdfCaptureButton
  onCapture={async ({ blob, pageIndex, rect }) => {
    const body = new FormData();
    body.append("image", blob, `page-${pageIndex + 1}.png`);
 
    await fetch("/api/captures", { method: "POST", body });
  }}
/>

The event also carries rect (the captured region in page coordinates), documentId, scale, imageType, and withAnnotations, so a capture can be stored alongside where it came from.

Copying to the clipboard is the other common destination:

<PdfCaptureButton
  onCapture={({ blob }) => {
    void navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
  }}
/>

Clipboard writes need a secure context, and Safari only accepts image/png — pick your imageType accordingly.

When copying is disabled

Capturing a region copies content out of the document, so the button reads the active document's copy-contents permission and disables itself — with a tooltip explaining why — when the flag is denied. The demo below forces the flag off with a permissions override; a real document carries it in its own flags. See Permissions for the full model.

"use client";

import { createPluginRegistration } from "@embedpdf/core";

Accessibility

The button is a <Toggle />, so whether capture is armed is exposed through its pressed state rather than colour alone, and it's reachable and operable from the keyboard.

Capturing a region is pointer-only — arming works from the keyboard, but dragging the marquee has no keyboard equivalent.

The default download is a silent side effect: nothing on screen changes when a capture succeeds. If capture is a significant part of your interface, pass onCapture and announce the result yourself.

API Reference

PdfCaptureButton

A <Toggle /> carrying data-slot="pdf-capture-button", bound to whether marquee capture is armed. Accepts every <Toggle /> prop.

PropTypeDefaultDescription
onCapture(event: CaptureAreaEvent) => voidDownloads the image.Called with each captured region.

Returns null when the document isn't loaded or the capture plugin isn't registered.

onCapture is read through a ref, so passing an inline function is safe — it won't resubscribe mid-drag or drop a capture in flight.