react-pdf
Version:
Display PDFs in your React app as easily as if they were images.
97 lines (96 loc) • 4.17 kB
JavaScript
'use client';
import { jsx as _jsx } from "react/jsx-runtime";
import { useCallback, useEffect, useMemo, useRef } from 'react';
import mergeRefs from 'merge-refs';
import invariant from 'tiny-invariant';
import warning from 'warning';
import * as pdfjs from 'pdfjs-dist';
import StructTree from '../StructTree.js';
import usePageContext from '../shared/hooks/usePageContext.js';
import { cancelRunningTask, getDevicePixelRatio, isCancelException, makePageCallback, } from '../shared/utils.js';
const ANNOTATION_MODE = pdfjs.AnnotationMode;
export default function Canvas(props) {
const pageContext = usePageContext();
invariant(pageContext, 'Unable to find Page context.');
const mergedProps = Object.assign(Object.assign({}, pageContext), props);
const { _className, canvasBackground, devicePixelRatio = getDevicePixelRatio(), onRenderError: onRenderErrorProps, onRenderSuccess: onRenderSuccessProps, page, renderForms, renderTextLayer, rotate, scale, } = mergedProps;
const { canvasRef } = props;
invariant(page, 'Attempted to render page canvas, but no page was specified.');
const canvasElement = useRef(null);
/**
* Called when a page is rendered successfully.
*/
function onRenderSuccess() {
if (!page) {
// Impossible, but TypeScript doesn't know that
return;
}
if (onRenderSuccessProps) {
onRenderSuccessProps(makePageCallback(page, scale));
}
}
/**
* Called when a page fails to render.
*/
function onRenderError(error) {
if (isCancelException(error)) {
return;
}
warning(false, error.toString());
if (onRenderErrorProps) {
onRenderErrorProps(error);
}
}
const renderViewport = useMemo(() => page.getViewport({ scale: scale * devicePixelRatio, rotation: rotate }), [devicePixelRatio, page, rotate, scale]);
const viewport = useMemo(() => page.getViewport({ scale, rotation: rotate }), [page, rotate, scale]);
// biome-ignore lint/correctness/useExhaustiveDependencies: Ommitted callbacks so they are not called every time they change
useEffect(function drawPageOnCanvas() {
if (!page) {
return;
}
// Ensures the canvas will be re-rendered from scratch. Otherwise all form data will stay.
page.cleanup();
const { current: canvas } = canvasElement;
if (!canvas) {
return;
}
canvas.width = renderViewport.width;
canvas.height = renderViewport.height;
canvas.style.width = `${Math.floor(viewport.width)}px`;
canvas.style.height = `${Math.floor(viewport.height)}px`;
canvas.style.visibility = 'hidden';
const renderContext = {
annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE,
canvasContext: canvas.getContext('2d', { alpha: false }),
viewport: renderViewport,
};
if (canvasBackground) {
renderContext.background = canvasBackground;
}
const cancellable = page.render(renderContext);
const runningTask = cancellable;
cancellable.promise
.then(() => {
canvas.style.visibility = '';
onRenderSuccess();
})
.catch(onRenderError);
return () => cancelRunningTask(runningTask);
}, [canvasBackground, page, renderForms, renderViewport, viewport]);
const cleanup = useCallback(() => {
const { current: canvas } = canvasElement;
/**
* Zeroing the width and height cause most browsers to release graphics
* resources immediately, which can greatly reduce memory consumption.
*/
if (canvas) {
canvas.width = 0;
canvas.height = 0;
}
}, []);
useEffect(() => cleanup, [cleanup]);
return (_jsx("canvas", { className: `${_className}__canvas`, dir: "ltr", ref: mergeRefs(canvasRef, canvasElement), style: {
display: 'block',
userSelect: 'none',
}, children: renderTextLayer ? _jsx(StructTree, {}) : null }));
}