Numerous Bug Fixes
Some checks failed
Automated Container Build / build-and-push (push) Failing after 8s

This commit is contained in:
Elijah 2026-05-22 15:50:45 -07:00
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
View 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;

View 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
View 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
View 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
View 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>
);
}

View 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
View 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
View 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>
);
}

View 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;
}

View 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
View 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
View 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
View 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
View 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;

View 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
View 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
View 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
View 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
View 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
View 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
View 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,
};

View 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 {};

View 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+)$/;

View 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;
};
}

View 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);
}

View 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);
}

View 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);
}

View 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 });
}

View 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
View 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
View 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);
});
}