Numerous Bug Fixes
Some checks failed
Automated Container Build / build-and-push (push) Failing after 8s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 8s
This commit is contained in:
parent
02eefdac0e
commit
267d429122
959 changed files with 145571 additions and 221 deletions
655
frontend/node_modules/react-pdf/src/Document.tsx
generated
vendored
Normal file
655
frontend/node_modules/react-pdf/src/Document.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,655 @@
|
|||
'use client';
|
||||
|
||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { dequal } from 'dequal';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import makeEventProps from 'make-event-props';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import DocumentContext from './DocumentContext.js';
|
||||
import LinkService from './LinkService.js';
|
||||
import Message from './Message.js';
|
||||
import PasswordResponses from './PasswordResponses.js';
|
||||
|
||||
import useResolver from './shared/hooks/useResolver.js';
|
||||
|
||||
import {
|
||||
cancelRunningTask,
|
||||
dataURItoByteString,
|
||||
displayCORSWarning,
|
||||
isArrayBuffer,
|
||||
isBlob,
|
||||
isBrowser,
|
||||
isDataURI,
|
||||
loadFromFile,
|
||||
} from './shared/utils.js';
|
||||
|
||||
import type { EventProps } from 'make-event-props';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api.js';
|
||||
import type {
|
||||
ClassName,
|
||||
DocumentCallback,
|
||||
DocumentContextType,
|
||||
DocumentRenderProps,
|
||||
ExternalLinkRel,
|
||||
ExternalLinkTarget,
|
||||
File,
|
||||
ImageResourcesPath,
|
||||
NodeOrRenderer,
|
||||
OnDocumentLoadError,
|
||||
OnDocumentLoadProgress,
|
||||
OnDocumentLoadSuccess,
|
||||
OnError,
|
||||
OnItemClickArgs,
|
||||
OnPasswordCallback,
|
||||
Options,
|
||||
PasswordResponse,
|
||||
RenderMode,
|
||||
ScrollPageIntoViewArgs,
|
||||
Source,
|
||||
} from './shared/types.js';
|
||||
|
||||
const { PDFDataRangeTransport } = pdfjs;
|
||||
|
||||
type OnItemClick = (args: OnItemClickArgs) => void;
|
||||
|
||||
type OnPassword = (callback: OnPasswordCallback, reason: PasswordResponse) => void;
|
||||
|
||||
type OnSourceError = OnError;
|
||||
|
||||
type OnSourceSuccess = () => void;
|
||||
|
||||
export type DocumentProps = {
|
||||
children?: React.ReactNode | ((props: DocumentRenderProps) => React.ReactNode);
|
||||
/**
|
||||
* Class name(s) that will be added to rendered element along with the default `react-pdf__Document`.
|
||||
*
|
||||
* @example 'custom-class-name-1 custom-class-name-2'
|
||||
* @example ['custom-class-name-1', 'custom-class-name-2']
|
||||
*/
|
||||
className?: ClassName;
|
||||
/**
|
||||
* What the component should display in case of an error.
|
||||
*
|
||||
* @default 'Failed to load PDF file.'
|
||||
* @example 'An error occurred!'
|
||||
* @example <p>An error occurred!</p>
|
||||
* @example {this.renderError}
|
||||
*/
|
||||
error?: NodeOrRenderer;
|
||||
/**
|
||||
* Link rel for links rendered in annotations.
|
||||
*
|
||||
* @default 'noopener noreferrer nofollow'
|
||||
*/
|
||||
externalLinkRel?: ExternalLinkRel;
|
||||
/**
|
||||
* Link target for external links rendered in annotations.
|
||||
*/
|
||||
externalLinkTarget?: ExternalLinkTarget;
|
||||
/**
|
||||
* What PDF should be displayed.
|
||||
*
|
||||
* Its value can be an URL, a file (imported using `import … from …` or from file input form element), or an object with parameters (`url` - URL; `data` - data, preferably Uint8Array; `range` - PDFDataRangeTransport.
|
||||
*
|
||||
* **Warning**: Since equality check (`===`) is used to determine if `file` object has changed, it must be memoized by setting it in component's state, `useMemo` or other similar technique.
|
||||
*
|
||||
* @example 'https://example.com/sample.pdf'
|
||||
* @example importedPdf
|
||||
* @example { url: 'https://example.com/sample.pdf' }
|
||||
*/
|
||||
file?: File;
|
||||
/**
|
||||
* The path used to prefix the src attributes of annotation SVGs.
|
||||
*
|
||||
* @default ''
|
||||
* @example '/public/images/'
|
||||
*/
|
||||
imageResourcesPath?: ImageResourcesPath;
|
||||
/**
|
||||
* A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to main `<div>` rendered by `<Document>` component.
|
||||
*
|
||||
* @example (ref) => { this.myDocument = ref; }
|
||||
* @example this.ref
|
||||
* @example ref
|
||||
*/
|
||||
inputRef?: React.Ref<HTMLDivElement | null>;
|
||||
/**
|
||||
* What the component should display while loading.
|
||||
*
|
||||
* @default 'Loading PDF…'
|
||||
* @example 'Please wait!'
|
||||
* @example <p>Please wait!</p>
|
||||
* @example {this.renderLoader}
|
||||
*/
|
||||
loading?: NodeOrRenderer;
|
||||
/**
|
||||
* What the component should display in case of no data.
|
||||
*
|
||||
* @default 'No PDF file specified.'
|
||||
* @example 'Please select a file.'
|
||||
* @example <p>Please select a file.</p>
|
||||
* @example {this.renderNoData}
|
||||
*/
|
||||
noData?: NodeOrRenderer;
|
||||
/**
|
||||
* Function called when an outline item or a thumbnail has been clicked. Usually, you would like to use this callback to move the user wherever they requested to.
|
||||
*
|
||||
* @example ({ dest, pageIndex, pageNumber }) => alert('Clicked an item from page ' + pageNumber + '!')
|
||||
*/
|
||||
onItemClick?: OnItemClick;
|
||||
/**
|
||||
* Function called in case of an error while loading a document.
|
||||
*
|
||||
* @example (error) => alert('Error while loading document! ' + error.message)
|
||||
*/
|
||||
onLoadError?: OnDocumentLoadError;
|
||||
/**
|
||||
* Function called, potentially multiple times, as the loading progresses.
|
||||
*
|
||||
* @example ({ loaded, total }) => alert('Loading a document: ' + (loaded / total) * 100 + '%')
|
||||
*/
|
||||
onLoadProgress?: OnDocumentLoadProgress;
|
||||
/**
|
||||
* Function called when the document is successfully loaded.
|
||||
*
|
||||
* @example (pdf) => alert('Loaded a file with ' + pdf.numPages + ' pages!')
|
||||
*/
|
||||
onLoadSuccess?: OnDocumentLoadSuccess;
|
||||
/**
|
||||
* Function called when a password-protected PDF is loaded.
|
||||
*
|
||||
* @example (callback) => callback('s3cr3t_p4ssw0rd')
|
||||
*/
|
||||
onPassword?: OnPassword;
|
||||
/**
|
||||
* Function called in case of an error while retrieving document source from `file` prop.
|
||||
*
|
||||
* @example (error) => alert('Error while retrieving document source! ' + error.message)
|
||||
*/
|
||||
onSourceError?: OnSourceError;
|
||||
/**
|
||||
* Function called when document source is successfully retrieved from `file` prop.
|
||||
*
|
||||
* @example () => alert('Document source retrieved!')
|
||||
*/
|
||||
onSourceSuccess?: OnSourceSuccess;
|
||||
/**
|
||||
* An object in which additional parameters to be passed to PDF.js can be defined. Most notably:
|
||||
* - `cMapUrl`;
|
||||
* - `httpHeaders` - custom request headers, e.g. for authorization);
|
||||
* - `wasmUrl`;
|
||||
* - `withCredentials` - a boolean to indicate whether or not to include cookies in the request (defaults to `false`)
|
||||
*
|
||||
* For a full list of possible parameters, check [PDF.js documentation on DocumentInitParameters](https://mozilla.github.io/pdf.js/api/draft/module-pdfjsLib.html#~DocumentInitParameters).
|
||||
*
|
||||
* **Note**: Make sure to define options object outside of your React component or use `useMemo` if you can't.
|
||||
*
|
||||
* @example { cMapUrl: '/cmaps/', wasmUrl: '/wasm/' }
|
||||
*/
|
||||
options?: Options;
|
||||
/**
|
||||
* Rendering mode of the document. Can be `"canvas"`, `"custom"` or `"none"``. If set to `"custom"`, `customRenderer` must also be provided.
|
||||
*
|
||||
* @default 'canvas'
|
||||
* @example 'custom'
|
||||
*/
|
||||
renderMode?: RenderMode;
|
||||
/**
|
||||
* Rotation of the document in degrees. If provided, will change rotation globally, even for the pages which were given `rotate` prop of their own. `90` = rotated to the right, `180` = upside down, `270` = rotated to the left.
|
||||
*
|
||||
* @example 90
|
||||
*/
|
||||
rotate?: number | null;
|
||||
/**
|
||||
* Document scale.
|
||||
*
|
||||
* @default 1
|
||||
* @example 0.5
|
||||
*/
|
||||
scale?: number;
|
||||
} & EventProps<DocumentCallback | false | undefined>;
|
||||
|
||||
const defaultOnPassword: OnPassword = (callback, reason) => {
|
||||
switch (reason) {
|
||||
case PasswordResponses.NEED_PASSWORD: {
|
||||
const password = prompt('Enter the password to open this PDF file.');
|
||||
callback(password);
|
||||
break;
|
||||
}
|
||||
case PasswordResponses.INCORRECT_PASSWORD: {
|
||||
const password = prompt('Invalid password. Please try again.');
|
||||
callback(password);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
}
|
||||
};
|
||||
|
||||
function isParameterObject(file: File): file is Source {
|
||||
return (
|
||||
typeof file === 'object' &&
|
||||
file !== null &&
|
||||
('data' in file || 'range' in file || 'url' in file)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a document passed using `file` prop.
|
||||
*/
|
||||
const Document: React.ForwardRefExoticComponent<
|
||||
DocumentProps &
|
||||
React.RefAttributes<{
|
||||
linkService: React.RefObject<LinkService>;
|
||||
pages: React.RefObject<HTMLDivElement[]>;
|
||||
viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>;
|
||||
}>
|
||||
> = forwardRef(function Document(
|
||||
{
|
||||
children,
|
||||
className,
|
||||
error = 'Failed to load PDF file.',
|
||||
externalLinkRel,
|
||||
externalLinkTarget,
|
||||
file,
|
||||
inputRef,
|
||||
imageResourcesPath,
|
||||
loading = 'Loading PDF…',
|
||||
noData = 'No PDF file specified.',
|
||||
onItemClick,
|
||||
onLoadError: onLoadErrorProps,
|
||||
onLoadProgress,
|
||||
onLoadSuccess: onLoadSuccessProps,
|
||||
onPassword = defaultOnPassword,
|
||||
onSourceError: onSourceErrorProps,
|
||||
onSourceSuccess: onSourceSuccessProps,
|
||||
options,
|
||||
renderMode,
|
||||
rotate,
|
||||
scale,
|
||||
...otherProps
|
||||
},
|
||||
ref,
|
||||
) {
|
||||
const [sourceState, sourceDispatch] = useResolver<Source | null>();
|
||||
const { value: source, error: sourceError } = sourceState;
|
||||
const [pdfState, pdfDispatch] = useResolver<PDFDocumentProxy>();
|
||||
const { value: pdf, error: pdfError } = pdfState;
|
||||
|
||||
const linkService = useRef(new LinkService());
|
||||
|
||||
const pages = useRef<HTMLDivElement[]>([]);
|
||||
|
||||
const prevFile = useRef<File | undefined>(undefined);
|
||||
const prevOptions = useRef<Options | undefined>(undefined);
|
||||
|
||||
if (file && file !== prevFile.current && isParameterObject(file)) {
|
||||
warning(
|
||||
!dequal(file, prevFile.current),
|
||||
`File prop passed to <Document /> changed, but it's equal to previous one. This might result in unnecessary reloads. Consider memoizing the value passed to "file" prop.`,
|
||||
);
|
||||
|
||||
prevFile.current = file;
|
||||
}
|
||||
|
||||
// Detect non-memoized changes in options prop
|
||||
if (options && options !== prevOptions.current) {
|
||||
warning(
|
||||
!dequal(options, prevOptions.current),
|
||||
`Options prop passed to <Document /> changed, but it's equal to previous one. This might result in unnecessary reloads. Consider memoizing the value passed to "options" prop.`,
|
||||
);
|
||||
|
||||
prevOptions.current = options;
|
||||
}
|
||||
|
||||
const viewer = useRef({
|
||||
// Handling jumping to internal links target
|
||||
scrollPageIntoView: (args: ScrollPageIntoViewArgs) => {
|
||||
const { dest, pageNumber, pageIndex = pageNumber - 1 } = args;
|
||||
|
||||
// First, check if custom handling of onItemClick was provided
|
||||
if (onItemClick) {
|
||||
onItemClick({ dest, pageIndex, pageNumber });
|
||||
return;
|
||||
}
|
||||
|
||||
// If not, try to look for target page within the <Document>.
|
||||
const page = pages.current[pageIndex];
|
||||
|
||||
if (page) {
|
||||
// Scroll to the page automatically
|
||||
page.scrollIntoView();
|
||||
return;
|
||||
}
|
||||
|
||||
warning(
|
||||
false,
|
||||
`An internal link leading to page ${pageNumber} was clicked, but neither <Document> was provided with onItemClick nor it was able to find the page within itself. Either provide onItemClick to <Document> and handle navigating by yourself or ensure that all pages are rendered within <Document>.`,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
linkService,
|
||||
pages,
|
||||
viewer,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* Called when a document source is resolved correctly
|
||||
*/
|
||||
function onSourceSuccess() {
|
||||
if (onSourceSuccessProps) {
|
||||
onSourceSuccessProps();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a document source failed to be resolved correctly
|
||||
*/
|
||||
function onSourceError() {
|
||||
if (!sourceError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, sourceError.toString());
|
||||
|
||||
if (onSourceErrorProps) {
|
||||
onSourceErrorProps(sourceError);
|
||||
}
|
||||
}
|
||||
|
||||
function resetSource() {
|
||||
sourceDispatch({ type: 'RESET' });
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: See https://github.com/biomejs/biome/issues/3080
|
||||
useEffect(resetSource, [file, sourceDispatch]);
|
||||
|
||||
const findDocumentSource = useCallback(async (): Promise<Source | null> => {
|
||||
if (!file) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// File is a string
|
||||
if (typeof file === 'string') {
|
||||
if (isDataURI(file)) {
|
||||
const fileByteString = dataURItoByteString(file);
|
||||
return { data: fileByteString };
|
||||
}
|
||||
|
||||
displayCORSWarning();
|
||||
return { url: file };
|
||||
}
|
||||
|
||||
// File is PDFDataRangeTransport
|
||||
if (file instanceof PDFDataRangeTransport) {
|
||||
return { range: file };
|
||||
}
|
||||
|
||||
// File is an ArrayBuffer
|
||||
if (isArrayBuffer(file)) {
|
||||
return { data: file };
|
||||
}
|
||||
|
||||
/**
|
||||
* The cases below are browser-only.
|
||||
* If you're running on a non-browser environment, these cases will be of no use.
|
||||
*/
|
||||
if (isBrowser) {
|
||||
// File is a Blob
|
||||
if (isBlob(file)) {
|
||||
const data = await loadFromFile(file);
|
||||
|
||||
return { data };
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, file must be an object
|
||||
invariant(
|
||||
typeof file === 'object',
|
||||
'Invalid parameter in file, need either Uint8Array, string or a parameter object',
|
||||
);
|
||||
|
||||
invariant(
|
||||
isParameterObject(file),
|
||||
'Invalid parameter object: need either .data, .range or .url',
|
||||
);
|
||||
|
||||
// File .url is a string
|
||||
if ('url' in file && typeof file.url === 'string') {
|
||||
if (isDataURI(file.url)) {
|
||||
const { url, ...otherParams } = file;
|
||||
const fileByteString = dataURItoByteString(url);
|
||||
return { data: fileByteString, ...otherParams };
|
||||
}
|
||||
|
||||
displayCORSWarning();
|
||||
}
|
||||
|
||||
return file;
|
||||
}, [file]);
|
||||
|
||||
useEffect(() => {
|
||||
const cancellable = makeCancellable(findDocumentSource());
|
||||
|
||||
cancellable.promise
|
||||
.then((nextSource) => {
|
||||
sourceDispatch({ type: 'RESOLVE', value: nextSource });
|
||||
})
|
||||
.catch((error) => {
|
||||
sourceDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelRunningTask(cancellable);
|
||||
};
|
||||
}, [findDocumentSource, sourceDispatch]);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (typeof source === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (source === false) {
|
||||
onSourceError();
|
||||
return;
|
||||
}
|
||||
|
||||
onSourceSuccess();
|
||||
}, [source]);
|
||||
|
||||
/**
|
||||
* Called when a document is read successfully
|
||||
*/
|
||||
function onLoadSuccess() {
|
||||
if (!pdf) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
if (onLoadSuccessProps) {
|
||||
onLoadSuccessProps(pdf);
|
||||
}
|
||||
|
||||
pages.current = new Array(pdf.numPages);
|
||||
linkService.current.setDocument(pdf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a document failed to read successfully
|
||||
*/
|
||||
function onLoadError() {
|
||||
if (!pdfError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, pdfError.toString());
|
||||
|
||||
if (onLoadErrorProps) {
|
||||
onLoadErrorProps(pdfError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on source change
|
||||
useEffect(
|
||||
function resetDocument() {
|
||||
pdfDispatch({ type: 'RESET' });
|
||||
},
|
||||
[pdfDispatch, source],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(
|
||||
function loadDocument() {
|
||||
if (!source) {
|
||||
return;
|
||||
}
|
||||
|
||||
const documentInitParams: DocumentInitParameters = options
|
||||
? { ...source, ...options }
|
||||
: source;
|
||||
|
||||
const destroyable = pdfjs.getDocument(documentInitParams);
|
||||
if (onLoadProgress) {
|
||||
destroyable.onProgress = onLoadProgress;
|
||||
}
|
||||
if (onPassword) {
|
||||
destroyable.onPassword = onPassword;
|
||||
}
|
||||
const loadingTask = destroyable;
|
||||
|
||||
loadingTask.promise
|
||||
.then((nextPdf) => {
|
||||
if (loadingTask.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
pdfDispatch({ type: 'RESOLVE', value: nextPdf });
|
||||
})
|
||||
.catch((error) => {
|
||||
if (loadingTask.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
pdfDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => {
|
||||
loadingTask.destroy();
|
||||
};
|
||||
},
|
||||
[options, pdfDispatch, source],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (typeof pdf === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pdf === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [pdf]);
|
||||
|
||||
useEffect(
|
||||
function setupLinkService() {
|
||||
linkService.current.setViewer(viewer.current);
|
||||
linkService.current.setExternalLinkRel(externalLinkRel);
|
||||
linkService.current.setExternalLinkTarget(externalLinkTarget);
|
||||
},
|
||||
[externalLinkRel, externalLinkTarget],
|
||||
);
|
||||
|
||||
const registerPage = useCallback((pageIndex: number, ref: HTMLDivElement) => {
|
||||
pages.current[pageIndex] = ref;
|
||||
}, []);
|
||||
|
||||
const unregisterPage = useCallback((pageIndex: number) => {
|
||||
delete pages.current[pageIndex];
|
||||
}, []);
|
||||
|
||||
const childContext = useMemo(
|
||||
() => ({
|
||||
imageResourcesPath,
|
||||
linkService: linkService.current,
|
||||
onItemClick,
|
||||
pdf,
|
||||
registerPage,
|
||||
renderMode,
|
||||
rotate,
|
||||
scale,
|
||||
unregisterPage,
|
||||
}),
|
||||
[imageResourcesPath, onItemClick, pdf, registerPage, renderMode, rotate, scale, unregisterPage],
|
||||
);
|
||||
|
||||
const eventProps = useMemo(
|
||||
() => makeEventProps(otherProps, () => pdf),
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: FIXME
|
||||
[otherProps, pdf],
|
||||
);
|
||||
|
||||
function renderChildren() {
|
||||
function isFulfilledContext(context: DocumentContextType): context is DocumentRenderProps {
|
||||
return Boolean(context?.pdf);
|
||||
}
|
||||
|
||||
if (!isFulfilledContext(childContext)) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
throw new Error('pdf is undefined');
|
||||
}
|
||||
|
||||
const resolvedChildren = typeof children === 'function' ? children(childContext) : children;
|
||||
|
||||
return (
|
||||
<DocumentContext.Provider value={childContext}>{resolvedChildren}</DocumentContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
if (!file) {
|
||||
return <Message type="no-data">{typeof noData === 'function' ? noData() : noData}</Message>;
|
||||
}
|
||||
|
||||
if (pdf === undefined || pdf === null) {
|
||||
return (
|
||||
<Message type="loading">{typeof loading === 'function' ? loading() : loading}</Message>
|
||||
);
|
||||
}
|
||||
|
||||
if (pdf === false) {
|
||||
return <Message type="error">{typeof error === 'function' ? error() : error}</Message>;
|
||||
}
|
||||
|
||||
return renderChildren();
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx('react-pdf__Document', className)}
|
||||
// Assertion is needed for React 18 compatibility
|
||||
ref={inputRef as React.Ref<HTMLDivElement>}
|
||||
{...eventProps}
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Document;
|
||||
10
frontend/node_modules/react-pdf/src/DocumentContext.tsx
generated
vendored
Normal file
10
frontend/node_modules/react-pdf/src/DocumentContext.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { DocumentContextType } from './shared/types.js';
|
||||
|
||||
const documentContext: React.Context<DocumentContextType> =
|
||||
createContext<DocumentContextType>(null);
|
||||
|
||||
export default documentContext;
|
||||
210
frontend/node_modules/react-pdf/src/LinkService.ts
generated
vendored
Normal file
210
frontend/node_modules/react-pdf/src/LinkService.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/* Copyright 2015 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import type { IPDFLinkService } from 'pdfjs-dist/types/web/interfaces.js';
|
||||
import type {
|
||||
Dest,
|
||||
ExternalLinkRel,
|
||||
ExternalLinkTarget,
|
||||
ResolvedDest,
|
||||
ScrollPageIntoViewArgs,
|
||||
} from './shared/types.js';
|
||||
|
||||
const DEFAULT_LINK_REL = 'noopener noreferrer nofollow';
|
||||
|
||||
type PDFViewer = {
|
||||
currentPageNumber?: number;
|
||||
scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void;
|
||||
};
|
||||
|
||||
export default class LinkService implements IPDFLinkService {
|
||||
externalLinkEnabled: boolean;
|
||||
externalLinkRel?: ExternalLinkRel;
|
||||
externalLinkTarget?: ExternalLinkTarget;
|
||||
isInPresentationMode: boolean;
|
||||
pdfDocument?: PDFDocumentProxy | null;
|
||||
pdfViewer?: PDFViewer | null;
|
||||
|
||||
constructor() {
|
||||
this.externalLinkEnabled = true;
|
||||
this.externalLinkRel = undefined;
|
||||
this.externalLinkTarget = undefined;
|
||||
this.isInPresentationMode = false;
|
||||
this.pdfDocument = undefined;
|
||||
this.pdfViewer = undefined;
|
||||
}
|
||||
|
||||
setDocument(pdfDocument: PDFDocumentProxy): void {
|
||||
this.pdfDocument = pdfDocument;
|
||||
}
|
||||
|
||||
setViewer(pdfViewer: PDFViewer): void {
|
||||
this.pdfViewer = pdfViewer;
|
||||
}
|
||||
|
||||
setExternalLinkRel(externalLinkRel?: ExternalLinkRel): void {
|
||||
this.externalLinkRel = externalLinkRel;
|
||||
}
|
||||
|
||||
setExternalLinkTarget(externalLinkTarget?: ExternalLinkTarget): void {
|
||||
this.externalLinkTarget = externalLinkTarget;
|
||||
}
|
||||
|
||||
setHash(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
setHistory(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
get pagesCount(): number {
|
||||
return this.pdfDocument ? this.pdfDocument.numPages : 0;
|
||||
}
|
||||
|
||||
get page(): number {
|
||||
invariant(this.pdfViewer, 'PDF viewer is not initialized.');
|
||||
|
||||
return this.pdfViewer.currentPageNumber || 0;
|
||||
}
|
||||
|
||||
set page(value: number) {
|
||||
invariant(this.pdfViewer, 'PDF viewer is not initialized.');
|
||||
|
||||
this.pdfViewer.currentPageNumber = value;
|
||||
}
|
||||
|
||||
get rotation(): number {
|
||||
return 0;
|
||||
}
|
||||
|
||||
set rotation(_value) {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
addLinkAttributes(link: HTMLAnchorElement, url: string, newWindow: boolean): void {
|
||||
link.href = url;
|
||||
link.rel = this.externalLinkRel || DEFAULT_LINK_REL;
|
||||
link.target = newWindow ? '_blank' : this.externalLinkTarget || '';
|
||||
}
|
||||
|
||||
goToDestination(dest: Dest): Promise<void> {
|
||||
return new Promise<ResolvedDest | null>((resolve) => {
|
||||
invariant(this.pdfDocument, 'PDF document not loaded.');
|
||||
|
||||
invariant(dest, 'Destination is not specified.');
|
||||
|
||||
if (typeof dest === 'string') {
|
||||
this.pdfDocument.getDestination(dest).then(resolve);
|
||||
} else if (Array.isArray(dest)) {
|
||||
resolve(dest);
|
||||
} else {
|
||||
dest.then(resolve);
|
||||
}
|
||||
}).then((explicitDest) => {
|
||||
invariant(Array.isArray(explicitDest), `"${explicitDest}" is not a valid destination array.`);
|
||||
|
||||
const destRef = explicitDest[0];
|
||||
|
||||
new Promise<number>((resolve) => {
|
||||
invariant(this.pdfDocument, 'PDF document not loaded.');
|
||||
|
||||
if (destRef instanceof Object) {
|
||||
this.pdfDocument
|
||||
.getPageIndex(destRef)
|
||||
.then((pageIndex) => {
|
||||
resolve(pageIndex);
|
||||
})
|
||||
.catch(() => {
|
||||
invariant(false, `"${destRef}" is not a valid page reference.`);
|
||||
});
|
||||
} else if (typeof destRef === 'number') {
|
||||
resolve(destRef);
|
||||
} else {
|
||||
invariant(false, `"${destRef}" is not a valid destination reference.`);
|
||||
}
|
||||
}).then((pageIndex) => {
|
||||
const pageNumber = pageIndex + 1;
|
||||
|
||||
invariant(this.pdfViewer, 'PDF viewer is not initialized.');
|
||||
|
||||
invariant(
|
||||
pageNumber >= 1 && pageNumber <= this.pagesCount,
|
||||
`"${pageNumber}" is not a valid page number.`,
|
||||
);
|
||||
|
||||
this.pdfViewer.scrollPageIntoView({
|
||||
dest: explicitDest,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
goToPage(pageNumber: number): void {
|
||||
const pageIndex = pageNumber - 1;
|
||||
|
||||
invariant(this.pdfViewer, 'PDF viewer is not initialized.');
|
||||
|
||||
invariant(
|
||||
pageNumber >= 1 && pageNumber <= this.pagesCount,
|
||||
`"${pageNumber}" is not a valid page number.`,
|
||||
);
|
||||
|
||||
this.pdfViewer.scrollPageIntoView({
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
});
|
||||
}
|
||||
|
||||
goToXY(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
cachePageRef(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
getDestinationHash(): string {
|
||||
return '#';
|
||||
}
|
||||
|
||||
getAnchorUrl(): string {
|
||||
return '#';
|
||||
}
|
||||
|
||||
executeNamedAction(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
executeSetOCGState(): void {
|
||||
// Intentionally empty
|
||||
}
|
||||
|
||||
isPageVisible(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
isPageCached(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
navigateTo(dest: Dest): void {
|
||||
this.goToDestination(dest);
|
||||
}
|
||||
}
|
||||
8
frontend/node_modules/react-pdf/src/Message.tsx
generated
vendored
Normal file
8
frontend/node_modules/react-pdf/src/Message.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
type MessageProps = {
|
||||
children?: React.ReactNode;
|
||||
type: 'error' | 'loading' | 'no-data';
|
||||
};
|
||||
|
||||
export default function Message({ children, type }: MessageProps): React.ReactElement {
|
||||
return <div className={`react-pdf__message react-pdf__message--${type}`}>{children}</div>;
|
||||
}
|
||||
197
frontend/node_modules/react-pdf/src/Outline.tsx
generated
vendored
Normal file
197
frontend/node_modules/react-pdf/src/Outline.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import makeEventProps from 'make-event-props';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import OutlineContext from './OutlineContext.js';
|
||||
import OutlineItem from './OutlineItem.js';
|
||||
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
import useResolver from './shared/hooks/useResolver.js';
|
||||
|
||||
import { cancelRunningTask } from './shared/utils.js';
|
||||
|
||||
import type { EventProps } from 'make-event-props';
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import type { ClassName, OnItemClickArgs } from './shared/types.js';
|
||||
|
||||
type PDFOutline = Awaited<ReturnType<PDFDocumentProxy['getOutline']>>;
|
||||
|
||||
export type OutlineProps = {
|
||||
/**
|
||||
* Class name(s) that will be added to rendered element along with the default `react-pdf__Outline`.
|
||||
*
|
||||
* @example 'custom-class-name-1 custom-class-name-2'
|
||||
* @example ['custom-class-name-1', 'custom-class-name-2']
|
||||
*/
|
||||
className?: ClassName;
|
||||
/**
|
||||
* A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to main `<div>` rendered by `<Outline>` component.
|
||||
*
|
||||
* @example (ref) => { this.myOutline = ref; }
|
||||
* @example this.ref
|
||||
* @example ref
|
||||
*/
|
||||
inputRef?: React.Ref<HTMLDivElement>;
|
||||
/**
|
||||
* Function called when an outline item has been clicked. Usually, you would like to use this callback to move the user wherever they requested to.
|
||||
*
|
||||
* @example ({ dest, pageIndex, pageNumber }) => alert('Clicked an item from page ' + pageNumber + '!')
|
||||
*/
|
||||
onItemClick?: (props: OnItemClickArgs) => void;
|
||||
/**
|
||||
* Function called in case of an error while retrieving the outline.
|
||||
*
|
||||
* @example (error) => alert('Error while retrieving the outline! ' + error.message)
|
||||
*/
|
||||
onLoadError?: (error: Error) => void;
|
||||
/**
|
||||
* Function called when the outline is successfully retrieved.
|
||||
*
|
||||
* @example (outline) => alert('The outline has been successfully retrieved.')
|
||||
*/
|
||||
onLoadSuccess?: (outline: PDFOutline | null) => void;
|
||||
pdf?: PDFDocumentProxy | false;
|
||||
} & EventProps<PDFOutline | null | false | undefined>;
|
||||
|
||||
/**
|
||||
* Displays an outline (table of contents).
|
||||
*
|
||||
* Should be placed inside `<Document />`. Alternatively, it can have `pdf` prop passed, which can be obtained from `<Document />`'s `onLoadSuccess` callback function.
|
||||
*/
|
||||
export default function Outline(props: OutlineProps): React.ReactElement | null {
|
||||
const documentContext = useDocumentContext();
|
||||
|
||||
const mergedProps = { ...documentContext, ...props };
|
||||
const {
|
||||
className,
|
||||
inputRef,
|
||||
onItemClick,
|
||||
onLoadError: onLoadErrorProps,
|
||||
onLoadSuccess: onLoadSuccessProps,
|
||||
pdf,
|
||||
...otherProps
|
||||
} = mergedProps;
|
||||
|
||||
invariant(
|
||||
pdf,
|
||||
'Attempted to load an outline, but no document was specified. Wrap <Outline /> in a <Document /> or pass explicit `pdf` prop.',
|
||||
);
|
||||
|
||||
const [outlineState, outlineDispatch] = useResolver<PDFOutline | null>();
|
||||
const { value: outline, error: outlineError } = outlineState;
|
||||
|
||||
/**
|
||||
* Called when an outline is read successfully
|
||||
*/
|
||||
function onLoadSuccess() {
|
||||
if (typeof outline === 'undefined' || outline === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onLoadSuccessProps) {
|
||||
onLoadSuccessProps(outline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when an outline failed to read successfully
|
||||
*/
|
||||
function onLoadError() {
|
||||
if (!outlineError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, outlineError.toString());
|
||||
|
||||
if (onLoadErrorProps) {
|
||||
onLoadErrorProps(outlineError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf change
|
||||
useEffect(
|
||||
function resetOutline() {
|
||||
outlineDispatch({ type: 'RESET' });
|
||||
},
|
||||
[outlineDispatch, pdf],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function loadOutline() {
|
||||
const cancellable = makeCancellable(pdf.getOutline());
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable.promise
|
||||
.then((nextOutline) => {
|
||||
outlineDispatch({ type: 'RESOLVE', value: nextOutline });
|
||||
})
|
||||
.catch((error) => {
|
||||
outlineDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => cancelRunningTask(runningTask);
|
||||
},
|
||||
[outlineDispatch, pdf],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (outline === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (outline === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [outline]);
|
||||
|
||||
const childContext = useMemo(
|
||||
() => ({
|
||||
onItemClick,
|
||||
}),
|
||||
[onItemClick],
|
||||
);
|
||||
|
||||
const eventProps = useMemo(
|
||||
() => makeEventProps(otherProps, () => outline),
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: FIXME
|
||||
[otherProps, outline],
|
||||
);
|
||||
|
||||
if (!outline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderOutline() {
|
||||
if (!outline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{outline.map((item, itemIndex) => (
|
||||
<OutlineItem
|
||||
key={typeof item.dest === 'string' ? item.dest : itemIndex}
|
||||
item={item}
|
||||
pdf={pdf}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('react-pdf__Outline', className)} ref={inputRef} {...eventProps}>
|
||||
<OutlineContext.Provider value={childContext}>{renderOutline()}</OutlineContext.Provider>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
frontend/node_modules/react-pdf/src/OutlineContext.tsx
generated
vendored
Normal file
9
frontend/node_modules/react-pdf/src/OutlineContext.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { OutlineContextType } from './shared/types.js';
|
||||
|
||||
const outlineContext: React.Context<OutlineContextType> = createContext<OutlineContextType>(null);
|
||||
|
||||
export default outlineContext;
|
||||
115
frontend/node_modules/react-pdf/src/OutlineItem.tsx
generated
vendored
Normal file
115
frontend/node_modules/react-pdf/src/OutlineItem.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
import invariant from 'tiny-invariant';
|
||||
|
||||
import Ref from './Ref.js';
|
||||
|
||||
import useCachedValue from './shared/hooks/useCachedValue.js';
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
import useOutlineContext from './shared/hooks/useOutlineContext.js';
|
||||
|
||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
||||
import type { RefProxy } from 'pdfjs-dist/types/src/display/api.js';
|
||||
|
||||
type PDFOutline = Awaited<ReturnType<PDFDocumentProxy['getOutline']>>;
|
||||
|
||||
type PDFOutlineItem = PDFOutline[number];
|
||||
|
||||
type OutlineItemProps = {
|
||||
item: PDFOutlineItem;
|
||||
pdf?: PDFDocumentProxy | false;
|
||||
};
|
||||
|
||||
export default function OutlineItem(props: OutlineItemProps): React.ReactElement {
|
||||
const documentContext = useDocumentContext();
|
||||
|
||||
const outlineContext = useOutlineContext();
|
||||
|
||||
invariant(outlineContext, 'Unable to find Outline context.');
|
||||
|
||||
const mergedProps = { ...documentContext, ...outlineContext, ...props };
|
||||
const { item, linkService, onItemClick, pdf, ...otherProps } = mergedProps;
|
||||
|
||||
invariant(
|
||||
pdf,
|
||||
'Attempted to load an outline, but no document was specified. Wrap <Outline /> in a <Document /> or pass explicit `pdf` prop.',
|
||||
);
|
||||
|
||||
const getDestination = useCachedValue(() => {
|
||||
if (typeof item.dest === 'string') {
|
||||
return pdf.getDestination(item.dest);
|
||||
}
|
||||
|
||||
return item.dest;
|
||||
});
|
||||
|
||||
const getPageIndex = useCachedValue(async () => {
|
||||
const destination = await getDestination();
|
||||
|
||||
if (!destination) {
|
||||
throw new Error('Destination not found.');
|
||||
}
|
||||
|
||||
const [ref] = destination as [RefProxy];
|
||||
|
||||
return pdf.getPageIndex(new Ref(ref));
|
||||
});
|
||||
|
||||
const getPageNumber = useCachedValue(async () => {
|
||||
const pageIndex = await getPageIndex();
|
||||
|
||||
return pageIndex + 1;
|
||||
});
|
||||
|
||||
function onClick(event: React.MouseEvent<HTMLAnchorElement>) {
|
||||
event.preventDefault();
|
||||
|
||||
invariant(
|
||||
onItemClick || linkService,
|
||||
'Either onItemClick callback or linkService must be defined in order to navigate to an outline item.',
|
||||
);
|
||||
|
||||
if (onItemClick) {
|
||||
Promise.all([getDestination(), getPageIndex(), getPageNumber()]).then(
|
||||
([dest, pageIndex, pageNumber]) => {
|
||||
onItemClick({
|
||||
dest,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
});
|
||||
},
|
||||
);
|
||||
} else if (linkService) {
|
||||
linkService.goToDestination(item.dest);
|
||||
}
|
||||
}
|
||||
|
||||
function renderSubitems() {
|
||||
if (!item.items || !item.items.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { items: subitems } = item;
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{subitems.map((subitem, subitemIndex) => (
|
||||
<OutlineItem
|
||||
key={typeof subitem.dest === 'string' ? subitem.dest : subitemIndex}
|
||||
item={subitem}
|
||||
pdf={pdf}
|
||||
{...otherProps}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li>
|
||||
{/* biome-ignore lint/a11y/useValidAnchor: We can't provide real href here */}
|
||||
<a href="#" onClick={onClick}>
|
||||
{item.title}
|
||||
</a>
|
||||
{renderSubitems()}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
677
frontend/node_modules/react-pdf/src/Page.tsx
generated
vendored
Normal file
677
frontend/node_modules/react-pdf/src/Page.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,677 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import makeEventProps from 'make-event-props';
|
||||
import mergeRefs from 'merge-refs';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import Message from './Message.js';
|
||||
import AnnotationLayer from './Page/AnnotationLayer.js';
|
||||
import Canvas from './Page/Canvas.js';
|
||||
import TextLayer from './Page/TextLayer.js';
|
||||
import PageContext from './PageContext.js';
|
||||
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
import useResolver from './shared/hooks/useResolver.js';
|
||||
|
||||
import { cancelRunningTask, isProvided, makePageCallback } from './shared/utils.js';
|
||||
|
||||
import type { EventProps } from 'make-event-props';
|
||||
import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
|
||||
import type {
|
||||
ClassName,
|
||||
CustomRenderer,
|
||||
CustomTextRenderer,
|
||||
FilterAnnotations,
|
||||
NodeOrRenderer,
|
||||
OnGetAnnotationsError,
|
||||
OnGetAnnotationsSuccess,
|
||||
OnGetStructTreeError,
|
||||
OnGetStructTreeSuccess,
|
||||
OnGetTextError,
|
||||
OnGetTextSuccess,
|
||||
OnPageLoadError,
|
||||
OnPageLoadSuccess,
|
||||
OnRenderAnnotationLayerError,
|
||||
OnRenderAnnotationLayerSuccess,
|
||||
OnRenderError,
|
||||
OnRenderSuccess,
|
||||
OnRenderTextLayerError,
|
||||
OnRenderTextLayerSuccess,
|
||||
PageCallback,
|
||||
PageColors,
|
||||
PageContextType,
|
||||
PageRenderProps,
|
||||
RenderMode,
|
||||
} from './shared/types.js';
|
||||
|
||||
const defaultScale = 1;
|
||||
|
||||
export type PageProps = {
|
||||
_className?: string;
|
||||
_enableRegisterUnregisterPage?: boolean;
|
||||
/**
|
||||
* Canvas background color. Any valid `canvas.fillStyle` can be used.
|
||||
*
|
||||
* @example 'transparent'
|
||||
*/
|
||||
canvasBackground?: string;
|
||||
/**
|
||||
* A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to `<canvas>` rendered by `<PageCanvas>` component.
|
||||
*
|
||||
* @example (ref) => { this.myCanvas = ref; }
|
||||
* @example this.ref
|
||||
* @example ref
|
||||
*/
|
||||
canvasRef?: React.Ref<HTMLCanvasElement>;
|
||||
children?: React.ReactNode | ((props: PageRenderProps) => React.ReactNode);
|
||||
/**
|
||||
* Class name(s) that will be added to rendered element along with the default `react-pdf__Page`.
|
||||
*
|
||||
* @example 'custom-class-name-1 custom-class-name-2'
|
||||
* @example ['custom-class-name-1', 'custom-class-name-2']
|
||||
*/
|
||||
className?: ClassName;
|
||||
/**
|
||||
* Function that customizes how a page is rendered. You must set `renderMode` to `"custom"` to use this prop.
|
||||
*
|
||||
* @example MyCustomRenderer
|
||||
*/
|
||||
customRenderer?: CustomRenderer;
|
||||
/**
|
||||
* Function that customizes how a text layer is rendered.
|
||||
*
|
||||
* @example ({ str, itemIndex }) => str.replace(/ipsum/g, value => `<mark>${value}</mark>`)
|
||||
*/
|
||||
customTextRenderer?: CustomTextRenderer;
|
||||
/**
|
||||
* The ratio between physical pixels and device-independent pixels (DIPs) on the current device.
|
||||
*
|
||||
* @default window.devicePixelRatio
|
||||
* @example 1
|
||||
*/
|
||||
devicePixelRatio?: number;
|
||||
/**
|
||||
* What the component should display in case of an error.
|
||||
*
|
||||
* @default 'Failed to load the page.'
|
||||
* @example 'An error occurred!'
|
||||
* @example <p>An error occurred!</p>
|
||||
* @example this.renderError
|
||||
*/
|
||||
error?: NodeOrRenderer;
|
||||
/**
|
||||
* Function to filter annotations before they are rendered.
|
||||
*
|
||||
* @example ({ annotations }) => annotations.filter(annotation => annotation.subtype === 'Text')
|
||||
*/
|
||||
filterAnnotations?: FilterAnnotations;
|
||||
/**
|
||||
* Page height. If neither `height` nor `width` are defined, page will be rendered at the size defined in PDF. If you define `width` and `height` at the same time, `height` will be ignored. If you define `height` and `scale` at the same time, the height will be multiplied by a given factor.
|
||||
*
|
||||
* @example 300
|
||||
*/
|
||||
height?: number;
|
||||
/**
|
||||
* The path used to prefix the src attributes of annotation SVGs.
|
||||
*
|
||||
* @default ''
|
||||
* @example '/public/images/'
|
||||
*/
|
||||
imageResourcesPath?: string;
|
||||
/**
|
||||
* A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to main `<div>` rendered by `<Page>` component.
|
||||
*
|
||||
* @example (ref) => { this.myPage = ref; }
|
||||
* @example this.ref
|
||||
* @example ref
|
||||
*/
|
||||
inputRef?: React.Ref<HTMLDivElement | null>;
|
||||
/**
|
||||
* What the component should display while loading.
|
||||
*
|
||||
* @default 'Loading page…'
|
||||
* @example 'Please wait!'
|
||||
* @example <p>Please wait!</p>
|
||||
* @example this.renderLoader
|
||||
*/
|
||||
loading?: NodeOrRenderer;
|
||||
/**
|
||||
* What the component should display in case of no data.
|
||||
*
|
||||
* @default 'No page specified.'
|
||||
* @example 'Please select a page.'
|
||||
* @example <p>Please select a page.</p>
|
||||
* @example this.renderNoData
|
||||
*/
|
||||
noData?: NodeOrRenderer;
|
||||
/**
|
||||
* Function called in case of an error while loading annotations.
|
||||
*
|
||||
* @example (error) => alert('Error while loading annotations! ' + error.message)
|
||||
*/
|
||||
onGetAnnotationsError?: OnGetAnnotationsError;
|
||||
/**
|
||||
* Function called when annotations are successfully loaded.
|
||||
*
|
||||
* @example (annotations) => alert('Now displaying ' + annotations.length + ' annotations!')
|
||||
*/
|
||||
onGetAnnotationsSuccess?: OnGetAnnotationsSuccess;
|
||||
/**
|
||||
* Function called in case of an error while loading structure tree.
|
||||
*
|
||||
* @example (error) => alert('Error while loading structure tree! ' + error.message)
|
||||
*/
|
||||
onGetStructTreeError?: OnGetStructTreeError;
|
||||
/**
|
||||
* Function called when structure tree is successfully loaded.
|
||||
*
|
||||
* @example (structTree) => alert(JSON.stringify(structTree))
|
||||
*/
|
||||
onGetStructTreeSuccess?: OnGetStructTreeSuccess;
|
||||
/**
|
||||
* Function called in case of an error while loading text layer items.
|
||||
*
|
||||
* @example (error) => alert('Error while loading text layer items! ' + error.message)
|
||||
*/
|
||||
onGetTextError?: OnGetTextError;
|
||||
/**
|
||||
* Function called when text layer items are successfully loaded.
|
||||
*
|
||||
* @example ({ items, styles }) => alert('Now displaying ' + items.length + ' text layer items!')
|
||||
*/
|
||||
onGetTextSuccess?: OnGetTextSuccess;
|
||||
/**
|
||||
* Function called in case of an error while loading the page.
|
||||
*
|
||||
* @example (error) => alert('Error while loading page! ' + error.message)
|
||||
*/
|
||||
onLoadError?: OnPageLoadError;
|
||||
/**
|
||||
* Function called when the page is successfully loaded.
|
||||
*
|
||||
* @example (page) => alert('Now displaying a page number ' + page.pageNumber + '!')
|
||||
*/
|
||||
onLoadSuccess?: OnPageLoadSuccess;
|
||||
/**
|
||||
* Function called in case of an error while rendering the annotation layer.
|
||||
*
|
||||
* @example (error) => alert('Error while rendering annotation layer! ' + error.message)
|
||||
*/
|
||||
onRenderAnnotationLayerError?: OnRenderAnnotationLayerError;
|
||||
/**
|
||||
* Function called when annotations are successfully rendered on the screen.
|
||||
*
|
||||
* @example () => alert('Rendered the annotation layer!')
|
||||
*/
|
||||
onRenderAnnotationLayerSuccess?: OnRenderAnnotationLayerSuccess;
|
||||
/**
|
||||
* Function called in case of an error while rendering the page.
|
||||
*
|
||||
* @example (error) => alert('Error while loading page! ' + error.message)
|
||||
*/
|
||||
onRenderError?: OnRenderError;
|
||||
/**
|
||||
* Function called when the page is successfully rendered on the screen.
|
||||
*
|
||||
* @example () => alert('Rendered the page!')
|
||||
*/
|
||||
onRenderSuccess?: OnRenderSuccess;
|
||||
/**
|
||||
* Function called in case of an error while rendering the text layer.
|
||||
*
|
||||
* @example (error) => alert('Error while rendering text layer! ' + error.message)
|
||||
*/
|
||||
onRenderTextLayerError?: OnRenderTextLayerError;
|
||||
/**
|
||||
* Function called when the text layer is successfully rendered on the screen.
|
||||
*
|
||||
* @example () => alert('Rendered the text layer!')
|
||||
*/
|
||||
onRenderTextLayerSuccess?: OnRenderTextLayerSuccess;
|
||||
/**
|
||||
* Colors used to render the page. If not provided, the default colors from PDF will be used.
|
||||
*
|
||||
* @example { background: 'transparent', foreground: '#ff0000' }
|
||||
*/
|
||||
pageColors?: PageColors;
|
||||
/**
|
||||
* Which page from PDF file should be displayed, by page index. Ignored if `pageNumber` prop is provided.
|
||||
*
|
||||
* @default 0
|
||||
* @example 1
|
||||
*/
|
||||
pageIndex?: number;
|
||||
/**
|
||||
* Which page from PDF file should be displayed, by page number. If provided, `pageIndex` prop will be ignored.
|
||||
*
|
||||
* @default 1
|
||||
* @example 2
|
||||
*/
|
||||
pageNumber?: number;
|
||||
/**
|
||||
* pdf object obtained from `<Document />`'s `onLoadSuccess` callback function.
|
||||
*
|
||||
* @example pdf
|
||||
*/
|
||||
pdf?: PDFDocumentProxy | false;
|
||||
registerPage?: undefined;
|
||||
/**
|
||||
* Whether annotations (e.g. links) should be rendered.
|
||||
*
|
||||
* @default true
|
||||
* @example false
|
||||
*/
|
||||
renderAnnotationLayer?: boolean;
|
||||
/**
|
||||
* Whether forms should be rendered. `renderAnnotationLayer` prop must be set to `true`.
|
||||
*
|
||||
* @default false
|
||||
* @example true
|
||||
*/
|
||||
renderForms?: boolean;
|
||||
/**
|
||||
* Rendering mode of the document. Can be `"canvas"`, `"custom"` or `"none"`. If set to `"custom"`, `customRenderer` must also be provided.
|
||||
*
|
||||
* @default 'canvas'
|
||||
* @example 'custom'
|
||||
*/
|
||||
renderMode?: RenderMode;
|
||||
/**
|
||||
* Whether a text layer should be rendered.
|
||||
*
|
||||
* @default true
|
||||
* @example false
|
||||
*/
|
||||
renderTextLayer?: boolean;
|
||||
/**
|
||||
* Rotation of the page in degrees. `90` = rotated to the right, `180` = upside down, `270` = rotated to the left.
|
||||
*
|
||||
* @default 0
|
||||
* @example 90
|
||||
*/
|
||||
rotate?: number | null;
|
||||
/**
|
||||
* Page scale.
|
||||
*
|
||||
* @default 1
|
||||
* @example 0.5
|
||||
*/
|
||||
scale?: number;
|
||||
unregisterPage?: undefined;
|
||||
/**
|
||||
* Page width. If neither `height` nor `width` are defined, page will be rendered at the size defined in PDF. If you define `width` and `height` at the same time, `height` will be ignored. If you define `width` and `scale` at the same time, the width will be multiplied by a given factor.
|
||||
*
|
||||
* @example 300
|
||||
*/
|
||||
width?: number;
|
||||
} & EventProps<PageCallback | false | undefined>;
|
||||
|
||||
/**
|
||||
* Displays a page.
|
||||
*
|
||||
* Should be placed inside `<Document />`. Alternatively, it can have `pdf` prop passed, which can be obtained from `<Document />`'s `onLoadSuccess` callback function, however some advanced functions like linking between pages inside a document may not be working correctly.
|
||||
*/
|
||||
export default function Page(props: PageProps): React.ReactElement {
|
||||
const documentContext = useDocumentContext();
|
||||
|
||||
const mergedProps = { ...documentContext, ...props };
|
||||
const {
|
||||
_className = 'react-pdf__Page',
|
||||
_enableRegisterUnregisterPage = true,
|
||||
canvasBackground,
|
||||
canvasRef,
|
||||
children,
|
||||
className,
|
||||
customRenderer: CustomRenderer,
|
||||
customTextRenderer,
|
||||
devicePixelRatio,
|
||||
error = 'Failed to load the page.',
|
||||
filterAnnotations,
|
||||
height,
|
||||
inputRef,
|
||||
loading = 'Loading page…',
|
||||
noData = 'No page specified.',
|
||||
onGetAnnotationsError: onGetAnnotationsErrorProps,
|
||||
onGetAnnotationsSuccess: onGetAnnotationsSuccessProps,
|
||||
onGetStructTreeError: onGetStructTreeErrorProps,
|
||||
onGetStructTreeSuccess: onGetStructTreeSuccessProps,
|
||||
onGetTextError: onGetTextErrorProps,
|
||||
onGetTextSuccess: onGetTextSuccessProps,
|
||||
onLoadError: onLoadErrorProps,
|
||||
onLoadSuccess: onLoadSuccessProps,
|
||||
onRenderAnnotationLayerError: onRenderAnnotationLayerErrorProps,
|
||||
onRenderAnnotationLayerSuccess: onRenderAnnotationLayerSuccessProps,
|
||||
onRenderError: onRenderErrorProps,
|
||||
onRenderSuccess: onRenderSuccessProps,
|
||||
onRenderTextLayerError: onRenderTextLayerErrorProps,
|
||||
onRenderTextLayerSuccess: onRenderTextLayerSuccessProps,
|
||||
pageColors,
|
||||
pageIndex: pageIndexProps,
|
||||
pageNumber: pageNumberProps,
|
||||
pdf,
|
||||
registerPage,
|
||||
renderAnnotationLayer: renderAnnotationLayerProps = true,
|
||||
renderForms = false,
|
||||
renderMode = 'canvas',
|
||||
renderTextLayer: renderTextLayerProps = true,
|
||||
rotate: rotateProps,
|
||||
scale: scaleProps = defaultScale,
|
||||
unregisterPage,
|
||||
width,
|
||||
...otherProps
|
||||
} = mergedProps;
|
||||
|
||||
const [pageState, pageDispatch] = useResolver<PDFPageProxy>();
|
||||
const { value: page, error: pageError } = pageState;
|
||||
const pageElement = useRef<HTMLDivElement>(null);
|
||||
|
||||
invariant(
|
||||
pdf,
|
||||
'Attempted to load a page, but no document was specified. Wrap <Page /> in a <Document /> or pass explicit `pdf` prop.',
|
||||
);
|
||||
|
||||
const pageIndex = isProvided(pageNumberProps) ? pageNumberProps - 1 : (pageIndexProps ?? null);
|
||||
|
||||
const pageNumber = pageNumberProps ?? (isProvided(pageIndexProps) ? pageIndexProps + 1 : null);
|
||||
|
||||
const rotate = rotateProps ?? (page ? page.rotate : null);
|
||||
|
||||
const scale = useMemo(() => {
|
||||
if (!page) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Be default, we'll render page at 100% * scale width.
|
||||
let pageScale = 1;
|
||||
|
||||
// Passing scale explicitly null would cause the page not to render
|
||||
const scaleWithDefault = scaleProps ?? defaultScale;
|
||||
|
||||
// If width/height is defined, calculate the scale of the page so it could be of desired width.
|
||||
if (width || height) {
|
||||
const viewport = page.getViewport({ scale: 1, rotation: rotate as number });
|
||||
if (width) {
|
||||
pageScale = width / viewport.width;
|
||||
} else if (height) {
|
||||
pageScale = height / viewport.height;
|
||||
}
|
||||
}
|
||||
|
||||
return scaleWithDefault * pageScale;
|
||||
}, [height, page, rotate, scaleProps, width]);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf change
|
||||
useEffect(
|
||||
function hook() {
|
||||
return () => {
|
||||
if (!isProvided(pageIndex)) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
if (_enableRegisterUnregisterPage && unregisterPage) {
|
||||
unregisterPage(pageIndex);
|
||||
}
|
||||
};
|
||||
},
|
||||
[_enableRegisterUnregisterPage, pdf, pageIndex, unregisterPage],
|
||||
);
|
||||
|
||||
/**
|
||||
* Called when a page is loaded successfully
|
||||
*/
|
||||
function onLoadSuccess() {
|
||||
if (onLoadSuccessProps) {
|
||||
if (!page || !scale) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccessProps(makePageCallback(page, scale));
|
||||
}
|
||||
|
||||
if (_enableRegisterUnregisterPage && registerPage) {
|
||||
if (!isProvided(pageIndex) || !pageElement.current) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
registerPage(pageIndex, pageElement.current);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a page failed to load
|
||||
*/
|
||||
function onLoadError() {
|
||||
if (!pageError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, pageError.toString());
|
||||
|
||||
if (onLoadErrorProps) {
|
||||
onLoadErrorProps(pageError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on pdf and pageIndex change
|
||||
useEffect(
|
||||
function resetPage() {
|
||||
pageDispatch({ type: 'RESET' });
|
||||
},
|
||||
[pageDispatch, pdf, pageIndex],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function loadPage() {
|
||||
if (!pdf || !pageNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellable = makeCancellable(pdf.getPage(pageNumber));
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable.promise
|
||||
.then((nextPage) => {
|
||||
pageDispatch({ type: 'RESOLVE', value: nextPage });
|
||||
})
|
||||
.catch((error) => {
|
||||
pageDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => cancelRunningTask(runningTask);
|
||||
},
|
||||
[pageDispatch, pdf, pageNumber],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (page === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (page === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [page, scale]);
|
||||
|
||||
const childContext = useMemo(
|
||||
() =>
|
||||
// Technically there cannot be page without pageIndex, pageNumber, rotate and scale, but TypeScript doesn't know that
|
||||
isProvided(pageIndex) && pageNumber && isProvided(rotate) && isProvided(scale)
|
||||
? {
|
||||
_className,
|
||||
canvasBackground,
|
||||
customTextRenderer,
|
||||
devicePixelRatio,
|
||||
filterAnnotations,
|
||||
onGetAnnotationsError: onGetAnnotationsErrorProps,
|
||||
onGetAnnotationsSuccess: onGetAnnotationsSuccessProps,
|
||||
onGetStructTreeError: onGetStructTreeErrorProps,
|
||||
onGetStructTreeSuccess: onGetStructTreeSuccessProps,
|
||||
onGetTextError: onGetTextErrorProps,
|
||||
onGetTextSuccess: onGetTextSuccessProps,
|
||||
onRenderAnnotationLayerError: onRenderAnnotationLayerErrorProps,
|
||||
onRenderAnnotationLayerSuccess: onRenderAnnotationLayerSuccessProps,
|
||||
onRenderError: onRenderErrorProps,
|
||||
onRenderSuccess: onRenderSuccessProps,
|
||||
onRenderTextLayerError: onRenderTextLayerErrorProps,
|
||||
onRenderTextLayerSuccess: onRenderTextLayerSuccessProps,
|
||||
page,
|
||||
pageColors,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
renderForms,
|
||||
renderTextLayer: renderTextLayerProps,
|
||||
rotate,
|
||||
scale,
|
||||
}
|
||||
: null,
|
||||
[
|
||||
_className,
|
||||
canvasBackground,
|
||||
customTextRenderer,
|
||||
devicePixelRatio,
|
||||
filterAnnotations,
|
||||
onGetAnnotationsErrorProps,
|
||||
onGetAnnotationsSuccessProps,
|
||||
onGetStructTreeErrorProps,
|
||||
onGetStructTreeSuccessProps,
|
||||
onGetTextErrorProps,
|
||||
onGetTextSuccessProps,
|
||||
onRenderAnnotationLayerErrorProps,
|
||||
onRenderAnnotationLayerSuccessProps,
|
||||
onRenderErrorProps,
|
||||
onRenderSuccessProps,
|
||||
onRenderTextLayerErrorProps,
|
||||
onRenderTextLayerSuccessProps,
|
||||
page,
|
||||
pageColors,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
renderForms,
|
||||
renderTextLayerProps,
|
||||
rotate,
|
||||
scale,
|
||||
],
|
||||
);
|
||||
|
||||
const eventProps = useMemo(
|
||||
() =>
|
||||
makeEventProps(otherProps, () =>
|
||||
page ? (scale ? makePageCallback(page, scale) : undefined) : page,
|
||||
),
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: FIXME
|
||||
[otherProps, page, scale],
|
||||
);
|
||||
|
||||
const pageKey = `${pageIndex}@${scale}/${rotate}`;
|
||||
|
||||
function renderMainLayer() {
|
||||
switch (renderMode) {
|
||||
case 'custom': {
|
||||
invariant(
|
||||
CustomRenderer,
|
||||
`renderMode was set to "custom", but no customRenderer was passed.`,
|
||||
);
|
||||
|
||||
return <CustomRenderer key={`${pageKey}_custom`} />;
|
||||
}
|
||||
case 'none':
|
||||
return null;
|
||||
case 'canvas':
|
||||
default:
|
||||
return <Canvas key={`${pageKey}_canvas`} canvasRef={canvasRef} />;
|
||||
}
|
||||
}
|
||||
|
||||
function renderTextLayer() {
|
||||
if (!renderTextLayerProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <TextLayer key={`${pageKey}_text`} />;
|
||||
}
|
||||
|
||||
function renderAnnotationLayer() {
|
||||
if (!renderAnnotationLayerProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <AnnotationLayer key={`${pageKey}_annotations`} />;
|
||||
}
|
||||
|
||||
function renderChildren() {
|
||||
function isFulfilledContext(context: PageContextType): context is PageRenderProps {
|
||||
return Boolean(context?.page);
|
||||
}
|
||||
|
||||
if (!isFulfilledContext(childContext)) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
throw new Error('page is undefined');
|
||||
}
|
||||
|
||||
const resolvedChildren = typeof children === 'function' ? children(childContext) : children;
|
||||
|
||||
return (
|
||||
<PageContext.Provider value={childContext}>
|
||||
{renderMainLayer()}
|
||||
{renderTextLayer()}
|
||||
{renderAnnotationLayer()}
|
||||
{resolvedChildren}
|
||||
</PageContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function renderContent() {
|
||||
if (!pageNumber) {
|
||||
return <Message type="no-data">{typeof noData === 'function' ? noData() : noData}</Message>;
|
||||
}
|
||||
|
||||
if (pdf === null || page === undefined || page === null) {
|
||||
return (
|
||||
<Message type="loading">{typeof loading === 'function' ? loading() : loading}</Message>
|
||||
);
|
||||
}
|
||||
|
||||
if (pdf === false || page === false) {
|
||||
return <Message type="error">{typeof error === 'function' ? error() : error}</Message>;
|
||||
}
|
||||
|
||||
return renderChildren();
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(_className, className)}
|
||||
data-page-number={pageNumber}
|
||||
// Assertion is needed for React 18 compatibility
|
||||
ref={mergeRefs(inputRef as React.Ref<HTMLDivElement>, pageElement)}
|
||||
style={
|
||||
{
|
||||
'--scale-round-x': '1px',
|
||||
'--scale-round-y': '1px',
|
||||
'--scale-factor': '1',
|
||||
'--user-unit': `${scale}`,
|
||||
'--total-scale-factor': 'calc(var(--scale-factor) * var(--user-unit))',
|
||||
backgroundColor: canvasBackground || 'white',
|
||||
position: 'relative',
|
||||
minWidth: 'min-content',
|
||||
minHeight: 'min-content',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
{...eventProps}
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
333
frontend/node_modules/react-pdf/src/Page/AnnotationLayer.css
generated
vendored
Normal file
333
frontend/node_modules/react-pdf/src/Page/AnnotationLayer.css
generated
vendored
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--react-pdf-annotation-layer: 1;
|
||||
--annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
|
||||
--input-focus-border-color: Highlight;
|
||||
--input-focus-outline: 1px solid Canvas;
|
||||
--input-unfocused-border-color: transparent;
|
||||
--input-disabled-border-color: transparent;
|
||||
--input-hover-border-color: black;
|
||||
--link-outline: none;
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
:root {
|
||||
--input-focus-border-color: CanvasText;
|
||||
--input-unfocused-border-color: ActiveText;
|
||||
--input-disabled-border-color: GrayText;
|
||||
--input-hover-border-color: Highlight;
|
||||
--link-outline: 1.5px solid LinkText;
|
||||
}
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea):required,
|
||||
.annotationLayer .choiceWidgetAnnotation select:required,
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required {
|
||||
outline: 1.5px solid selectedItem;
|
||||
}
|
||||
|
||||
.annotationLayer .linkAnnotation:hover {
|
||||
backdrop-filter: invert(100%);
|
||||
}
|
||||
}
|
||||
|
||||
.annotationLayer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
transform-origin: 0 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.annotationLayer[data-main-rotation='90'] .norotate {
|
||||
transform: rotate(270deg) translateX(-100%);
|
||||
}
|
||||
.annotationLayer[data-main-rotation='180'] .norotate {
|
||||
transform: rotate(180deg) translate(-100%, -100%);
|
||||
}
|
||||
.annotationLayer[data-main-rotation='270'] .norotate {
|
||||
transform: rotate(90deg) translateY(-100%);
|
||||
}
|
||||
|
||||
.annotationLayer canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.annotationLayer section {
|
||||
position: absolute;
|
||||
text-align: initial;
|
||||
pointer-events: auto;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
.annotationLayer .linkAnnotation {
|
||||
outline: var(--link-outline);
|
||||
}
|
||||
|
||||
.textLayer.selecting ~ .annotationLayer section {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a {
|
||||
position: absolute;
|
||||
font-size: 1em;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a:hover {
|
||||
opacity: 0.2;
|
||||
background: rgba(255, 255, 0, 1);
|
||||
box-shadow: 0 2px 10px rgba(255, 255, 0, 1);
|
||||
}
|
||||
|
||||
.annotationLayer .textAnnotation img {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea),
|
||||
.annotationLayer .choiceWidgetAnnotation select,
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input {
|
||||
background-image: var(--annotation-unfocused-field-background);
|
||||
border: 2px solid var(--input-unfocused-border-color);
|
||||
box-sizing: border-box;
|
||||
font: calc(9px * var(--total-scale-factor)) sans-serif;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
vertical-align: top;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea):required,
|
||||
.annotationLayer .choiceWidgetAnnotation select:required,
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required {
|
||||
outline: 1.5px solid red;
|
||||
}
|
||||
|
||||
.annotationLayer .choiceWidgetAnnotation select option {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.radioButton input {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea)[disabled],
|
||||
.annotationLayer .choiceWidgetAnnotation select[disabled],
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input[disabled] {
|
||||
background: none;
|
||||
border: 2px solid var(--input-disabled-border-color);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea):hover,
|
||||
.annotationLayer .choiceWidgetAnnotation select:hover,
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover {
|
||||
border: 2px solid var(--input-hover-border-color);
|
||||
}
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea):hover,
|
||||
.annotationLayer .choiceWidgetAnnotation select:hover,
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:hover {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation :is(input, textarea):focus,
|
||||
.annotationLayer .choiceWidgetAnnotation select:focus {
|
||||
background: none;
|
||||
border: 2px solid var(--input-focus-border-color);
|
||||
border-radius: 2px;
|
||||
outline: var(--input-focus-outline);
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) :focus {
|
||||
background-image: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox :focus {
|
||||
border: 2px solid var(--input-focus-border-color);
|
||||
border-radius: 2px;
|
||||
outline: var(--input-focus-outline);
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.radioButton :focus {
|
||||
border: 2px solid var(--input-focus-border-color);
|
||||
outline: var(--input-focus-outline);
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after,
|
||||
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
|
||||
background-color: CanvasText;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
|
||||
height: 80%;
|
||||
left: 45%;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before {
|
||||
border-radius: 50%;
|
||||
height: 50%;
|
||||
left: 30%;
|
||||
top: 20%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input.comb {
|
||||
font-family: monospace;
|
||||
padding-left: 2px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.annotationLayer .textWidgetAnnotation input.comb:focus {
|
||||
/*
|
||||
* Letter spacing is placed on the right side of each character. Hence, the
|
||||
* letter spacing of the last character may be placed outside the visible
|
||||
* area, causing horizontal scrolling. We avoid this by extending the width
|
||||
* when the element has focus and revert this when it loses focus.
|
||||
*/
|
||||
width: 103%;
|
||||
}
|
||||
|
||||
.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.annotationLayer .popupTriggerArea {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.annotationLayer .fileAttachmentAnnotation .popupTriggerArea {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.annotationLayer .popupWrapper {
|
||||
position: absolute;
|
||||
font-size: calc(9px * var(--total-scale-factor));
|
||||
width: 100%;
|
||||
min-width: calc(180px * var(--total-scale-factor));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.annotationLayer .popup {
|
||||
position: absolute;
|
||||
max-width: calc(180px * var(--total-scale-factor));
|
||||
background-color: rgba(255, 255, 153, 1);
|
||||
box-shadow: 0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor))
|
||||
rgba(136, 136, 136, 1);
|
||||
border-radius: calc(2px * var(--total-scale-factor));
|
||||
padding: calc(6px * var(--total-scale-factor));
|
||||
margin-left: calc(5px * var(--total-scale-factor));
|
||||
cursor: pointer;
|
||||
font: message-box;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.annotationLayer .popup > * {
|
||||
font-size: calc(9px * var(--total-scale-factor));
|
||||
}
|
||||
|
||||
.annotationLayer .popup h1 {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.annotationLayer .popupDate {
|
||||
display: inline-block;
|
||||
margin-left: calc(5px * var(--total-scale-factor));
|
||||
}
|
||||
|
||||
.annotationLayer .popupContent {
|
||||
border-top: 1px solid rgba(51, 51, 51, 1);
|
||||
margin-top: calc(2px * var(--total-scale-factor));
|
||||
padding-top: calc(2px * var(--total-scale-factor));
|
||||
}
|
||||
|
||||
.annotationLayer .richText > * {
|
||||
white-space: pre-wrap;
|
||||
font-size: calc(9px * var(--total-scale-factor));
|
||||
}
|
||||
|
||||
.annotationLayer .highlightAnnotation,
|
||||
.annotationLayer .underlineAnnotation,
|
||||
.annotationLayer .squigglyAnnotation,
|
||||
.annotationLayer .strikeoutAnnotation,
|
||||
.annotationLayer .freeTextAnnotation,
|
||||
.annotationLayer .lineAnnotation svg line,
|
||||
.annotationLayer .squareAnnotation svg rect,
|
||||
.annotationLayer .circleAnnotation svg ellipse,
|
||||
.annotationLayer .polylineAnnotation svg polyline,
|
||||
.annotationLayer .polygonAnnotation svg polygon,
|
||||
.annotationLayer .caretAnnotation,
|
||||
.annotationLayer .inkAnnotation svg polyline,
|
||||
.annotationLayer .stampAnnotation,
|
||||
.annotationLayer .fileAttachmentAnnotation {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.annotationLayer section svg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.annotationLayer .annotationTextContent {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
color: transparent;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.annotationLayer .annotationTextContent span {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
219
frontend/node_modules/react-pdf/src/Page/AnnotationLayer.tsx
generated
vendored
Normal file
219
frontend/node_modules/react-pdf/src/Page/AnnotationLayer.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import useDocumentContext from '../shared/hooks/useDocumentContext.js';
|
||||
import usePageContext from '../shared/hooks/usePageContext.js';
|
||||
import useResolver from '../shared/hooks/useResolver.js';
|
||||
|
||||
import { cancelRunningTask } from '../shared/utils.js';
|
||||
|
||||
import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/annotation_layer.js';
|
||||
import type { Annotations } from '../shared/types.js';
|
||||
|
||||
export default function AnnotationLayer(): React.ReactElement {
|
||||
const documentContext = useDocumentContext();
|
||||
const pageContext = usePageContext();
|
||||
|
||||
invariant(pageContext, 'Unable to find Page context.');
|
||||
|
||||
const mergedProps = { ...documentContext, ...pageContext };
|
||||
const {
|
||||
filterAnnotations,
|
||||
imageResourcesPath,
|
||||
linkService,
|
||||
onGetAnnotationsError: onGetAnnotationsErrorProps,
|
||||
onGetAnnotationsSuccess: onGetAnnotationsSuccessProps,
|
||||
onRenderAnnotationLayerError: onRenderAnnotationLayerErrorProps,
|
||||
onRenderAnnotationLayerSuccess: onRenderAnnotationLayerSuccessProps,
|
||||
page,
|
||||
pdf,
|
||||
renderForms,
|
||||
rotate,
|
||||
scale = 1,
|
||||
} = mergedProps;
|
||||
|
||||
invariant(
|
||||
pdf,
|
||||
'Attempted to load page annotations, but no document was specified. Wrap <Page /> in a <Document /> or pass explicit `pdf` prop.',
|
||||
);
|
||||
invariant(page, 'Attempted to load page annotations, but no page was specified.');
|
||||
invariant(linkService, 'Attempted to load page annotations, but no linkService was specified.');
|
||||
|
||||
const [annotationsState, annotationsDispatch] = useResolver<Annotations>();
|
||||
const { value: annotations, error: annotationsError } = annotationsState;
|
||||
const layerElement = useRef<HTMLDivElement>(null);
|
||||
|
||||
warning(
|
||||
Number.parseInt(
|
||||
window.getComputedStyle(document.body).getPropertyValue('--react-pdf-annotation-layer'),
|
||||
10,
|
||||
) === 1,
|
||||
'AnnotationLayer styles not found. Read more: https://github.com/wojtekmaj/react-pdf#support-for-annotations',
|
||||
);
|
||||
|
||||
function onLoadSuccess() {
|
||||
if (!annotations) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
if (onGetAnnotationsSuccessProps) {
|
||||
onGetAnnotationsSuccessProps(annotations);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoadError() {
|
||||
if (!annotationsError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, annotationsError.toString());
|
||||
|
||||
if (onGetAnnotationsErrorProps) {
|
||||
onGetAnnotationsErrorProps(annotationsError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change
|
||||
useEffect(
|
||||
function resetAnnotations() {
|
||||
annotationsDispatch({ type: 'RESET' });
|
||||
},
|
||||
[annotationsDispatch, page],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function loadAnnotations() {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellable = makeCancellable(page.getAnnotations());
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable.promise
|
||||
.then((nextAnnotations) => {
|
||||
annotationsDispatch({ type: 'RESOLVE', value: nextAnnotations });
|
||||
})
|
||||
.catch((error) => {
|
||||
annotationsDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelRunningTask(runningTask);
|
||||
};
|
||||
},
|
||||
[annotationsDispatch, page],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (annotations === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (annotations === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [annotations]);
|
||||
|
||||
function onRenderSuccess() {
|
||||
if (onRenderAnnotationLayerSuccessProps) {
|
||||
onRenderAnnotationLayerSuccessProps();
|
||||
}
|
||||
}
|
||||
|
||||
function onRenderError(error: unknown) {
|
||||
warning(false, `${error}`);
|
||||
|
||||
if (onRenderAnnotationLayerErrorProps) {
|
||||
onRenderAnnotationLayerErrorProps(error);
|
||||
}
|
||||
}
|
||||
|
||||
const viewport = useMemo(
|
||||
() => page.getViewport({ scale, rotation: rotate }),
|
||||
[page, rotate, scale],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(
|
||||
function renderAnnotationLayer() {
|
||||
if (!pdf || !page || !linkService || !annotations) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { current: layer } = layerElement;
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clonedViewport = viewport.clone({ dontFlip: true });
|
||||
|
||||
const annotationLayerParameters = {
|
||||
accessibilityManager: null, // TODO: Implement this
|
||||
annotationCanvasMap: null, // TODO: Implement this
|
||||
annotationEditorUIManager: null, // TODO: Implement this
|
||||
annotationStorage: pdf.annotationStorage,
|
||||
commentManager: null, // TODO: Implement this
|
||||
div: layer,
|
||||
l10n: null, // TODO: Implement this
|
||||
linkService,
|
||||
page,
|
||||
structTreeLayer: null, // TODO: Implement this
|
||||
viewport: clonedViewport,
|
||||
};
|
||||
|
||||
const renderParameters: AnnotationLayerParameters = {
|
||||
annotations: filterAnnotations ? filterAnnotations({ annotations }) : annotations,
|
||||
annotationStorage: pdf.annotationStorage,
|
||||
div: layer,
|
||||
imageResourcesPath,
|
||||
linkService,
|
||||
page,
|
||||
renderForms,
|
||||
viewport: clonedViewport,
|
||||
};
|
||||
|
||||
layer.innerHTML = '';
|
||||
|
||||
try {
|
||||
new pdfjs.AnnotationLayer(annotationLayerParameters).render(renderParameters);
|
||||
|
||||
// Intentional immediate callback
|
||||
onRenderSuccess();
|
||||
} catch (error) {
|
||||
onRenderError(error);
|
||||
}
|
||||
|
||||
return () => {
|
||||
// TODO: Cancel running task?
|
||||
};
|
||||
},
|
||||
[
|
||||
annotations,
|
||||
filterAnnotations,
|
||||
imageResourcesPath,
|
||||
linkService,
|
||||
page,
|
||||
pdf,
|
||||
renderForms,
|
||||
viewport,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={clsx('react-pdf__Page__annotations', 'annotationLayer')} ref={layerElement} />
|
||||
);
|
||||
}
|
||||
170
frontend/node_modules/react-pdf/src/Page/Canvas.tsx
generated
vendored
Normal file
170
frontend/node_modules/react-pdf/src/Page/Canvas.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import mergeRefs from 'merge-refs';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import StructTree from '../StructTree.js';
|
||||
|
||||
import usePageContext from '../shared/hooks/usePageContext.js';
|
||||
|
||||
import {
|
||||
cancelRunningTask,
|
||||
getDevicePixelRatio,
|
||||
isAbortException,
|
||||
makePageCallback,
|
||||
} from '../shared/utils.js';
|
||||
|
||||
import type { RenderParameters } from 'pdfjs-dist/types/src/display/api.js';
|
||||
|
||||
const ANNOTATION_MODE = pdfjs.AnnotationMode;
|
||||
|
||||
type CanvasProps = {
|
||||
canvasRef?: React.Ref<HTMLCanvasElement>;
|
||||
};
|
||||
|
||||
export default function Canvas(props: CanvasProps): React.ReactElement {
|
||||
const pageContext = usePageContext();
|
||||
|
||||
invariant(pageContext, 'Unable to find Page context.');
|
||||
|
||||
const mergedProps = { ...pageContext, ...props };
|
||||
const {
|
||||
_className,
|
||||
canvasBackground,
|
||||
devicePixelRatio = getDevicePixelRatio(),
|
||||
onRenderError: onRenderErrorProps,
|
||||
onRenderSuccess: onRenderSuccessProps,
|
||||
page,
|
||||
renderForms,
|
||||
renderTextLayer,
|
||||
pageColors,
|
||||
rotate,
|
||||
scale,
|
||||
} = mergedProps;
|
||||
const { canvasRef } = props;
|
||||
|
||||
invariant(page, 'Attempted to render page canvas, but no page was specified.');
|
||||
|
||||
const canvasElement = useRef<HTMLCanvasElement>(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: Error) {
|
||||
if (isAbortException(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: Omitted 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: RenderParameters = {
|
||||
annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE,
|
||||
canvas,
|
||||
canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D,
|
||||
pageColors,
|
||||
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, pageColors, 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 (
|
||||
<canvas
|
||||
className={`${_className}__canvas`}
|
||||
dir="ltr"
|
||||
ref={mergeRefs(canvasRef, canvasElement)}
|
||||
style={{
|
||||
display: 'block',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
>
|
||||
{renderTextLayer ? <StructTree /> : null}
|
||||
</canvas>
|
||||
);
|
||||
}
|
||||
119
frontend/node_modules/react-pdf/src/Page/TextLayer.css
generated
vendored
Normal file
119
frontend/node_modules/react-pdf/src/Page/TextLayer.css
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/* Copyright 2014 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
:root {
|
||||
--react-pdf-text-layer: 1;
|
||||
--highlight-bg-color: rgba(180, 0, 170, 1);
|
||||
--highlight-selected-bg-color: rgba(0, 100, 0, 1);
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
:root {
|
||||
--highlight-bg-color: Highlight;
|
||||
--highlight-selected-bg-color: ButtonText;
|
||||
}
|
||||
}
|
||||
|
||||
[data-main-rotation='90'] {
|
||||
transform: rotate(90deg) translateY(-100%);
|
||||
}
|
||||
[data-main-rotation='180'] {
|
||||
transform: rotate(180deg) translate(-100%, -100%);
|
||||
}
|
||||
[data-main-rotation='270'] {
|
||||
transform: rotate(270deg) translateX(-100%);
|
||||
}
|
||||
|
||||
.textLayer {
|
||||
position: absolute;
|
||||
text-align: initial;
|
||||
inset: 0;
|
||||
overflow: hidden;
|
||||
line-height: 1;
|
||||
text-size-adjust: none;
|
||||
forced-color-adjust: none;
|
||||
transform-origin: 0 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.textLayer :is(span, br) {
|
||||
color: transparent;
|
||||
position: absolute;
|
||||
white-space: pre;
|
||||
cursor: text;
|
||||
margin: 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
|
||||
/* Only necessary in Google Chrome, see issue 14205, and most unfortunately
|
||||
* the problem doesn't show up in "text" reference tests. */
|
||||
.textLayer span.markedContent {
|
||||
top: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.textLayer .highlight {
|
||||
margin: -1px;
|
||||
padding: 1px;
|
||||
background-color: var(--highlight-bg-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.appended {
|
||||
position: initial;
|
||||
}
|
||||
|
||||
.textLayer .highlight.begin {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.textLayer .highlight.end {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.textLayer .highlight.middle {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.textLayer .highlight.selected {
|
||||
background-color: var(--highlight-selected-bg-color);
|
||||
}
|
||||
|
||||
/* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
|
||||
.textLayer br::selection {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.textLayer .endOfContent {
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: 100% 0 0;
|
||||
z-index: -1;
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.textLayer.selecting .endOfContent {
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.hiddenCanvasElement {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
display: none;
|
||||
}
|
||||
353
frontend/node_modules/react-pdf/src/Page/TextLayer.tsx
generated
vendored
Normal file
353
frontend/node_modules/react-pdf/src/Page/TextLayer.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import * as pdfjs from 'pdfjs-dist';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import usePageContext from '../shared/hooks/usePageContext.js';
|
||||
import useResolver from '../shared/hooks/useResolver.js';
|
||||
|
||||
import { cancelRunningTask, isAbortException } from '../shared/utils.js';
|
||||
|
||||
import type { TextContent, TextItem, TextMarkedContent } from 'pdfjs-dist/types/src/display/api.js';
|
||||
|
||||
function isTextItem(item: TextItem | TextMarkedContent): item is TextItem {
|
||||
return 'str' in item;
|
||||
}
|
||||
|
||||
const BLOCKED_CUSTOM_TEXT_TAGS = new Set([
|
||||
'base',
|
||||
'embed',
|
||||
'iframe',
|
||||
'link',
|
||||
'meta',
|
||||
'object',
|
||||
'script',
|
||||
'style',
|
||||
'template',
|
||||
]);
|
||||
|
||||
const URL_ATTRIBUTES = new Set(['action', 'formaction', 'href', 'poster', 'src', 'xlink:href']);
|
||||
|
||||
function isDangerousUrl(value: string): boolean {
|
||||
let normalizedValue = '';
|
||||
|
||||
for (const char of value) {
|
||||
const charCode = char.charCodeAt(0);
|
||||
|
||||
if (charCode <= 32 || charCode === 127) {
|
||||
continue;
|
||||
}
|
||||
|
||||
normalizedValue += char.toLowerCase();
|
||||
}
|
||||
|
||||
return normalizedValue.startsWith('javascript:') || normalizedValue.startsWith('vbscript:');
|
||||
}
|
||||
|
||||
function isElementNode(node: Node): node is Element {
|
||||
return node.nodeType === Node.ELEMENT_NODE;
|
||||
}
|
||||
|
||||
function isHtmlElement(node: Node): node is HTMLElement {
|
||||
return isElementNode(node) && node instanceof HTMLElement;
|
||||
}
|
||||
|
||||
function isBlockedCustomTextElement(node: Node): boolean {
|
||||
return isHtmlElement(node) && BLOCKED_CUSTOM_TEXT_TAGS.has(node.tagName.toLowerCase());
|
||||
}
|
||||
|
||||
function sanitizeCustomHtmlElement(element: HTMLElement): HTMLElement {
|
||||
const sanitizedElement = document.createElement(element.tagName.toLowerCase());
|
||||
|
||||
Array.from(element.attributes).forEach((attribute) => {
|
||||
const attributeName = attribute.name.toLowerCase();
|
||||
|
||||
if (attributeName.startsWith('on') || attributeName === 'srcdoc') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (URL_ATTRIBUTES.has(attributeName) && isDangerousUrl(attribute.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
sanitizedElement.setAttribute(attribute.name, attribute.value);
|
||||
});
|
||||
|
||||
Array.from(element.childNodes).forEach((child) => {
|
||||
sanitizedElement.append(sanitizeCustomTextNode(child));
|
||||
});
|
||||
|
||||
return sanitizedElement;
|
||||
}
|
||||
|
||||
function sanitizeCustomTextNode(node: ChildNode): Node {
|
||||
if (isHtmlElement(node) && !isBlockedCustomTextElement(node)) {
|
||||
return sanitizeCustomHtmlElement(node);
|
||||
}
|
||||
|
||||
return document.createTextNode(node.textContent ?? '');
|
||||
}
|
||||
|
||||
function renderSafeCustomText(target: Element, content: string): void {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = content;
|
||||
|
||||
const sanitizedFragment = document.createDocumentFragment();
|
||||
|
||||
Array.from(template.content.childNodes).forEach((child) => {
|
||||
sanitizedFragment.append(sanitizeCustomTextNode(child));
|
||||
});
|
||||
|
||||
target.replaceChildren(sanitizedFragment);
|
||||
}
|
||||
|
||||
export default function TextLayer(): React.ReactElement {
|
||||
const pageContext = usePageContext();
|
||||
|
||||
invariant(pageContext, 'Unable to find Page context.');
|
||||
|
||||
const {
|
||||
customTextRenderer,
|
||||
onGetTextError,
|
||||
onGetTextSuccess,
|
||||
onRenderTextLayerError,
|
||||
onRenderTextLayerSuccess,
|
||||
page,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
rotate,
|
||||
scale,
|
||||
} = pageContext;
|
||||
|
||||
invariant(page, 'Attempted to load page text content, but no page was specified.');
|
||||
|
||||
const [textContentState, textContentDispatch] = useResolver<TextContent>();
|
||||
const { value: textContent, error: textContentError } = textContentState;
|
||||
const layerElement = useRef<HTMLDivElement>(null);
|
||||
|
||||
warning(
|
||||
Number.parseInt(
|
||||
window.getComputedStyle(document.body).getPropertyValue('--react-pdf-text-layer'),
|
||||
10,
|
||||
) === 1,
|
||||
'TextLayer styles not found. Read more: https://github.com/wojtekmaj/react-pdf#support-for-text-layer',
|
||||
);
|
||||
|
||||
/**
|
||||
* Called when a page text content is read successfully
|
||||
*/
|
||||
function onLoadSuccess() {
|
||||
if (!textContent) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
if (onGetTextSuccess) {
|
||||
onGetTextSuccess(textContent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a page text content failed to read successfully
|
||||
*/
|
||||
function onLoadError() {
|
||||
if (!textContentError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, textContentError.toString());
|
||||
|
||||
if (onGetTextError) {
|
||||
onGetTextError(textContentError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change
|
||||
useEffect(
|
||||
function resetTextContent() {
|
||||
textContentDispatch({ type: 'RESET' });
|
||||
},
|
||||
[page, textContentDispatch],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function loadTextContent() {
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellable = makeCancellable(page.getTextContent());
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable.promise
|
||||
.then((nextTextContent) => {
|
||||
textContentDispatch({ type: 'RESOLVE', value: nextTextContent });
|
||||
})
|
||||
.catch((error) => {
|
||||
textContentDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => cancelRunningTask(runningTask);
|
||||
},
|
||||
[page, textContentDispatch],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (textContent === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textContent === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [textContent]);
|
||||
|
||||
/**
|
||||
* Called when a text layer is rendered successfully
|
||||
*/
|
||||
const onRenderSuccess = useCallback(() => {
|
||||
if (onRenderTextLayerSuccess) {
|
||||
onRenderTextLayerSuccess();
|
||||
}
|
||||
}, [onRenderTextLayerSuccess]);
|
||||
|
||||
/**
|
||||
* Called when a text layer failed to render successfully
|
||||
*/
|
||||
const onRenderError = useCallback(
|
||||
(error: Error) => {
|
||||
if (isAbortException(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, error.toString());
|
||||
|
||||
if (onRenderTextLayerError) {
|
||||
onRenderTextLayerError(error);
|
||||
}
|
||||
},
|
||||
[onRenderTextLayerError],
|
||||
);
|
||||
|
||||
function onMouseDown() {
|
||||
const layer = layerElement.current;
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.classList.add('selecting');
|
||||
}
|
||||
|
||||
function onMouseUp() {
|
||||
const layer = layerElement.current;
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.classList.remove('selecting');
|
||||
}
|
||||
|
||||
const viewport = useMemo(
|
||||
() => page.getViewport({ scale, rotation: rotate }),
|
||||
[page, rotate, scale],
|
||||
);
|
||||
|
||||
useLayoutEffect(
|
||||
function renderTextLayer() {
|
||||
if (!page || !textContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { current: layer } = layerElement;
|
||||
|
||||
if (!layer) {
|
||||
return;
|
||||
}
|
||||
|
||||
layer.innerHTML = '';
|
||||
|
||||
const textContentSource = page.streamTextContent({ includeMarkedContent: true });
|
||||
|
||||
const parameters = {
|
||||
container: layer,
|
||||
textContentSource,
|
||||
viewport,
|
||||
};
|
||||
|
||||
const cancellable = new pdfjs.TextLayer(parameters);
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable
|
||||
.render()
|
||||
.then(() => {
|
||||
const end = document.createElement('div');
|
||||
end.className = 'endOfContent';
|
||||
layer.append(end);
|
||||
|
||||
const layerChildren = layer.querySelectorAll('[role="presentation"]');
|
||||
|
||||
if (customTextRenderer) {
|
||||
let index = 0;
|
||||
textContent.items.forEach((item, itemIndex) => {
|
||||
if (!isTextItem(item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const child = layerChildren[index];
|
||||
|
||||
if (!child) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = customTextRenderer({
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
itemIndex,
|
||||
...item,
|
||||
});
|
||||
|
||||
renderSafeCustomText(child, content);
|
||||
index += item.str && item.hasEOL ? 2 : 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Intentional immediate callback
|
||||
onRenderSuccess();
|
||||
})
|
||||
.catch(onRenderError);
|
||||
|
||||
return () => cancelRunningTask(runningTask);
|
||||
},
|
||||
[
|
||||
customTextRenderer,
|
||||
onRenderError,
|
||||
onRenderSuccess,
|
||||
page,
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
textContent,
|
||||
viewport,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
// biome-ignore lint/a11y/noStaticElementInteractions: False positive caused by non interactive wrapper listening for bubbling events
|
||||
<div
|
||||
className={clsx('react-pdf__Page__textContent', 'textLayer')}
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseDown={onMouseDown}
|
||||
ref={layerElement}
|
||||
/>
|
||||
);
|
||||
}
|
||||
9
frontend/node_modules/react-pdf/src/PageContext.tsx
generated
vendored
Normal file
9
frontend/node_modules/react-pdf/src/PageContext.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
'use client';
|
||||
|
||||
import { createContext } from 'react';
|
||||
|
||||
import type { PageContextType } from './shared/types.js';
|
||||
|
||||
const pageContext: React.Context<PageContextType> = createContext<PageContextType>(null);
|
||||
|
||||
export default pageContext;
|
||||
8
frontend/node_modules/react-pdf/src/PasswordResponses.ts
generated
vendored
Normal file
8
frontend/node_modules/react-pdf/src/PasswordResponses.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// As defined in https://github.com/mozilla/pdf.js/blob/d9fac3459609a807be6506fb3441b5da4b154d14/src/shared/util.js#L371-L374
|
||||
|
||||
const PasswordResponses = {
|
||||
NEED_PASSWORD: 1,
|
||||
INCORRECT_PASSWORD: 2,
|
||||
} as const;
|
||||
|
||||
export default PasswordResponses;
|
||||
17
frontend/node_modules/react-pdf/src/Ref.ts
generated
vendored
Normal file
17
frontend/node_modules/react-pdf/src/Ref.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
export default class Ref {
|
||||
num: number;
|
||||
gen: number;
|
||||
|
||||
constructor({ num, gen }: { num: number; gen: number }) {
|
||||
this.num = num;
|
||||
this.gen = gen;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
let str = `${this.num}R`;
|
||||
if (this.gen !== 0) {
|
||||
str += this.gen;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
108
frontend/node_modules/react-pdf/src/StructTree.tsx
generated
vendored
Normal file
108
frontend/node_modules/react-pdf/src/StructTree.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { useEffect } from 'react';
|
||||
import makeCancellable from 'make-cancellable-promise';
|
||||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import StructTreeItem from './StructTreeItem.js';
|
||||
|
||||
import usePageContext from './shared/hooks/usePageContext.js';
|
||||
import useResolver from './shared/hooks/useResolver.js';
|
||||
|
||||
import { cancelRunningTask } from './shared/utils.js';
|
||||
|
||||
import type { StructTreeNodeWithExtraAttributes } from './shared/types.js';
|
||||
|
||||
export default function StructTree(): React.ReactElement | null {
|
||||
const pageContext = usePageContext();
|
||||
|
||||
invariant(pageContext, 'Unable to find Page context.');
|
||||
|
||||
const {
|
||||
onGetStructTreeError: onGetStructTreeErrorProps,
|
||||
onGetStructTreeSuccess: onGetStructTreeSuccessProps,
|
||||
} = pageContext;
|
||||
|
||||
const [structTreeState, structTreeDispatch] = useResolver<StructTreeNodeWithExtraAttributes>();
|
||||
const { value: structTree, error: structTreeError } = structTreeState;
|
||||
|
||||
const { customTextRenderer, page } = pageContext;
|
||||
|
||||
function onLoadSuccess() {
|
||||
if (!structTree) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
if (onGetStructTreeSuccessProps) {
|
||||
onGetStructTreeSuccessProps(structTree);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoadError() {
|
||||
if (!structTreeError) {
|
||||
// Impossible, but TypeScript doesn't know that
|
||||
return;
|
||||
}
|
||||
|
||||
warning(false, structTreeError.toString());
|
||||
|
||||
if (onGetStructTreeErrorProps) {
|
||||
onGetStructTreeErrorProps(structTreeError);
|
||||
}
|
||||
}
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: useEffect intentionally triggered on page change
|
||||
useEffect(
|
||||
function resetStructTree() {
|
||||
structTreeDispatch({ type: 'RESET' });
|
||||
},
|
||||
[structTreeDispatch, page],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
function loadStructTree() {
|
||||
if (customTextRenderer) {
|
||||
// TODO: Document why this is necessary
|
||||
return;
|
||||
}
|
||||
|
||||
if (!page) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellable = makeCancellable(page.getStructTree());
|
||||
const runningTask = cancellable;
|
||||
|
||||
cancellable.promise
|
||||
.then((nextStructTree) => {
|
||||
structTreeDispatch({ type: 'RESOLVE', value: nextStructTree });
|
||||
})
|
||||
.catch((error) => {
|
||||
structTreeDispatch({ type: 'REJECT', error });
|
||||
});
|
||||
|
||||
return () => cancelRunningTask(runningTask);
|
||||
},
|
||||
[customTextRenderer, page, structTreeDispatch],
|
||||
);
|
||||
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: Omitted callbacks so they are not called every time they change
|
||||
useEffect(() => {
|
||||
if (structTree === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (structTree === false) {
|
||||
onLoadError();
|
||||
return;
|
||||
}
|
||||
|
||||
onLoadSuccess();
|
||||
}, [structTree]);
|
||||
|
||||
if (!structTree) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <StructTreeItem className="react-pdf__Page__structTree structTree" node={structTree} />;
|
||||
}
|
||||
45
frontend/node_modules/react-pdf/src/StructTreeItem.tsx
generated
vendored
Normal file
45
frontend/node_modules/react-pdf/src/StructTreeItem.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
getAttributes,
|
||||
isStructTreeNode,
|
||||
isStructTreeNodeWithOnlyContentChild,
|
||||
} from './shared/structTreeUtils.js';
|
||||
|
||||
import type { StructTreeContent } from 'pdfjs-dist/types/src/display/api.js';
|
||||
import type { StructTreeNodeWithExtraAttributes } from './shared/types.js';
|
||||
|
||||
type StructTreeItemProps = {
|
||||
className?: string;
|
||||
node: StructTreeNodeWithExtraAttributes | StructTreeContent;
|
||||
};
|
||||
|
||||
export default function StructTreeItem({
|
||||
className,
|
||||
node,
|
||||
}: StructTreeItemProps): React.ReactElement {
|
||||
const attributes = useMemo(() => getAttributes(node), [node]);
|
||||
|
||||
const children = useMemo(() => {
|
||||
if (!isStructTreeNode(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isStructTreeNodeWithOnlyContentChild(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.children.map((child, index) => {
|
||||
return (
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: index is stable here
|
||||
<StructTreeItem key={index} node={child} />
|
||||
);
|
||||
});
|
||||
}, [node]);
|
||||
|
||||
return (
|
||||
<span className={className} {...attributes}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
112
frontend/node_modules/react-pdf/src/Thumbnail.tsx
generated
vendored
Normal file
112
frontend/node_modules/react-pdf/src/Thumbnail.tsx
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
import Page from './Page.js';
|
||||
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
|
||||
import { isProvided } from './shared/utils.js';
|
||||
|
||||
import type { PageProps } from './Page.js';
|
||||
import type { ClassName, OnItemClickArgs } from './shared/types.js';
|
||||
|
||||
export type ThumbnailProps = Omit<
|
||||
PageProps,
|
||||
| 'className'
|
||||
| 'customTextRenderer'
|
||||
| 'onGetAnnotationsError'
|
||||
| 'onGetAnnotationsSuccess'
|
||||
| 'onGetTextError'
|
||||
| 'onGetTextSuccess'
|
||||
| 'onRenderAnnotationLayerError'
|
||||
| 'onRenderAnnotationLayerSuccess'
|
||||
| 'onRenderTextLayerError'
|
||||
| 'onRenderTextLayerSuccess'
|
||||
| 'renderAnnotationLayer'
|
||||
| 'renderForms'
|
||||
| 'renderTextLayer'
|
||||
> & {
|
||||
/**
|
||||
* Class name(s) that will be added to rendered element along with the default `react-pdf__Thumbnail`.
|
||||
*
|
||||
* @example 'custom-class-name-1 custom-class-name-2'
|
||||
* @example ['custom-class-name-1', 'custom-class-name-2']
|
||||
*/
|
||||
className?: ClassName;
|
||||
/**
|
||||
* Function called when a thumbnail has been clicked. Usually, you would like to use this callback to move the user wherever they requested to.
|
||||
*
|
||||
* @example ({ dest, pageIndex, pageNumber }) => alert('Clicked an item from page ' + pageNumber + '!')
|
||||
*/
|
||||
onItemClick?: (args: OnItemClickArgs) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Displays a thumbnail of a page. Does not render the annotation layer or the text layer. Does not register itself as a link target, so the user will not be scrolled to a Thumbnail component when clicked on an internal link (e.g. in Table of Contents). When clicked, attempts to navigate to the page clicked (similarly to a link in Outline).
|
||||
*
|
||||
* Should be placed inside `<Document />`. Alternatively, it can have `pdf` prop passed, which can be obtained from `<Document />`'s `onLoadSuccess` callback function.
|
||||
*/
|
||||
export default function Thumbnail(props: ThumbnailProps): React.ReactElement {
|
||||
const documentContext = useDocumentContext();
|
||||
|
||||
const mergedProps = { ...documentContext, ...props };
|
||||
const {
|
||||
className,
|
||||
linkService,
|
||||
onItemClick,
|
||||
pageIndex: pageIndexProps,
|
||||
pageNumber: pageNumberProps,
|
||||
pdf,
|
||||
} = mergedProps;
|
||||
|
||||
invariant(
|
||||
pdf,
|
||||
'Attempted to load a thumbnail, but no document was specified. Wrap <Thumbnail /> in a <Document /> or pass explicit `pdf` prop.',
|
||||
);
|
||||
|
||||
const pageIndex = isProvided(pageNumberProps) ? pageNumberProps - 1 : (pageIndexProps ?? null);
|
||||
|
||||
const pageNumber = pageNumberProps ?? (isProvided(pageIndexProps) ? pageIndexProps + 1 : null);
|
||||
|
||||
function onClick(event: React.MouseEvent<HTMLAnchorElement>) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!isProvided(pageIndex) || !pageNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
invariant(
|
||||
onItemClick || linkService,
|
||||
'Either onItemClick callback or linkService must be defined in order to navigate to an outline item.',
|
||||
);
|
||||
|
||||
if (onItemClick) {
|
||||
onItemClick({
|
||||
pageIndex,
|
||||
pageNumber,
|
||||
});
|
||||
} else if (linkService) {
|
||||
linkService.goToPage(pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
const { className: classNameProps, onItemClick: onItemClickProps, ...pageProps } = props;
|
||||
|
||||
return (
|
||||
<a
|
||||
className={clsx('react-pdf__Thumbnail', className)}
|
||||
href={pageNumber ? '#' : undefined}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Page
|
||||
{...pageProps}
|
||||
_className="react-pdf__Thumbnail__page"
|
||||
_enableRegisterUnregisterPage={false}
|
||||
renderAnnotationLayer={false}
|
||||
renderTextLayer={false}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
28
frontend/node_modules/react-pdf/src/index.test.ts
generated
vendored
Normal file
28
frontend/node_modules/react-pdf/src/index.test.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import * as pdfjs from 'pdfjs-dist';
|
||||
|
||||
import Document from './Document.js';
|
||||
import Outline from './Outline.js';
|
||||
import Page from './Page.js';
|
||||
import Thumbnail from './Thumbnail.js';
|
||||
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
import useOutlineContext from './shared/hooks/useOutlineContext.js';
|
||||
import usePageContext from './shared/hooks/usePageContext.js';
|
||||
|
||||
export type { DocumentProps } from './Document.js';
|
||||
export type { OutlineProps } from './Outline.js';
|
||||
export type { PageProps } from './Page.js';
|
||||
export type { ThumbnailProps } from './Thumbnail.js';
|
||||
|
||||
import './pdf.worker.entry.js';
|
||||
|
||||
export {
|
||||
pdfjs,
|
||||
Document,
|
||||
Outline,
|
||||
Page,
|
||||
Thumbnail,
|
||||
useDocumentContext,
|
||||
useOutlineContext,
|
||||
usePageContext,
|
||||
};
|
||||
44
frontend/node_modules/react-pdf/src/index.ts
generated
vendored
Normal file
44
frontend/node_modules/react-pdf/src/index.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import * as pdfjs from 'pdfjs-dist';
|
||||
|
||||
import Document from './Document.js';
|
||||
import Outline from './Outline.js';
|
||||
import Page from './Page.js';
|
||||
import PasswordResponses from './PasswordResponses.js';
|
||||
import Thumbnail from './Thumbnail.js';
|
||||
|
||||
import useDocumentContext from './shared/hooks/useDocumentContext.js';
|
||||
import useOutlineContext from './shared/hooks/useOutlineContext.js';
|
||||
import usePageContext from './shared/hooks/usePageContext.js';
|
||||
|
||||
import type LinkService from './LinkService.js';
|
||||
|
||||
export type { DocumentProps } from './Document.js';
|
||||
export type { OutlineProps } from './Outline.js';
|
||||
export type { PageProps } from './Page.js';
|
||||
export type {
|
||||
PasswordResponses as PasswordResponsesType,
|
||||
StructTreeNode,
|
||||
TextContent,
|
||||
TextItem,
|
||||
TextMarkedContent,
|
||||
} from './shared/types.js';
|
||||
export type { ThumbnailProps } from './Thumbnail.js';
|
||||
export type { LinkService };
|
||||
|
||||
import { displayWorkerWarning } from './shared/utils.js';
|
||||
|
||||
displayWorkerWarning();
|
||||
|
||||
pdfjs.GlobalWorkerOptions.workerSrc = 'pdf.worker.mjs';
|
||||
|
||||
export {
|
||||
pdfjs,
|
||||
Document,
|
||||
Outline,
|
||||
Page,
|
||||
Thumbnail,
|
||||
useDocumentContext,
|
||||
useOutlineContext,
|
||||
usePageContext,
|
||||
PasswordResponses,
|
||||
};
|
||||
15
frontend/node_modules/react-pdf/src/pdf.worker.entry.ts
generated
vendored
Normal file
15
frontend/node_modules/react-pdf/src/pdf.worker.entry.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* PDF.js worker entry file.
|
||||
*
|
||||
* This file is identical to Mozilla's pdf.worker.entry.js, with one exception being placed inside
|
||||
* this bundle, not theirs.
|
||||
*/
|
||||
|
||||
(
|
||||
(typeof window !== 'undefined' ? window : {}) as Window &
|
||||
typeof globalThis & { pdfjsWorker: unknown }
|
||||
).pdfjsWorker =
|
||||
// @ts-expect-error - pdfjs-dist does not ship with types
|
||||
await import('pdfjs-dist/build/pdf.worker.mjs');
|
||||
|
||||
export {};
|
||||
59
frontend/node_modules/react-pdf/src/shared/constants.ts
generated
vendored
Normal file
59
frontend/node_modules/react-pdf/src/shared/constants.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// From pdfjs-dist/lib/web/struct_tree_layer_builder.js
|
||||
|
||||
export const PDF_ROLE_TO_HTML_ROLE = {
|
||||
// Document level structure types
|
||||
Document: null, // There's a "document" role, but it doesn't make sense here.
|
||||
DocumentFragment: null,
|
||||
// Grouping level structure types
|
||||
Part: 'group',
|
||||
Sect: 'group', // XXX: There's a "section" role, but it's abstract.
|
||||
Div: 'group',
|
||||
Aside: 'note',
|
||||
NonStruct: 'none',
|
||||
// Block level structure types
|
||||
P: null,
|
||||
// H<n>,
|
||||
H: 'heading',
|
||||
Title: null,
|
||||
FENote: 'note',
|
||||
// Sub-block level structure type
|
||||
Sub: 'group',
|
||||
// General inline level structure types
|
||||
Lbl: null,
|
||||
Span: null,
|
||||
Em: null,
|
||||
Strong: null,
|
||||
Link: 'link',
|
||||
Annot: 'note',
|
||||
Form: 'form',
|
||||
// Ruby and Warichu structure types
|
||||
Ruby: null,
|
||||
RB: null,
|
||||
RT: null,
|
||||
RP: null,
|
||||
Warichu: null,
|
||||
WT: null,
|
||||
WP: null,
|
||||
// List standard structure types
|
||||
L: 'list',
|
||||
LI: 'listitem',
|
||||
LBody: null,
|
||||
// Table standard structure types
|
||||
Table: 'table',
|
||||
TR: 'row',
|
||||
TH: 'columnheader',
|
||||
TD: 'cell',
|
||||
THead: 'columnheader',
|
||||
TBody: null,
|
||||
TFoot: null,
|
||||
// Standard structure type Caption
|
||||
Caption: null,
|
||||
// Standard structure type Figure
|
||||
Figure: 'figure',
|
||||
// Standard structure type Formula
|
||||
Formula: null,
|
||||
// standard structure type Artifact
|
||||
Artifact: null,
|
||||
};
|
||||
|
||||
export const HEADING_PATTERN: RegExp = /^H(\d+)$/;
|
||||
23
frontend/node_modules/react-pdf/src/shared/hooks/useCachedValue.ts
generated
vendored
Normal file
23
frontend/node_modules/react-pdf/src/shared/hooks/useCachedValue.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
'use client';
|
||||
|
||||
import { useRef } from 'react';
|
||||
|
||||
import { isDefined } from '../utils.js';
|
||||
|
||||
export default function useCachedValue<T>(getter: () => T): () => T {
|
||||
const ref = useRef<T | undefined>(undefined);
|
||||
|
||||
const currentValue = ref.current;
|
||||
|
||||
if (isDefined(currentValue)) {
|
||||
return () => currentValue;
|
||||
}
|
||||
|
||||
return () => {
|
||||
const value = getter();
|
||||
|
||||
ref.current = value;
|
||||
|
||||
return value;
|
||||
};
|
||||
}
|
||||
9
frontend/node_modules/react-pdf/src/shared/hooks/useDocumentContext.ts
generated
vendored
Normal file
9
frontend/node_modules/react-pdf/src/shared/hooks/useDocumentContext.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { useContext } from 'react';
|
||||
|
||||
import DocumentContext from '../../DocumentContext.js';
|
||||
|
||||
import type { DocumentContextType } from '../types.js';
|
||||
|
||||
export default function useDocumentContext(): DocumentContextType {
|
||||
return useContext(DocumentContext);
|
||||
}
|
||||
9
frontend/node_modules/react-pdf/src/shared/hooks/useOutlineContext.ts
generated
vendored
Normal file
9
frontend/node_modules/react-pdf/src/shared/hooks/useOutlineContext.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { useContext } from 'react';
|
||||
|
||||
import OutlineContext from '../../OutlineContext.js';
|
||||
|
||||
import type { OutlineContextType } from '../types.js';
|
||||
|
||||
export default function useOutlineContext(): OutlineContextType {
|
||||
return useContext(OutlineContext);
|
||||
}
|
||||
9
frontend/node_modules/react-pdf/src/shared/hooks/usePageContext.ts
generated
vendored
Normal file
9
frontend/node_modules/react-pdf/src/shared/hooks/usePageContext.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { useContext } from 'react';
|
||||
|
||||
import PageContext from '../../PageContext.js';
|
||||
|
||||
import type { PageContextType } from '../types.js';
|
||||
|
||||
export default function usePageContext(): PageContextType {
|
||||
return useContext(PageContext);
|
||||
}
|
||||
28
frontend/node_modules/react-pdf/src/shared/hooks/useResolver.ts
generated
vendored
Normal file
28
frontend/node_modules/react-pdf/src/shared/hooks/useResolver.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { useReducer } from 'react';
|
||||
|
||||
type State<T> =
|
||||
| { value: T; error: undefined }
|
||||
| { value: false; error: Error }
|
||||
| { value: undefined; error: undefined };
|
||||
|
||||
type Action<T> =
|
||||
| { type: 'RESOLVE'; value: T }
|
||||
| { type: 'REJECT'; error: Error }
|
||||
| { type: 'RESET' };
|
||||
|
||||
function reducer<T>(state: State<T>, action: Action<T>): State<T> {
|
||||
switch (action.type) {
|
||||
case 'RESOLVE':
|
||||
return { value: action.value, error: undefined };
|
||||
case 'REJECT':
|
||||
return { value: false, error: action.error };
|
||||
case 'RESET':
|
||||
return { value: undefined, error: undefined };
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export default function useResolver<T>(): [State<T>, React.Dispatch<Action<T>>] {
|
||||
return useReducer(reducer<T>, { value: undefined, error: undefined });
|
||||
}
|
||||
95
frontend/node_modules/react-pdf/src/shared/structTreeUtils.ts
generated
vendored
Normal file
95
frontend/node_modules/react-pdf/src/shared/structTreeUtils.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { HEADING_PATTERN, PDF_ROLE_TO_HTML_ROLE } from './constants.js';
|
||||
|
||||
import type { StructTreeContent, StructTreeNode } from 'pdfjs-dist/types/src/display/api.js';
|
||||
import type { StructTreeNodeWithExtraAttributes } from './types.js';
|
||||
|
||||
type PdfRole = keyof typeof PDF_ROLE_TO_HTML_ROLE;
|
||||
|
||||
type Attributes = React.HTMLAttributes<HTMLElement>;
|
||||
|
||||
export function isPdfRole(role: string): role is PdfRole {
|
||||
return role in PDF_ROLE_TO_HTML_ROLE;
|
||||
}
|
||||
|
||||
export function isStructTreeNode(node: StructTreeNode | StructTreeContent): node is StructTreeNode {
|
||||
return 'children' in node;
|
||||
}
|
||||
|
||||
export function isStructTreeNodeWithOnlyContentChild(
|
||||
node: StructTreeNode | StructTreeContent,
|
||||
): boolean {
|
||||
if (!isStructTreeNode(node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return node.children.length === 1 && 0 in node.children && 'id' in node.children[0];
|
||||
}
|
||||
|
||||
export function getRoleAttributes(node: StructTreeNode | StructTreeContent): Attributes {
|
||||
const attributes: Attributes = {};
|
||||
|
||||
if (isStructTreeNode(node)) {
|
||||
const { role } = node;
|
||||
|
||||
const matches = role.match(HEADING_PATTERN);
|
||||
|
||||
if (matches) {
|
||||
attributes.role = 'heading';
|
||||
attributes['aria-level'] = Number(matches[1]);
|
||||
} else if (isPdfRole(role)) {
|
||||
const htmlRole = PDF_ROLE_TO_HTML_ROLE[role];
|
||||
|
||||
if (htmlRole) {
|
||||
attributes.role = htmlRole;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
export function getBaseAttributes(
|
||||
node: StructTreeNodeWithExtraAttributes | StructTreeContent,
|
||||
): Attributes {
|
||||
const attributes: Attributes = {};
|
||||
|
||||
if (isStructTreeNode(node)) {
|
||||
if (node.alt !== undefined) {
|
||||
attributes['aria-label'] = node.alt;
|
||||
}
|
||||
|
||||
if (node.lang !== undefined) {
|
||||
attributes.lang = node.lang;
|
||||
}
|
||||
|
||||
if (isStructTreeNodeWithOnlyContentChild(node)) {
|
||||
const [child] = node.children;
|
||||
|
||||
if (child) {
|
||||
const childAttributes = getBaseAttributes(child);
|
||||
|
||||
return {
|
||||
...attributes,
|
||||
...childAttributes,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if ('id' in node) {
|
||||
attributes['aria-owns'] = node.id;
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
export function getAttributes(
|
||||
node: StructTreeNodeWithExtraAttributes | StructTreeContent,
|
||||
): Attributes | null {
|
||||
if (!node) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...getRoleAttributes(node),
|
||||
...getBaseAttributes(node),
|
||||
};
|
||||
}
|
||||
198
frontend/node_modules/react-pdf/src/shared/types.ts
generated
vendored
Normal file
198
frontend/node_modules/react-pdf/src/shared/types.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
import type {
|
||||
PasswordResponses,
|
||||
PDFDataRangeTransport,
|
||||
PDFDocumentProxy,
|
||||
PDFPageProxy,
|
||||
} from 'pdfjs-dist';
|
||||
import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/annotation_layer.js';
|
||||
import type {
|
||||
DocumentInitParameters,
|
||||
RefProxy,
|
||||
StructTreeNode,
|
||||
TextContent,
|
||||
TextItem,
|
||||
TextMarkedContent,
|
||||
TypedArray,
|
||||
} from 'pdfjs-dist/types/src/display/api.js';
|
||||
import type LinkService from '../LinkService.js';
|
||||
|
||||
export type { PasswordResponses, StructTreeNode, TextContent, TextItem, TextMarkedContent };
|
||||
|
||||
type NullableObject<T extends object> = { [P in keyof T]: T[P] | null };
|
||||
|
||||
type KeyOfUnion<T> = T extends unknown ? keyof T : never;
|
||||
|
||||
/* Primitive types */
|
||||
export type Annotations = AnnotationLayerParameters['annotations'];
|
||||
|
||||
export type ClassName = string | null | undefined | (string | null | undefined)[];
|
||||
|
||||
export type ResolvedDest = (RefProxy | number)[];
|
||||
|
||||
export type Dest = Promise<ResolvedDest> | ResolvedDest | string | null;
|
||||
|
||||
export type ExternalLinkRel = string;
|
||||
|
||||
export type ExternalLinkTarget = '_self' | '_blank' | '_parent' | '_top';
|
||||
|
||||
export type FilterAnnotationsArgs = {
|
||||
annotations: Annotations;
|
||||
};
|
||||
|
||||
export type ImageResourcesPath = string;
|
||||
|
||||
export type OnError = (error: Error) => void;
|
||||
|
||||
export type OnItemClickArgs = {
|
||||
dest?: Dest;
|
||||
pageIndex: number;
|
||||
pageNumber: number;
|
||||
};
|
||||
|
||||
export type OnLoadProgressArgs = {
|
||||
loaded: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type RegisterPage = (pageIndex: number, ref: HTMLDivElement) => void;
|
||||
|
||||
export type RenderMode = 'canvas' | 'custom' | 'none';
|
||||
|
||||
export type ScrollPageIntoViewArgs = {
|
||||
dest?: ResolvedDest;
|
||||
pageIndex?: number;
|
||||
pageNumber: number;
|
||||
};
|
||||
|
||||
type BinaryData = TypedArray | ArrayBuffer | number[] | string;
|
||||
|
||||
export type Source =
|
||||
| { data: BinaryData | undefined }
|
||||
| { range: PDFDataRangeTransport }
|
||||
| { url: string };
|
||||
|
||||
export type UnregisterPage = (pageIndex: number) => void;
|
||||
|
||||
/* Complex types */
|
||||
export type CustomRenderer = React.FunctionComponent | React.ComponentClass;
|
||||
|
||||
export type CustomTextRenderer = (
|
||||
props: { pageIndex: number; pageNumber: number; itemIndex: number } & TextItem,
|
||||
) => string;
|
||||
|
||||
export type DocumentCallback = PDFDocumentProxy;
|
||||
|
||||
export type File = string | ArrayBuffer | Blob | Source | null;
|
||||
|
||||
export type PageCallback = PDFPageProxy & {
|
||||
width: number;
|
||||
height: number;
|
||||
originalWidth: number;
|
||||
originalHeight: number;
|
||||
};
|
||||
|
||||
export type NodeOrRenderer = React.ReactNode | (() => React.ReactNode);
|
||||
|
||||
export type FilterAnnotations = (args: FilterAnnotationsArgs) => Annotations;
|
||||
|
||||
export type OnDocumentLoadError = OnError;
|
||||
|
||||
export type OnDocumentLoadProgress = (args: OnLoadProgressArgs) => void;
|
||||
|
||||
export type OnDocumentLoadSuccess = (document: DocumentCallback) => void;
|
||||
|
||||
export type OnGetAnnotationsError = OnError;
|
||||
|
||||
export type OnGetAnnotationsSuccess = (annotations: Annotations) => void;
|
||||
|
||||
export type OnGetStructTreeError = OnError;
|
||||
|
||||
export type OnGetStructTreeSuccess = (tree: StructTreeNode) => void;
|
||||
|
||||
export type OnGetTextError = OnError;
|
||||
|
||||
export type OnGetTextSuccess = (textContent: TextContent) => void;
|
||||
|
||||
export type OnPageLoadError = OnError;
|
||||
|
||||
export type OnPageLoadSuccess = (page: PageCallback) => void;
|
||||
|
||||
export type OnPasswordCallback = (password: string | null) => void;
|
||||
|
||||
export type OnRenderAnnotationLayerError = (error: unknown) => void;
|
||||
|
||||
export type OnRenderAnnotationLayerSuccess = () => void;
|
||||
|
||||
export type OnRenderError = OnError;
|
||||
|
||||
export type OnRenderSuccess = (page: PageCallback) => void;
|
||||
|
||||
export type OnRenderTextLayerError = OnError;
|
||||
|
||||
export type OnRenderTextLayerSuccess = () => void;
|
||||
|
||||
export type PasswordResponse = (typeof PasswordResponses)[keyof typeof PasswordResponses];
|
||||
|
||||
export type Options = NullableObject<Omit<DocumentInitParameters, KeyOfUnion<Source>>>;
|
||||
|
||||
export type PageColors = {
|
||||
background: string;
|
||||
foreground: string;
|
||||
};
|
||||
|
||||
/* Context types */
|
||||
export type DocumentContextType = {
|
||||
imageResourcesPath?: ImageResourcesPath;
|
||||
linkService: LinkService;
|
||||
onItemClick?: (args: OnItemClickArgs) => void;
|
||||
pdf?: PDFDocumentProxy | false;
|
||||
registerPage: RegisterPage;
|
||||
renderMode?: RenderMode;
|
||||
rotate?: number | null;
|
||||
unregisterPage: UnregisterPage;
|
||||
} | null;
|
||||
|
||||
export type PageContextType = {
|
||||
_className?: string;
|
||||
canvasBackground?: string;
|
||||
customTextRenderer?: CustomTextRenderer;
|
||||
devicePixelRatio?: number;
|
||||
filterAnnotations?: FilterAnnotations;
|
||||
onGetAnnotationsError?: OnGetAnnotationsError;
|
||||
onGetAnnotationsSuccess?: OnGetAnnotationsSuccess;
|
||||
onGetStructTreeError?: OnGetStructTreeError;
|
||||
onGetStructTreeSuccess?: OnGetStructTreeSuccess;
|
||||
onGetTextError?: OnGetTextError;
|
||||
onGetTextSuccess?: OnGetTextSuccess;
|
||||
onRenderAnnotationLayerError?: OnRenderAnnotationLayerError;
|
||||
onRenderAnnotationLayerSuccess?: OnRenderAnnotationLayerSuccess;
|
||||
onRenderError?: OnRenderError;
|
||||
onRenderSuccess?: OnRenderSuccess;
|
||||
onRenderTextLayerError?: OnRenderTextLayerError;
|
||||
onRenderTextLayerSuccess?: OnRenderTextLayerSuccess;
|
||||
page: PDFPageProxy | false | undefined;
|
||||
pageColors?: PageColors;
|
||||
pageIndex: number;
|
||||
pageNumber: number;
|
||||
renderForms: boolean;
|
||||
renderTextLayer: boolean;
|
||||
rotate: number;
|
||||
scale: number;
|
||||
} | null;
|
||||
|
||||
export type OutlineContextType = {
|
||||
onItemClick?: (args: OnItemClickArgs) => void;
|
||||
} | null;
|
||||
|
||||
export type StructTreeNodeWithExtraAttributes = StructTreeNode & {
|
||||
alt?: string;
|
||||
lang?: string;
|
||||
};
|
||||
|
||||
export type DocumentRenderProps = Omit<NonNullable<DocumentContextType>, 'pdf'> & {
|
||||
pdf: PDFDocumentProxy;
|
||||
};
|
||||
|
||||
export type PageRenderProps = Omit<NonNullable<PageContextType>, 'page'> & {
|
||||
page: PDFPageProxy;
|
||||
};
|
||||
180
frontend/node_modules/react-pdf/src/shared/utils.ts
generated
vendored
Normal file
180
frontend/node_modules/react-pdf/src/shared/utils.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import invariant from 'tiny-invariant';
|
||||
import warning from 'warning';
|
||||
|
||||
import type { PDFPageProxy } from 'pdfjs-dist';
|
||||
import type { PageCallback } from './types.js';
|
||||
|
||||
/**
|
||||
* Checks if we're running in a browser environment.
|
||||
*/
|
||||
export const isBrowser: boolean = typeof window !== 'undefined';
|
||||
|
||||
/**
|
||||
* Checks whether we're running from a local file system.
|
||||
*/
|
||||
export const isLocalFileSystem: boolean = isBrowser && window.location.protocol === 'file:';
|
||||
|
||||
/**
|
||||
* Checks whether a variable is defined.
|
||||
*
|
||||
* @param {*} variable Variable to check
|
||||
*/
|
||||
export function isDefined<T>(variable: T | undefined): variable is T {
|
||||
return typeof variable !== 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a variable is defined and not null.
|
||||
*
|
||||
* @param {*} variable Variable to check
|
||||
*/
|
||||
export function isProvided<T>(variable: T | null | undefined): variable is T {
|
||||
return isDefined(variable) && variable !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a variable provided is a string.
|
||||
*
|
||||
* @param {*} variable Variable to check
|
||||
*/
|
||||
export function isString(variable: unknown): variable is string {
|
||||
return typeof variable === 'string';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a variable provided is an ArrayBuffer.
|
||||
*
|
||||
* @param {*} variable Variable to check
|
||||
*/
|
||||
export function isArrayBuffer(variable: unknown): variable is ArrayBuffer {
|
||||
return variable instanceof ArrayBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a variable provided is a Blob.
|
||||
*
|
||||
* @param {*} variable Variable to check
|
||||
*/
|
||||
export function isBlob(variable: unknown): variable is Blob {
|
||||
invariant(isBrowser, 'isBlob can only be used in a browser environment');
|
||||
|
||||
return variable instanceof Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a variable provided is a data URI.
|
||||
*
|
||||
* @param {*} variable String to check
|
||||
*/
|
||||
export function isDataURI(variable: unknown): variable is `data:${string}` {
|
||||
return isString(variable) && /^data:/.test(variable);
|
||||
}
|
||||
|
||||
export function dataURItoByteString(dataURI: unknown): string {
|
||||
invariant(isDataURI(dataURI), 'Invalid data URI.');
|
||||
|
||||
const [headersString = '', dataString = ''] = dataURI.split(',');
|
||||
const headers = headersString.split(';');
|
||||
|
||||
if (headers.indexOf('base64') !== -1) {
|
||||
return atob(dataString);
|
||||
}
|
||||
|
||||
return unescape(dataString);
|
||||
}
|
||||
|
||||
export function getDevicePixelRatio(): number {
|
||||
return (isBrowser && window.devicePixelRatio) || 1;
|
||||
}
|
||||
|
||||
const allowFileAccessFromFilesTip =
|
||||
'On Chromium based browsers, you can use --allow-file-access-from-files flag for debugging purposes.';
|
||||
|
||||
export function displayCORSWarning(): void {
|
||||
warning(
|
||||
!isLocalFileSystem,
|
||||
`Loading PDF as base64 strings/URLs may not work on protocols other than HTTP/HTTPS. ${allowFileAccessFromFilesTip}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function displayWorkerWarning(): void {
|
||||
warning(
|
||||
!isLocalFileSystem,
|
||||
`Loading PDF.js worker may not work on protocols other than HTTP/HTTPS. ${allowFileAccessFromFilesTip}`,
|
||||
);
|
||||
}
|
||||
|
||||
export function cancelRunningTask(runningTask?: { cancel?: () => void } | null): void {
|
||||
if (runningTask?.cancel) runningTask.cancel();
|
||||
}
|
||||
|
||||
export function makePageCallback(page: PDFPageProxy, scale: number): PageCallback {
|
||||
Object.defineProperty(page, 'width', {
|
||||
get() {
|
||||
return this.getViewport({ scale }).width;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(page, 'height', {
|
||||
get() {
|
||||
return this.getViewport({ scale }).height;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(page, 'originalWidth', {
|
||||
get() {
|
||||
return this.getViewport({ scale: 1 }).width;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(page, 'originalHeight', {
|
||||
get() {
|
||||
return this.getViewport({ scale: 1 }).height;
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
return page as PageCallback;
|
||||
}
|
||||
|
||||
export function isAbortException(error: Error): boolean {
|
||||
return error.name === 'AbortException' || error.name === 'RenderingCancelledException';
|
||||
}
|
||||
|
||||
export function loadFromFile(file: Blob): Promise<ArrayBuffer> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = () => {
|
||||
if (!reader.result) {
|
||||
return reject(new Error('Error while reading a file.'));
|
||||
}
|
||||
|
||||
resolve(reader.result as ArrayBuffer);
|
||||
};
|
||||
|
||||
reader.onerror = (event) => {
|
||||
if (!event.target) {
|
||||
return reject(new Error('Error while reading a file.'));
|
||||
}
|
||||
|
||||
const { error } = event.target;
|
||||
|
||||
if (!error) {
|
||||
return reject(new Error('Error while reading a file.'));
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case error.NOT_FOUND_ERR:
|
||||
return reject(new Error('Error while reading a file: File not found.'));
|
||||
case error.SECURITY_ERR:
|
||||
return reject(new Error('Error while reading a file: Security error.'));
|
||||
case error.ABORT_ERR:
|
||||
return reject(new Error('Error while reading a file: Aborted.'));
|
||||
default:
|
||||
return reject(new Error('Error while reading a file.'));
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
});
|
||||
}
|
||||
Reference in a new issue