97 lines
4.1 KiB
JavaScript
97 lines
4.1 KiB
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 }));
|
|
}
|