From 342b2c79b2ab4fa5117e91b2fb2bf13bb635f26f Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Mon, 23 Jun 2025 14:15:42 +0200 Subject: [PATCH 01/10] Add support for optional content configuration in Canvas component --- packages/react-pdf/src/Page.tsx | 9 +++++++++ packages/react-pdf/src/Page/Canvas.tsx | 6 +++++- packages/react-pdf/src/shared/types.ts | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index 39d849100..4ae90c0ce 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -21,6 +21,7 @@ import useDocumentContext from './shared/hooks/useDocumentContext.js'; import useResolver from './shared/hooks/useResolver.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type { EventProps } from 'make-event-props'; import type { ClassName, @@ -293,6 +294,11 @@ export type PageProps = { * @example 300 */ width?: number; + /** + * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. + * If `null`, the configuration will be fetched automatically with the default visibility states set. + */ + optionalContentConfig?: OptionalContentConfig | null; } & EventProps; /** @@ -345,6 +351,7 @@ export default function Page(props: PageProps): React.ReactElement { scale: scaleProps = defaultScale, unregisterPage, width, + optionalContentConfig, ...otherProps } = mergedProps; @@ -515,6 +522,7 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayer: renderTextLayerProps, rotate, scale, + optionalContentConfig } : null, [ @@ -541,6 +549,7 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayerProps, rotate, scale, + optionalContentConfig ], ); diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index 3f7f61d58..44048ccb1 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -14,6 +14,7 @@ import { getDevicePixelRatio, isCancelException, makePageCallback, + isProvided, } from '../shared/utils.js'; import type { RenderParameters } from 'pdfjs-dist/types/src/display/api.js'; @@ -41,6 +42,7 @@ export default function Canvas(props: CanvasProps): React.ReactElement { renderTextLayer, rotate, scale, + optionalContentConfig, } = mergedProps; const { canvasRef } = props; @@ -114,6 +116,8 @@ export default function Canvas(props: CanvasProps): React.ReactElement { annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE, canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, viewport: renderViewport, + optionalContentConfigPromise: isProvided(optionalContentConfig) ? + Promise.resolve(optionalContentConfig) : undefined, }; if (canvasBackground) { renderContext.background = canvasBackground; @@ -132,7 +136,7 @@ export default function Canvas(props: CanvasProps): React.ReactElement { return () => cancelRunningTask(runningTask); }, - [canvasBackground, page, renderForms, renderViewport, viewport], + [canvasBackground, page, renderForms, renderViewport, viewport, optionalContentConfig], ); const cleanup = useCallback(() => { diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index cc5258405..ebdfd378f 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -14,6 +14,7 @@ import type { TextMarkedContent, } from 'pdfjs-dist/types/src/display/api.js'; import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/annotation_layer.js'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type LinkService from '../LinkService.js'; export type { PasswordResponses, StructTreeNode, TextContent, TextItem, TextMarkedContent }; @@ -165,6 +166,7 @@ export type PageContextType = { renderTextLayer: boolean; rotate: number; scale: number; + optionalContentConfig?: OptionalContentConfig | null; } | null; export type OutlineContextType = { From 5555a856eb82ac39b0252966ebad241eb4471903 Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Fri, 27 Jun 2025 11:07:33 +0200 Subject: [PATCH 02/10] Add tests and integrate optional content configuration support for Document components, removing them from page props --- __mocks__/_pdf5.pdf | Bin 0 -> 120332 bytes packages/react-pdf/src/Document.spec.tsx | 40 ++++++++++++++++- packages/react-pdf/src/Document.tsx | 19 +++++++- packages/react-pdf/src/Page.spec.tsx | 53 +++++++++++++++++++++++ packages/react-pdf/src/Page.tsx | 10 +---- packages/react-pdf/src/Page/Canvas.tsx | 5 ++- packages/react-pdf/src/shared/types.ts | 1 + 7 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 __mocks__/_pdf5.pdf diff --git a/__mocks__/_pdf5.pdf b/__mocks__/_pdf5.pdf new file mode 100644 index 0000000000000000000000000000000000000000..50b0a682d759d8aaf0a6ea820f33773b197a6676 GIT binary patch literal 120332 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 literal 0 HcmV?d00001 diff --git a/packages/react-pdf/src/Document.spec.tsx b/packages/react-pdf/src/Document.spec.tsx index 8a74343ab..33d7d68c9 100644 --- a/packages/react-pdf/src/Document.spec.tsx +++ b/packages/react-pdf/src/Document.spec.tsx @@ -11,11 +11,13 @@ import Page from './Page.js'; import { makeAsyncCallback, loadPDF, muteConsole, restoreConsole } from '../../../test-utils.js'; import type { PDFDocumentProxy } from 'pdfjs-dist'; -import type { ScrollPageIntoViewArgs } from './shared/types.js'; +import type { DocumentContextType, ScrollPageIntoViewArgs } from './shared/types.js'; import type LinkService from './LinkService.js'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); +const pdfFile5 = await loadPDF('../../__mocks__/_pdf5.pdf'); const OK = Symbol('OK'); @@ -44,6 +46,9 @@ async function waitForAsync() { } describe('Document', () => { + // Loaded PDF file + let pdf5: PDFDocumentProxy; + // Object with basic loaded PDF information that shall match after successful loading const desiredLoadedPdf: Partial = {}; const desiredLoadedPdf2: Partial = {}; @@ -54,6 +59,8 @@ describe('Document', () => { const pdf2 = await pdfjs.getDocument({ data: pdfFile2.arrayBuffer }).promise; desiredLoadedPdf2._pdfInfo = pdf2._pdfInfo; + + pdf5 = await pdfjs.getDocument({ data: pdfFile5.arrayBuffer }).promise; }); describe('loading', () => { @@ -425,6 +432,37 @@ describe('Document', () => { expect(child.dataset.rotate).toBe('180'); }); + + it('passes optionalContentConfig prop to its children', async () => { + const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); + const optionalContentConfig: OptionalContentConfig = await pdf5.getOptionalContentConfig(); + + expect(optionalContentConfig.getGroup('1R').visible).toBe(true); + + optionalContentConfig.setVisibility('1R', false); + + let documentContext: DocumentContextType | undefined; + + render( + + + {(context) => { + documentContext = context; + return null; + }} + + , + ); + + await onLoadSuccessPromise; + + expect(documentContext?.optionalContentConfig).toBeDefined(); + expect(documentContext!.optionalContentConfig!.getGroup('1R').visible).toBe(false); + }); }); describe('viewer', () => { diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index c25411265..d7651b83f 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -31,6 +31,7 @@ import useResolver from './shared/hooks/useResolver.js'; import type { PDFDocumentProxy } from 'pdfjs-dist'; import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api.js'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type { EventProps } from 'make-event-props'; import type { ClassName, @@ -204,6 +205,11 @@ export type DocumentProps = { * @example 90 */ rotate?: number | null; + /** + * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. + * If `null`, the configuration will be fetched automatically with the default visibility states set. + */ + optionalContentConfig?: OptionalContentConfig | null; } & EventProps; const defaultOnPassword: OnPassword = (callback, reason) => { @@ -262,6 +268,7 @@ const Document: React.ForwardRefExoticComponent< options, renderMode, rotate, + optionalContentConfig, ...otherProps }, ref, @@ -581,8 +588,18 @@ const Document: React.ForwardRefExoticComponent< renderMode, rotate, unregisterPage, + optionalContentConfig, }), - [imageResourcesPath, onItemClick, pdf, registerPage, renderMode, rotate, unregisterPage], + [ + imageResourcesPath, + onItemClick, + pdf, + registerPage, + renderMode, + rotate, + unregisterPage, + optionalContentConfig, + ], ); const eventProps = useMemo( diff --git a/packages/react-pdf/src/Page.spec.tsx b/packages/react-pdf/src/Page.spec.tsx index 88aafac29..dbe53b017 100644 --- a/packages/react-pdf/src/Page.spec.tsx +++ b/packages/react-pdf/src/Page.spec.tsx @@ -15,10 +15,12 @@ import DocumentContext from './DocumentContext.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; import type { DocumentContextType, PageCallback } from './shared/types.js'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); const pdfFile4 = await loadPDF('../../__mocks__/_pdf4.pdf'); +const pdfFile5 = await loadPDF('../../__mocks__/_pdf5.pdf'); function renderWithContext(children: React.ReactNode, context: Partial) { const { rerender, ...otherResult } = render( @@ -48,6 +50,7 @@ describe('Page', () => { let pdf: PDFDocumentProxy; let pdf2: PDFDocumentProxy; let pdf4: PDFDocumentProxy; + let pdf5: PDFDocumentProxy; // Object with basic loaded page information that shall match after successful loading const desiredLoadedPage: Partial = {}; @@ -79,6 +82,8 @@ describe('Page', () => { unregisterPageArguments = [page._pageIndex]; pdf4 = await pdfjs.getDocument({ data: pdfFile4.arrayBuffer }).promise; + + pdf5 = await pdfjs.getDocument({ data: pdfFile5.arrayBuffer }).promise; }); describe('loading', () => { @@ -733,6 +738,54 @@ describe('Page', () => { expect(annotationLayer).not.toBeInTheDocument(); }); + + it('requests page to be rendered with default visibility for optionalContentConfig', async () => { + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = + makeAsyncCallback<[PageCallback]>(); + + const { container } = renderWithContext( + , + { + linkService, + pdf: pdf5, + }, + ); + + await onRenderSuccessPromise; + + const pageCanvas = container.querySelector('.react-pdf__Page__canvas') as HTMLCanvasElement; + const context: CanvasRenderingContext2D = pageCanvas.getContext('2d')!; + const imageData: ImageData = context.getImageData(100, 100, 1, 1); + + // should render green pixel because the layer is visible + expect(imageData.data).toStrictEqual(new Uint8ClampedArray([191, 255, 191, 255])); + }); + + it('requests page to be rendered with given optionalContentConfig', async () => { + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = + makeAsyncCallback<[PageCallback]>(); + const optionalContentConfig: OptionalContentConfig = await pdf5.getOptionalContentConfig(); + + optionalContentConfig.setVisibility('1R', false); + + const { container } = renderWithContext( + , + { + optionalContentConfig, + linkService, + pdf: pdf5, + }, + ); + + await onRenderSuccessPromise; + + const pageCanvas = container.querySelector('.react-pdf__Page__canvas') as HTMLCanvasElement; + const context: CanvasRenderingContext2D = pageCanvas.getContext('2d')!; + const imageData: ImageData = context.getImageData(100, 100, 1, 1); + + // should render white pixel because the layer is hidden + expect(imageData.data).toStrictEqual(new Uint8ClampedArray([255, 255, 255, 255])); + }); }); it('requests page to be rendered without forms by default', async () => { diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index 4ae90c0ce..0e812a08a 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -21,7 +21,6 @@ import useDocumentContext from './shared/hooks/useDocumentContext.js'; import useResolver from './shared/hooks/useResolver.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; -import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type { EventProps } from 'make-event-props'; import type { ClassName, @@ -294,11 +293,6 @@ export type PageProps = { * @example 300 */ width?: number; - /** - * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. - * If `null`, the configuration will be fetched automatically with the default visibility states set. - */ - optionalContentConfig?: OptionalContentConfig | null; } & EventProps; /** @@ -522,7 +516,7 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayer: renderTextLayerProps, rotate, scale, - optionalContentConfig + optionalContentConfig, } : null, [ @@ -549,7 +543,7 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayerProps, rotate, scale, - optionalContentConfig + optionalContentConfig, ], ); diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index 44048ccb1..3969458ee 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -116,8 +116,9 @@ export default function Canvas(props: CanvasProps): React.ReactElement { annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE, canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, viewport: renderViewport, - optionalContentConfigPromise: isProvided(optionalContentConfig) ? - Promise.resolve(optionalContentConfig) : undefined, + optionalContentConfigPromise: isProvided(optionalContentConfig) + ? Promise.resolve(optionalContentConfig) + : undefined, }; if (canvasBackground) { renderContext.background = canvasBackground; diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index ebdfd378f..24f7793c3 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -140,6 +140,7 @@ export type DocumentContextType = { renderMode?: RenderMode; rotate?: number | null; unregisterPage: UnregisterPage; + optionalContentConfig?: OptionalContentConfig | null; } | null; export type PageContextType = { From eb5dd47457b38b44b1b069c9552af6a0199580c7 Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Fri, 27 Jun 2025 11:29:23 +0200 Subject: [PATCH 03/10] fixed missing comment start --- packages/react-pdf/src/Document.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index 3fde60275..117e5d7f8 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -205,6 +205,7 @@ export type DocumentProps = { * @example 90 */ rotate?: number | null; + /* * Document scale. * * @default 1 From f26180ea39c10847385a6900209cde928fb73dac Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Fri, 27 Jun 2025 13:41:34 +0200 Subject: [PATCH 04/10] Sort props, improve type safety --- packages/react-pdf/src/Document.spec.tsx | 26 +++++++++++++-------- packages/react-pdf/src/Document.tsx | 20 ++++++++-------- packages/react-pdf/src/Page.spec.tsx | 29 ++++++++++++++++-------- packages/react-pdf/src/Page.tsx | 6 ++--- packages/react-pdf/src/Page/Canvas.tsx | 6 ++--- packages/react-pdf/src/shared/types.ts | 13 ++++++++--- 6 files changed, 62 insertions(+), 38 deletions(-) diff --git a/packages/react-pdf/src/Document.spec.tsx b/packages/react-pdf/src/Document.spec.tsx index 1f44b388d..8e2ee8278 100644 --- a/packages/react-pdf/src/Document.spec.tsx +++ b/packages/react-pdf/src/Document.spec.tsx @@ -13,7 +13,6 @@ import { makeAsyncCallback, loadPDF, muteConsole, restoreConsole } from '../../. import type { PDFDocumentProxy } from 'pdfjs-dist'; import type { DocumentContextType, ScrollPageIntoViewArgs } from './shared/types.js'; import type LinkService from './LinkService.js'; -import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -50,13 +49,13 @@ async function waitForAsync() { } describe('Document', () => { - // Loaded PDF file - let pdf5: PDFDocumentProxy; - // Object with basic loaded PDF information that shall match after successful loading const desiredLoadedPdf: Partial = {}; const desiredLoadedPdf2: Partial = {}; + // Loaded PDF file + let pdf5: PDFDocumentProxy; + beforeAll(async () => { const pdf = await pdfjs.getDocument({ data: pdfFile.arrayBuffer }).promise; desiredLoadedPdf._pdfInfo = pdf._pdfInfo; @@ -472,13 +471,11 @@ describe('Document', () => { expect(child.dataset.scale).toBe('2'); }); - + it('passes optionalContentConfig prop to its children', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); - const optionalContentConfig: OptionalContentConfig = await pdf5.getOptionalContentConfig(); - - expect(optionalContentConfig.getGroup('1R').visible).toBe(true); + const optionalContentConfig = await pdf5.getOptionalContentConfig(); optionalContentConfig.setVisibility('1R', false); let documentContext: DocumentContextType | undefined; @@ -500,8 +497,17 @@ describe('Document', () => { await onLoadSuccessPromise; - expect(documentContext?.optionalContentConfig).toBeDefined(); - expect(documentContext!.optionalContentConfig!.getGroup('1R').visible).toBe(false); + if (!documentContext) { + throw new Error('Document context is not set'); + } + + expect(documentContext.optionalContentConfig).toBeDefined(); + + if (!documentContext.optionalContentConfig) { + throw new Error('Optional content config is not set'); + } + + expect(documentContext.optionalContentConfig.getGroup('1R').visible).toBe(false); }); }); diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index 117e5d7f8..d49d3c7b0 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -179,6 +179,13 @@ export type DocumentProps = { * @example () => alert('Document source retrieved!') */ onSourceSuccess?: OnSourceSuccess; + /** + * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. + * If `null`, the configuration will be fetched automatically with the default visibility states set. + * + * @example pdfDocument.getOptionalContentConfig().then(optionalContentConfig => optionalContentConfig.setVisibility('1R', false)); + */ + optionalContentConfig?: OptionalContentConfig | null; /** * An object in which additional parameters to be passed to PDF.js can be defined. Most notably: * - `cMapUrl`; @@ -205,18 +212,13 @@ export type DocumentProps = { * @example 90 */ rotate?: number | null; - /* + /** * Document scale. * * @default 1 * @example 0.5 */ scale?: number; - /** - * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. - * If `null`, the configuration will be fetched automatically with the default visibility states set. - */ - optionalContentConfig?: OptionalContentConfig | null; } & EventProps; const defaultOnPassword: OnPassword = (callback, reason) => { @@ -272,11 +274,11 @@ const Document: React.ForwardRefExoticComponent< onPassword = defaultOnPassword, onSourceError: onSourceErrorProps, onSourceSuccess: onSourceSuccessProps, + optionalContentConfig, options, renderMode, rotate, scale, - optionalContentConfig, ...otherProps }, ref, @@ -591,24 +593,24 @@ const Document: React.ForwardRefExoticComponent< imageResourcesPath, linkService: linkService.current, onItemClick, + optionalContentConfig, pdf, registerPage, renderMode, rotate, scale, unregisterPage, - optionalContentConfig, }), [ imageResourcesPath, onItemClick, + optionalContentConfig, pdf, registerPage, renderMode, rotate, scale, unregisterPage, - optionalContentConfig, ], ); diff --git a/packages/react-pdf/src/Page.spec.tsx b/packages/react-pdf/src/Page.spec.tsx index dbe53b017..40830e77e 100644 --- a/packages/react-pdf/src/Page.spec.tsx +++ b/packages/react-pdf/src/Page.spec.tsx @@ -15,7 +15,6 @@ import DocumentContext from './DocumentContext.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; import type { DocumentContextType, PageCallback } from './shared/types.js'; -import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -739,7 +738,7 @@ describe('Page', () => { expect(annotationLayer).not.toBeInTheDocument(); }); - it('requests page to be rendered with default visibility for optionalContentConfig', async () => { + it('requests page to be rendered with default visibility given no optionalContentConfig', async () => { const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); @@ -754,25 +753,30 @@ describe('Page', () => { await onRenderSuccessPromise; const pageCanvas = container.querySelector('.react-pdf__Page__canvas') as HTMLCanvasElement; - const context: CanvasRenderingContext2D = pageCanvas.getContext('2d')!; - const imageData: ImageData = context.getImageData(100, 100, 1, 1); + const context = pageCanvas.getContext('2d'); - // should render green pixel because the layer is visible + if (!context) { + throw new Error('CanvasRenderingContext2D is not available'); + } + + const imageData = context.getImageData(100, 100, 1, 1); + + // Should render green pixel because the layer is visible expect(imageData.data).toStrictEqual(new Uint8ClampedArray([191, 255, 191, 255])); }); it('requests page to be rendered with given optionalContentConfig', async () => { const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); - const optionalContentConfig: OptionalContentConfig = await pdf5.getOptionalContentConfig(); + const optionalContentConfig = await pdf5.getOptionalContentConfig(); optionalContentConfig.setVisibility('1R', false); const { container } = renderWithContext( , { - optionalContentConfig, linkService, + optionalContentConfig, pdf: pdf5, }, ); @@ -780,10 +784,15 @@ describe('Page', () => { await onRenderSuccessPromise; const pageCanvas = container.querySelector('.react-pdf__Page__canvas') as HTMLCanvasElement; - const context: CanvasRenderingContext2D = pageCanvas.getContext('2d')!; - const imageData: ImageData = context.getImageData(100, 100, 1, 1); + const context = pageCanvas.getContext('2d'); + + if (!context) { + throw new Error('CanvasRenderingContext2D is not available'); + } + + const imageData = context.getImageData(100, 100, 1, 1); - // should render white pixel because the layer is hidden + // Should render white pixel because the layer is hidden expect(imageData.data).toStrictEqual(new Uint8ClampedArray([255, 255, 255, 255])); }); }); diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index cec03c3fe..9f01bf6b8 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -333,6 +333,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccess: onRenderSuccessProps, onRenderTextLayerError: onRenderTextLayerErrorProps, onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, + optionalContentConfig, pageIndex: pageIndexProps, pageNumber: pageNumberProps, pdf, @@ -345,7 +346,6 @@ export default function Page(props: PageProps): React.ReactElement { scale: scaleProps = defaultScale, unregisterPage, width, - optionalContentConfig, ...otherProps } = mergedProps; @@ -509,6 +509,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccess: onRenderSuccessProps, onRenderTextLayerError: onRenderTextLayerErrorProps, onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, + optionalContentConfig, page, pageIndex, pageNumber, @@ -516,7 +517,6 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayer: renderTextLayerProps, rotate, scale, - optionalContentConfig, } : null, [ @@ -536,6 +536,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccessProps, onRenderTextLayerErrorProps, onRenderTextLayerSuccessProps, + optionalContentConfig, page, pageIndex, pageNumber, @@ -543,7 +544,6 @@ export default function Page(props: PageProps): React.ReactElement { renderTextLayerProps, rotate, scale, - optionalContentConfig, ], ); diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index 3969458ee..04959ee77 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -13,8 +13,8 @@ import { cancelRunningTask, getDevicePixelRatio, isCancelException, - makePageCallback, isProvided, + makePageCallback, } from '../shared/utils.js'; import type { RenderParameters } from 'pdfjs-dist/types/src/display/api.js'; @@ -37,12 +37,12 @@ export default function Canvas(props: CanvasProps): React.ReactElement { devicePixelRatio = getDevicePixelRatio(), onRenderError: onRenderErrorProps, onRenderSuccess: onRenderSuccessProps, + optionalContentConfig, page, renderForms, renderTextLayer, rotate, scale, - optionalContentConfig, } = mergedProps; const { canvasRef } = props; @@ -137,7 +137,7 @@ export default function Canvas(props: CanvasProps): React.ReactElement { return () => cancelRunningTask(runningTask); }, - [canvasBackground, page, renderForms, renderViewport, viewport, optionalContentConfig], + [canvasBackground, optionalContentConfig, page, renderForms, renderViewport, viewport], ); const cleanup = useCallback(() => { diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index 24f7793c3..a43b1c6ee 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -17,7 +17,14 @@ import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/ann import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type LinkService from '../LinkService.js'; -export type { PasswordResponses, StructTreeNode, TextContent, TextItem, TextMarkedContent }; +export type { + OptionalContentConfig, + PasswordResponses, + StructTreeNode, + TextContent, + TextItem, + TextMarkedContent, +}; type NullableObject = { [P in keyof T]: T[P] | null }; @@ -135,12 +142,12 @@ export type DocumentContextType = { imageResourcesPath?: ImageResourcesPath; linkService: LinkService; onItemClick?: (args: OnItemClickArgs) => void; + optionalContentConfig?: OptionalContentConfig | null; pdf?: PDFDocumentProxy | false; registerPage: RegisterPage; renderMode?: RenderMode; rotate?: number | null; unregisterPage: UnregisterPage; - optionalContentConfig?: OptionalContentConfig | null; } | null; export type PageContextType = { @@ -160,6 +167,7 @@ export type PageContextType = { onRenderSuccess?: OnRenderSuccess; onRenderTextLayerError?: OnRenderTextLayerError; onRenderTextLayerSuccess?: OnRenderTextLayerSuccess; + optionalContentConfig?: OptionalContentConfig | null; page: PDFPageProxy | false | undefined; pageIndex: number; pageNumber: number; @@ -167,7 +175,6 @@ export type PageContextType = { renderTextLayer: boolean; rotate: number; scale: number; - optionalContentConfig?: OptionalContentConfig | null; } | null; export type OutlineContextType = { From bcb6498972de80eb335015b631b4f7d92f20322c Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Fri, 27 Jun 2025 13:53:57 +0200 Subject: [PATCH 05/10] Add documentation --- packages/react-pdf/README.md | 45 +++++++++++++++-------------- packages/react-pdf/src/Document.tsx | 7 +++-- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/packages/react-pdf/README.md b/packages/react-pdf/README.md index 863e0f4ab..ade285199 100644 --- a/packages/react-pdf/README.md +++ b/packages/react-pdf/README.md @@ -422,28 +422,29 @@ Loads a document passed using `file` prop. #### Props -| Prop name | Description | Default value | Example values | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| className | Class name(s) that will be added to rendered element along with the default `react-pdf__Document`. | n/a |
  • String:
    `"custom-class-name-1 custom-class-name-2"`
  • Array of strings:
    `["custom-class-name-1", "custom-class-name-2"]`
| -| error | What the component should display in case of an error. | `"Failed to load PDF file."` |
  • String:
    `"An error occurred!"`
  • React element:
    `

    An error occurred!

    `
  • Function:
    `this.renderError`
| -| externalLinkRel | Link rel for links rendered in annotations. | `"noopener noreferrer nofollow"` | One of valid [values for `rel` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-rel).
  • `"noopener"`
  • `"noreferrer"`
  • `"nofollow"`
  • `"noopener noreferrer"`
| -| externalLinkTarget | Link target for external links rendered in annotations. | unset, which means that default behavior will be used | One of valid [values for `target` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target).
  • `"_self"`
  • `"_blank"`
  • `"_parent"`
  • `"_top"`
| -| file | 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. | n/a |
  • URL:
    `"https://example.com/sample.pdf"`
  • File:
    `import importedPdf from '../static/sample.pdf'` and then
    `sample`
  • Parameter object:
    `{ url: 'https://example.com/sample.pdf' }`
| -| imageResourcesPath | The path used to prefix the src attributes of annotation SVGs. | n/a (pdf.js will fallback to an empty string) | `"/public/images/"` | -| inputRef | A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to main `
` rendered by `` component. | n/a |
  • Function:
    `(ref) => { this.myDocument = ref; }`
  • Ref created using `createRef`:
    `this.ref = createRef();`

    `inputRef={this.ref}`
  • Ref created using `useRef`:
    `const ref = useRef();`

    `inputRef={ref}`
| -| loading | What the component should display while loading. | `"Loading PDF…"` |
  • String:
    `"Please wait!"`
  • React element:
    `

    Please wait!

    `
  • Function:
    `this.renderLoader`
| -| noData | What the component should display in case of no data. | `"No PDF file specified."` |
  • String:
    `"Please select a file."`
  • React element:
    `

    Please select a file.

    `
  • Function:
    `this.renderNoData`
| -| onItemClick | 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. | n/a | `({ dest, pageIndex, pageNumber }) => alert('Clicked an item from page ' + pageNumber + '!')` | -| onLoadError | Function called in case of an error while loading a document. | n/a | `(error) => alert('Error while loading document! ' + error.message)` | -| onLoadProgress | Function called, potentially multiple times, as the loading progresses. | n/a | `({ loaded, total }) => alert('Loading a document: ' + (loaded / total) * 100 + '%')` | -| onLoadSuccess | Function called when the document is successfully loaded. | n/a | `(pdf) => alert('Loaded a file with ' + pdf.numPages + ' pages!')` | -| onPassword | Function called when a password-protected PDF is loaded. | Function that prompts the user for password. | `(callback) => callback('s3cr3t_p4ssw0rd')` | -| onSourceError | Function called in case of an error while retrieving document source from `file` prop. | n/a | `(error) => alert('Error while retrieving document source! ' + error.message)` | -| onSourceSuccess | Function called when document source is successfully retrieved from `file` prop. | n/a | `() => alert('Document source retrieved!')` | -| options | 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);
  • `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. | n/a | `{ cMapUrl: '/cmaps/' }` | -| renderMode | Rendering mode of the document. Can be `"canvas"`, `"custom"` or `"none"`. If set to `"custom"`, `customRenderer` must also be provided. | `"canvas"` | `"custom"` | -| rotate | 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. | n/a | `90` | -| scale | Document scale. | `1` | `0.5` | +| Prop name | Description | Default value | Example values | +| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| className | Class name(s) that will be added to rendered element along with the default `react-pdf__Document`. | n/a |
  • String:
    `"custom-class-name-1 custom-class-name-2"`
  • Array of strings:
    `["custom-class-name-1", "custom-class-name-2"]`
| +| error | What the component should display in case of an error. | `"Failed to load PDF file."` |
  • String:
    `"An error occurred!"`
  • React element:
    `

    An error occurred!

    `
  • Function:
    `this.renderError`
| +| externalLinkRel | Link rel for links rendered in annotations. | `"noopener noreferrer nofollow"` | One of valid [values for `rel` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-rel).
  • `"noopener"`
  • `"noreferrer"`
  • `"nofollow"`
  • `"noopener noreferrer"`
| +| externalLinkTarget | Link target for external links rendered in annotations. | unset, which means that default behavior will be used | One of valid [values for `target` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target).
  • `"_self"`
  • `"_blank"`
  • `"_parent"`
  • `"_top"`
| +| file | 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. | n/a |
  • URL:
    `"https://example.com/sample.pdf"`
  • File:
    `import importedPdf from '../static/sample.pdf'` and then
    `sample`
  • Parameter object:
    `{ url: 'https://example.com/sample.pdf' }`
| +| imageResourcesPath | The path used to prefix the src attributes of annotation SVGs. | n/a (pdf.js will fallback to an empty string) | `"/public/images/"` | +| inputRef | A prop that behaves like [ref](https://reactjs.org/docs/refs-and-the-dom.html), but it's passed to main `
` rendered by `` component. | n/a |
  • Function:
    `(ref) => { this.myDocument = ref; }`
  • Ref created using `createRef`:
    `this.ref = createRef();`

    `inputRef={this.ref}`
  • Ref created using `useRef`:
    `const ref = useRef();`

    `inputRef={ref}`
| +| loading | What the component should display while loading. | `"Loading PDF…"` |
  • String:
    `"Please wait!"`
  • React element:
    `

    Please wait!

    `
  • Function:
    `this.renderLoader`
| +| noData | What the component should display in case of no data. | `"No PDF file specified."` |
  • String:
    `"Please select a file."`
  • React element:
    `

    Please select a file.

    `
  • Function:
    `this.renderNoData`
| +| onItemClick | 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. | n/a | `({ dest, pageIndex, pageNumber }) => alert('Clicked an item from page ' + pageNumber + '!')` | +| onLoadError | Function called in case of an error while loading a document. | n/a | `(error) => alert('Error while loading document! ' + error.message)` | +| onLoadProgress | Function called, potentially multiple times, as the loading progresses. | n/a | `({ loaded, total }) => alert('Loading a document: ' + (loaded / total) * 100 + '%')` | +| onLoadSuccess | Function called when the document is successfully loaded. | n/a | `(pdf) => alert('Loaded a file with ' + pdf.numPages + ' pages!')` | +| onPassword | Function called when a password-protected PDF is loaded. | Function that prompts the user for password. | `(callback) => callback('s3cr3t_p4ssw0rd')` | +| onSourceError | Function called in case of an error while retrieving document source from `file` prop. | n/a | `(error) => alert('Error while retrieving document source! ' + error.message)` | +| onSourceSuccess | Function called when document source is successfully retrieved from `file` prop. | n/a | `() => alert('Document source retrieved!')` | +| optionalContentConfig | An `OptionalContentConfig` object that can be used to control the visibility of optional content groups (OCGs) in the PDF document. This is useful for PDFs that contain layers, such as maps or technical drawings, where you may want to toggle the visibility of certain layers. | `null` | `pdfDocument.getOptionalContentConfig()` | +| options | 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);
  • `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. | n/a | `{ cMapUrl: '/cmaps/' }` | +| renderMode | Rendering mode of the document. Can be `"canvas"`, `"custom"` or `"none"`. If set to `"custom"`, `customRenderer` must also be provided. | `"canvas"` | `"custom"` | +| rotate | 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. | n/a | `90` | +| scale | Document scale. | `1` | `0.5` | ### Page diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index d49d3c7b0..55b24878f 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -180,10 +180,11 @@ export type DocumentProps = { */ onSourceSuccess?: OnSourceSuccess; /** - * An {@link OptionalContentConfig} created from {@link PDFDocumentProxy.getOptionalContentConfig}. - * If `null`, the configuration will be fetched automatically with the default visibility states set. + * An `OptionalContentConfig` object that can be used to control the visibility of optional content groups (OCGs) in the PDF document. This is useful for PDFs that contain layers, such as maps or technical drawings, where you may want to toggle the visibility of certain layers. * - * @example pdfDocument.getOptionalContentConfig().then(optionalContentConfig => optionalContentConfig.setVisibility('1R', false)); + * @example + * const optionalContentConfig = await pdfDocument.getOptionalContentConfig() + * optionalContentConfig.setVisibility('1R', false) */ optionalContentConfig?: OptionalContentConfig | null; /** From fdc4a7c79d5a7a048d7ad170df4c80af410163c2 Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Fri, 27 Jun 2025 13:56:01 +0200 Subject: [PATCH 06/10] Export type --- packages/react-pdf/src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-pdf/src/index.ts b/packages/react-pdf/src/index.ts index 7d72b4604..92d81e856 100644 --- a/packages/react-pdf/src/index.ts +++ b/packages/react-pdf/src/index.ts @@ -16,6 +16,7 @@ export type { OutlineProps } from './Outline.js'; export type { PageProps } from './Page.js'; export type { ThumbnailProps } from './Thumbnail.js'; export type { + OptionalContentConfig, PasswordResponses as PasswordResponsesType, StructTreeNode, TextContent, From 946f23037fe4bbf65b590237a60668d28775239a Mon Sep 17 00:00:00 2001 From: Wojciech Maj Date: Fri, 27 Jun 2025 14:16:32 +0200 Subject: [PATCH 07/10] Add optionalContentConfig to test suite --- test/Test.tsx | 19 +++++++++++++++++++ test/ViewOptions.tsx | 30 ++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/test/Test.tsx b/test/Test.tsx index 9c691d874..af1ff7be2 100644 --- a/test/Test.tsx +++ b/test/Test.tsx @@ -16,6 +16,7 @@ import { isArrayBuffer, isBlob, isBrowser, loadFromFile, dataURItoBlob } from '. import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; import type { ExternalLinkTarget, File, PassMethod, RenderMode } from './shared/types.js'; +import type { OptionalContentConfig } from 'react-pdf'; const { PDFDataRangeTransport } = pdfjs; @@ -76,6 +77,9 @@ export default function Test() { const [file, setFile] = useState(null); const [fileForProps, setFileForProps] = useState(); const [numPages, setNumPages] = useState(); + const [optionalContentConfigLayerVisible, setOptionalContentConfigLayerVisible] = + useState(true); + const [optionalContentConfig, setOptionalContentConfig] = useState(); const [pageHeight, setPageHeight] = useState(); const [pageNumber, setPageNumber] = useState(); const [pageScale, setPageScale] = useState(); @@ -101,6 +105,10 @@ export default function Test() { const { numPages: nextNumPages } = document; setNumPages(nextNumPages); setPageNumber(1); + + document.getOptionalContentConfig({ intent: 'display' }).then((nextOptionalContentConfig) => { + setOptionalContentConfig(nextOptionalContentConfig); + }); }, []); const onDocumentLoadError = useCallback((error: Error) => { @@ -129,6 +137,14 @@ export default function Test() { [], ); + if (optionalContentConfig) { + if (optionalContentConfig.isVisible('1R') !== optionalContentConfigLayerVisible) { + optionalContentConfig.setVisibility('1R', optionalContentConfigLayerVisible); + + // FIXME: This should somehow trigger a re-render of the document + } + } + useEffect(() => { (async () => { const nextFileForProps = await (async () => { @@ -209,6 +225,7 @@ export default function Test() { const documentProps = { externalLinkTarget, file: fileForProps, + optionalContentConfig, options, rotate, }; @@ -253,6 +270,7 @@ export default function Test() { canvasBackground={canvasBackground} devicePixelRatio={devicePixelRatio} displayAll={displayAll} + optionalContentConfigLayerVisible={optionalContentConfigLayerVisible} pageHeight={pageHeight} pageScale={pageScale} pageWidth={pageWidth} @@ -261,6 +279,7 @@ export default function Test() { setCanvasBackground={setCanvasBackground} setDevicePixelRatio={setDevicePixelRatio} setDisplayAll={setDisplayAll} + setOptionalContentConfigLayerVisible={setOptionalContentConfigLayerVisible} setPageHeight={setPageHeight} setPageScale={setPageScale} setPageWidth={setPageWidth} diff --git a/test/ViewOptions.tsx b/test/ViewOptions.tsx index f74c03653..02d0bcaa3 100644 --- a/test/ViewOptions.tsx +++ b/test/ViewOptions.tsx @@ -6,6 +6,7 @@ type ViewOptionsProps = { canvasBackground?: string; devicePixelRatio?: number; displayAll: boolean; + optionalContentConfigLayerVisible?: boolean; pageHeight?: number; pageScale?: number; pageWidth?: number; @@ -14,6 +15,7 @@ type ViewOptionsProps = { setCanvasBackground: (value: string | undefined) => void; setDevicePixelRatio: (value: number | undefined) => void; setDisplayAll: (value: boolean) => void; + setOptionalContentConfigLayerVisible: (value: boolean) => void; setPageHeight: (value: number | undefined) => void; setPageScale: (value: number | undefined) => void; setPageWidth: (value: number | undefined) => void; @@ -25,6 +27,7 @@ export default function ViewOptions({ canvasBackground, devicePixelRatio, displayAll, + optionalContentConfigLayerVisible, pageHeight, pageScale, pageWidth, @@ -33,6 +36,7 @@ export default function ViewOptions({ setCanvasBackground, setDevicePixelRatio, setDisplayAll, + setOptionalContentConfigLayerVisible, setPageHeight, setPageScale, setPageWidth, @@ -53,6 +57,7 @@ export default function ViewOptions({ const renderCustomId = useId(); const renderNoneId = useId(); const rotationId = useId(); + const optionalContentConfigLayerVisibleId = useId(); const displayAllId = useId(); function onCanvasBackgroundChange(event: React.ChangeEvent) { @@ -68,6 +73,10 @@ export default function ViewOptions({ setDevicePixelRatio(devicePixelRatio); } + function onOptionalContentConfigLayerVisibleChange(event: React.ChangeEvent) { + setOptionalContentConfigLayerVisible(event.target.checked); + } + function onDisplayAllChange(event: React.ChangeEvent) { setDisplayAll(event.target.checked); } @@ -298,8 +307,25 @@ export default function ViewOptions({
- - +
+ + +
+ +
+ + +
); } From 7e620808e01c45a6bc4ec5a0512d4b413905b5e9 Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Thu, 10 Jul 2025 11:47:57 +0200 Subject: [PATCH 08/10] Integrate OptionalContentService to manage optional content across Document and Page components, replacing optionalContentConfig usage. Update tests accordingly. --- packages/react-pdf/src/Document.spec.tsx | 33 +++-- packages/react-pdf/src/Document.tsx | 32 ++--- .../react-pdf/src/OptionalContentService.ts | 125 ++++++++++++++++++ packages/react-pdf/src/Page.spec.tsx | 38 ++++-- packages/react-pdf/src/Page.tsx | 6 +- packages/react-pdf/src/Page/Canvas.tsx | 40 +++++- packages/react-pdf/src/shared/types.ts | 5 +- 7 files changed, 229 insertions(+), 50 deletions(-) create mode 100644 packages/react-pdf/src/OptionalContentService.ts diff --git a/packages/react-pdf/src/Document.spec.tsx b/packages/react-pdf/src/Document.spec.tsx index 8e2ee8278..d99167463 100644 --- a/packages/react-pdf/src/Document.spec.tsx +++ b/packages/react-pdf/src/Document.spec.tsx @@ -13,6 +13,7 @@ import { makeAsyncCallback, loadPDF, muteConsole, restoreConsole } from '../../. import type { PDFDocumentProxy } from 'pdfjs-dist'; import type { DocumentContextType, ScrollPageIntoViewArgs } from './shared/types.js'; import type LinkService from './LinkService.js'; +import type OptionalContentService from "./OptionalContentService.js"; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -53,17 +54,12 @@ describe('Document', () => { const desiredLoadedPdf: Partial = {}; const desiredLoadedPdf2: Partial = {}; - // Loaded PDF file - let pdf5: PDFDocumentProxy; - beforeAll(async () => { const pdf = await pdfjs.getDocument({ data: pdfFile.arrayBuffer }).promise; desiredLoadedPdf._pdfInfo = pdf._pdfInfo; const pdf2 = await pdfjs.getDocument({ data: pdfFile2.arrayBuffer }).promise; desiredLoadedPdf2._pdfInfo = pdf2._pdfInfo; - - pdf5 = await pdfjs.getDocument({ data: pdfFile5.arrayBuffer }).promise; }); describe('loading', () => { @@ -472,11 +468,15 @@ describe('Document', () => { expect(child.dataset.scale).toBe('2'); }); - it('passes optionalContentConfig prop to its children', async () => { + it('passes optionalContentService prop to its children', async () => { const { func: onLoadSuccess, promise: onLoadSuccessPromise } = makeAsyncCallback(); - const optionalContentConfig = await pdf5.getOptionalContentConfig(); - optionalContentConfig.setVisibility('1R', false); + const instance = createRef<{ + linkService: React.RefObject; + pages: React.RefObject; + optionalContentService: React.RefObject; + viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; + }>(); let documentContext: DocumentContextType | undefined; @@ -484,7 +484,7 @@ describe('Document', () => { {(context) => { @@ -495,19 +495,26 @@ describe('Document', () => { , ); + if (!instance.current) { + throw new Error('Document ref is not set'); + } + await onLoadSuccessPromise; + const optionalContentService = instance.current.optionalContentService.current; + optionalContentService.setVisibility('1R', false); + if (!documentContext) { throw new Error('Document context is not set'); } - expect(documentContext.optionalContentConfig).toBeDefined(); + expect(documentContext.optionalContentService).toBeDefined(); - if (!documentContext.optionalContentConfig) { + if (!documentContext.optionalContentService) { throw new Error('Optional content config is not set'); } - expect(documentContext.optionalContentConfig.getGroup('1R').visible).toBe(false); + expect(documentContext.optionalContentService.isVisible('1R')).toBe(false); }); }); @@ -519,6 +526,7 @@ describe('Document', () => { const instance = createRef<{ linkService: React.RefObject; pages: React.RefObject; + optionalContentService: React.RefObject; viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; }>(); @@ -561,6 +569,7 @@ describe('Document', () => { linkService: React.RefObject; // biome-ignore lint/suspicious/noExplicitAny: Intentional use to simplify the test pages: React.RefObject; + optionalContentService: React.RefObject; viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; }>(); diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index b2aaec11a..f0f39979a 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -13,6 +13,7 @@ import DocumentContext from './DocumentContext.js'; import Message from './Message.js'; +import OptionalContentService from "./OptionalContentService.js"; import LinkService from './LinkService.js'; import PasswordResponses from './PasswordResponses.js'; @@ -31,7 +32,6 @@ import useResolver from './shared/hooks/useResolver.js'; import type { PDFDocumentProxy } from 'pdfjs-dist'; import type { DocumentInitParameters } from 'pdfjs-dist/types/src/display/api.js'; -import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type { EventProps } from 'make-event-props'; import type { ClassName, @@ -179,14 +179,6 @@ export type DocumentProps = { * @example () => alert('Document source retrieved!') */ onSourceSuccess?: OnSourceSuccess; - /** - * An `OptionalContentConfig` object that can be used to control the visibility of optional content groups (OCGs) in the PDF document. This is useful for PDFs that contain layers, such as maps or technical drawings, where you may want to toggle the visibility of certain layers. - * - * @example - * const optionalContentConfig = await pdfDocument.getOptionalContentConfig() - * optionalContentConfig.setVisibility('1R', false) - */ - optionalContentConfig?: OptionalContentConfig | null; /** * An object in which additional parameters to be passed to PDF.js can be defined. Most notably: * - `cMapUrl`; @@ -252,11 +244,12 @@ function isParameterObject(file: File): file is Source { */ const Document: React.ForwardRefExoticComponent< DocumentProps & - React.RefAttributes<{ - linkService: React.RefObject; - pages: React.RefObject; - viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; - }> + React.RefAttributes<{ + linkService: React.RefObject; + optionalContentService: React.RefObject; + pages: React.RefObject; + viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; + }> > = forwardRef(function Document( { children, @@ -276,7 +269,6 @@ const Document: React.ForwardRefExoticComponent< onPassword = defaultOnPassword, onSourceError: onSourceErrorProps, onSourceSuccess: onSourceSuccessProps, - optionalContentConfig, options, renderMode, rotate, @@ -292,6 +284,8 @@ const Document: React.ForwardRefExoticComponent< const linkService = useRef(new LinkService()); + const optionalContentService = useRef(new OptionalContentService()); + const pages = useRef([]); const prevFile = useRef(undefined); @@ -347,6 +341,7 @@ const Document: React.ForwardRefExoticComponent< ref, () => ({ linkService, + optionalContentService, pages, viewer, }), @@ -541,7 +536,9 @@ const Document: React.ForwardRefExoticComponent< const loadingTask = destroyable; const loadingPromise = loadingTask.promise - .then((nextPdf) => { + .then(async (nextPdf) => { + optionalContentService.current.setDocument(nextPdf); + await optionalContentService.current.loadOptionalContentConfig(); pdfDispatch({ type: 'RESOLVE', value: nextPdf }); }) .catch((error) => { @@ -595,7 +592,7 @@ const Document: React.ForwardRefExoticComponent< imageResourcesPath, linkService: linkService.current, onItemClick, - optionalContentConfig, + optionalContentService: optionalContentService.current, pdf, registerPage, renderMode, @@ -606,7 +603,6 @@ const Document: React.ForwardRefExoticComponent< [ imageResourcesPath, onItemClick, - optionalContentConfig, pdf, registerPage, renderMode, diff --git a/packages/react-pdf/src/OptionalContentService.ts b/packages/react-pdf/src/OptionalContentService.ts new file mode 100644 index 000000000..2fdc4c145 --- /dev/null +++ b/packages/react-pdf/src/OptionalContentService.ts @@ -0,0 +1,125 @@ +import type { PDFDocumentProxy } from "pdfjs-dist"; +import type { OptionalContentConfig } from "pdfjs-dist/types/src/display/optional_content_config.js"; + +/** + * A service responsible for managing the optional content configuration (OCC) of a PDF document + * and controlling the visibility of optional content groups (OCGs). + */ +export default class OptionalContentService { + private optionalContentConfig?: OptionalContentConfig; + private pdfDocument?: PDFDocumentProxy | null; + private visibilityChangeListener: Array<(id: string, visible?: boolean) => void>; + + constructor() { + this.pdfDocument = undefined; + this.visibilityChangeListener = []; + } + + /** + * Sets the PDF document for internal use. + * + * @param {PDFDocumentProxy} pdfDocument - The PDF document instance to be set. + * @return {void} + */ + public setDocument(pdfDocument: PDFDocumentProxy): void { + this.pdfDocument = pdfDocument; + } + + /** + * Loads the optional content configuration for the associated PDF document. + * If the PDF document is not set or the configuration cannot be loaded, an error will be thrown. + * + * @return {Promise} A promise that resolves to the loaded optional content configuration. + * @throws {Error} Throws an error if the PDF document is not set or the configuration cannot be loaded. + */ + public async loadOptionalContentConfig(): Promise { + if (!this.pdfDocument) { + throw new Error("The PDF document is not set. Call setDocument() first."); + } + + this.optionalContentConfig = await this.pdfDocument.getOptionalContentConfig(); + + if (!this.optionalContentConfig) { + throw new Error("The optional content configuration could not be loaded."); + } + + return this.optionalContentConfig; + } + + /** + * Retrieves the optional content configuration. + * Throws an error if the configuration is not loaded before calling this method. + * + * @return {OptionalContentConfig} The loaded optional content configuration. + */ + public getOptionalContentConfig(): OptionalContentConfig { + if (!this.optionalContentConfig) { + throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + } + + return this.optionalContentConfig; + } + + /** + * Sets the visibility of a specific element and optionally preserves the related behavior. + * + * @param {string} id - The identifier of the element whose visibility is being set. + * @param {boolean} [visible] - Optional. Determines whether the element is visible or not. Defaults to undefined. + * @param {boolean} [preserveRB] - Optional. Indicates if related behavior should be preserved. Defaults to undefined. + */ + public setVisibility(id: string, visible?: boolean, preserveRB?: boolean): void { + if (!this.optionalContentConfig) { + throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + } + + this.optionalContentConfig.setVisibility(id, visible, preserveRB); + + for (const listener of this.visibilityChangeListener) { + listener(id, visible); + } + } + + /** + * Determines whether a specific group identified by its ID is visible + * in the optional content configuration. + * + * @param {string} id - The identifier of the group to check visibility for. + * @return {boolean} Returns true if the group is visible; otherwise, false. + * @throws {Error} Throws an error if the optional content configuration + * is not loaded. + */ + public isVisible(id: string): boolean { + if (!this.optionalContentConfig) { + throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + } + + return this.optionalContentConfig.getGroup(id).visible; + } + + /** + * Registers a listener callback to be invoked whenever a visibility change event occurs. + * + * @param {function} callback - A function that is called when a visibility change occurs. The callback receives the following parameters: + * - id: The unique identifier of the group with the visibility change. + * - visible: Optional parameter indicating the visibility status as a boolean. + */ + public addVisibilityChangeListener(callback: (id: string, visible?: boolean) => void): void { + this.visibilityChangeListener.push(callback); + } + + /** + * Removes a visibility change listener from the internal listener collection. + * + * @param callback A function reference to the listener that should be removed. + */ + public removeVisibilityChangeListener(callback: (id: string, visible?: boolean) => void): void { + for (let i = 0, ii = this.visibilityChangeListener.length; i < ii; i++) { + const listener = this.visibilityChangeListener[i]; + + if (listener === callback) { + this.visibilityChangeListener.splice(i, 1); + break; + } + } + } +} \ No newline at end of file diff --git a/packages/react-pdf/src/Page.spec.tsx b/packages/react-pdf/src/Page.spec.tsx index 40830e77e..102318081 100644 --- a/packages/react-pdf/src/Page.spec.tsx +++ b/packages/react-pdf/src/Page.spec.tsx @@ -15,6 +15,7 @@ import DocumentContext from './DocumentContext.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; import type { DocumentContextType, PageCallback } from './shared/types.js'; +import OptionalContentService from "./OptionalContentService.js"; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -739,8 +740,7 @@ describe('Page', () => { }); it('requests page to be rendered with default visibility given no optionalContentConfig', async () => { - const { func: onRenderSuccess, promise: onRenderSuccessPromise } = - makeAsyncCallback<[PageCallback]>(); + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); const { container } = renderWithContext( , @@ -765,18 +765,27 @@ describe('Page', () => { expect(imageData.data).toStrictEqual(new Uint8ClampedArray([191, 255, 191, 255])); }); - it('requests page to be rendered with given optionalContentConfig', async () => { - const { func: onRenderSuccess, promise: onRenderSuccessPromise } = - makeAsyncCallback<[PageCallback]>(); + it('requests page to be changed when updating with optionalContentService', async () => { + let isFirstRender: boolean = true; + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); + const { func: onRerenderSuccess, promise: onRerenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); - const optionalContentConfig = await pdf5.getOptionalContentConfig(); - optionalContentConfig.setVisibility('1R', false); + const optionalContentService = new OptionalContentService(); + optionalContentService.setDocument(pdf5); + await optionalContentService.loadOptionalContentConfig(); const { container } = renderWithContext( - , + { + if (isFirstRender) { + isFirstRender = false; + onRenderSuccess(page); + } else { + onRerenderSuccess(page); + } + }} pageIndex={0} />, { linkService, - optionalContentConfig, + optionalContentService, pdf: pdf5, }, ); @@ -790,7 +799,16 @@ describe('Page', () => { throw new Error('CanvasRenderingContext2D is not available'); } - const imageData = context.getImageData(100, 100, 1, 1); + let imageData = context.getImageData(100, 100, 1, 1); + + // Should render green pixel because the layer is visible + expect(imageData.data).toStrictEqual(new Uint8ClampedArray([191, 255, 191, 255])); + + optionalContentService.setVisibility('1R', false); + + await onRerenderSuccessPromise; + + imageData = context.getImageData(100, 100, 1, 1); // Should render white pixel because the layer is hidden expect(imageData.data).toStrictEqual(new Uint8ClampedArray([255, 255, 255, 255])); diff --git a/packages/react-pdf/src/Page.tsx b/packages/react-pdf/src/Page.tsx index a1ca0b1b1..cc5f40e58 100644 --- a/packages/react-pdf/src/Page.tsx +++ b/packages/react-pdf/src/Page.tsx @@ -333,7 +333,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccess: onRenderSuccessProps, onRenderTextLayerError: onRenderTextLayerErrorProps, onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, - optionalContentConfig, + optionalContentService, pageIndex: pageIndexProps, pageNumber: pageNumberProps, pdf, @@ -509,7 +509,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccess: onRenderSuccessProps, onRenderTextLayerError: onRenderTextLayerErrorProps, onRenderTextLayerSuccess: onRenderTextLayerSuccessProps, - optionalContentConfig, + optionalContentService, page, pageIndex, pageNumber, @@ -536,7 +536,7 @@ export default function Page(props: PageProps): React.ReactElement { onRenderSuccessProps, onRenderTextLayerErrorProps, onRenderTextLayerSuccessProps, - optionalContentConfig, + optionalContentService, page, pageIndex, pageNumber, diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index e9d62f1dc..6720f560f 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -1,6 +1,6 @@ 'use client'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import mergeRefs from 'merge-refs'; import invariant from 'tiny-invariant'; import warning from 'warning'; @@ -37,7 +37,7 @@ export default function Canvas(props: CanvasProps): React.ReactElement { devicePixelRatio = getDevicePixelRatio(), onRenderError: onRenderErrorProps, onRenderSuccess: onRenderSuccessProps, - optionalContentConfig, + optionalContentService, page, renderForms, renderTextLayer, @@ -50,6 +50,28 @@ export default function Canvas(props: CanvasProps): React.ReactElement { const canvasElement = useRef(null); + const [optionalContentConfigLastUpdate, setOptionalContentConfigLastUpdate] = useState(new Date()); + + const onLayerVisibilityChange = useCallback((): void => { + if (!optionalContentService) { + return; + } + + setOptionalContentConfigLastUpdate(new Date()); + }, [optionalContentService]); + + useEffect(() => { + if (!optionalContentService) { + return; + } + + optionalContentService.addVisibilityChangeListener(onLayerVisibilityChange); + + return () => { + optionalContentService.removeVisibilityChangeListener(onLayerVisibilityChange); + }; + }, [optionalContentService, onLayerVisibilityChange]); + /** * Called when a page is rendered successfully. */ @@ -116,8 +138,8 @@ export default function Canvas(props: CanvasProps): React.ReactElement { annotationMode: renderForms ? ANNOTATION_MODE.ENABLE_FORMS : ANNOTATION_MODE.ENABLE, canvasContext: canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D, viewport: renderViewport, - optionalContentConfigPromise: isProvided(optionalContentConfig) - ? Promise.resolve(optionalContentConfig) + optionalContentConfigPromise: isProvided(optionalContentService) + ? Promise.resolve(optionalContentService.getOptionalContentConfig()) : undefined, }; if (canvasBackground) { @@ -137,7 +159,15 @@ export default function Canvas(props: CanvasProps): React.ReactElement { return () => cancelRunningTask(runningTask); }, - [canvasBackground, optionalContentConfig, page, renderForms, renderViewport, viewport], + [ + canvasBackground, + optionalContentConfigLastUpdate, + optionalContentService, + page, + renderForms, + renderViewport, + viewport, + ], ); const cleanup = useCallback(() => { diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index a43b1c6ee..097b125a2 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -16,6 +16,7 @@ import type { import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/annotation_layer.js'; import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type LinkService from '../LinkService.js'; +import type OptionalContentService from "../OptionalContentService.js"; export type { OptionalContentConfig, @@ -142,7 +143,7 @@ export type DocumentContextType = { imageResourcesPath?: ImageResourcesPath; linkService: LinkService; onItemClick?: (args: OnItemClickArgs) => void; - optionalContentConfig?: OptionalContentConfig | null; + optionalContentService?: OptionalContentService; pdf?: PDFDocumentProxy | false; registerPage: RegisterPage; renderMode?: RenderMode; @@ -167,7 +168,7 @@ export type PageContextType = { onRenderSuccess?: OnRenderSuccess; onRenderTextLayerError?: OnRenderTextLayerError; onRenderTextLayerSuccess?: OnRenderTextLayerSuccess; - optionalContentConfig?: OptionalContentConfig | null; + optionalContentService?: OptionalContentService; page: PDFPageProxy | false | undefined; pageIndex: number; pageNumber: number; From f2505881de0cbfe57b2f9922b2942754914e90eb Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Thu, 10 Jul 2025 11:50:07 +0200 Subject: [PATCH 09/10] Refactor code to unify formatting for imports, callbacks, and error messages. Improve readability in OptionalContentService and related components. --- packages/react-pdf/src/Document.spec.tsx | 8 ++--- packages/react-pdf/src/Document.tsx | 25 +++++----------- .../react-pdf/src/OptionalContentService.ts | 22 +++++++++----- packages/react-pdf/src/Page.spec.tsx | 30 +++++++++++-------- packages/react-pdf/src/Page/Canvas.tsx | 4 ++- packages/react-pdf/src/shared/types.ts | 2 +- 6 files changed, 46 insertions(+), 45 deletions(-) diff --git a/packages/react-pdf/src/Document.spec.tsx b/packages/react-pdf/src/Document.spec.tsx index d99167463..22b45ddfe 100644 --- a/packages/react-pdf/src/Document.spec.tsx +++ b/packages/react-pdf/src/Document.spec.tsx @@ -13,7 +13,7 @@ import { makeAsyncCallback, loadPDF, muteConsole, restoreConsole } from '../../. import type { PDFDocumentProxy } from 'pdfjs-dist'; import type { DocumentContextType, ScrollPageIntoViewArgs } from './shared/types.js'; import type LinkService from './LinkService.js'; -import type OptionalContentService from "./OptionalContentService.js"; +import type OptionalContentService from './OptionalContentService.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -481,11 +481,7 @@ describe('Document', () => { let documentContext: DocumentContextType | undefined; render( - + {(context) => { documentContext = context; diff --git a/packages/react-pdf/src/Document.tsx b/packages/react-pdf/src/Document.tsx index f0f39979a..248928ee8 100644 --- a/packages/react-pdf/src/Document.tsx +++ b/packages/react-pdf/src/Document.tsx @@ -13,7 +13,7 @@ import DocumentContext from './DocumentContext.js'; import Message from './Message.js'; -import OptionalContentService from "./OptionalContentService.js"; +import OptionalContentService from './OptionalContentService.js'; import LinkService from './LinkService.js'; import PasswordResponses from './PasswordResponses.js'; @@ -244,12 +244,12 @@ function isParameterObject(file: File): file is Source { */ const Document: React.ForwardRefExoticComponent< DocumentProps & - React.RefAttributes<{ - linkService: React.RefObject; - optionalContentService: React.RefObject; - pages: React.RefObject; - viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; - }> + React.RefAttributes<{ + linkService: React.RefObject; + optionalContentService: React.RefObject; + pages: React.RefObject; + viewer: React.RefObject<{ scrollPageIntoView: (args: ScrollPageIntoViewArgs) => void }>; + }> > = forwardRef(function Document( { children, @@ -600,16 +600,7 @@ const Document: React.ForwardRefExoticComponent< scale, unregisterPage, }), - [ - imageResourcesPath, - onItemClick, - pdf, - registerPage, - renderMode, - rotate, - scale, - unregisterPage, - ], + [imageResourcesPath, onItemClick, pdf, registerPage, renderMode, rotate, scale, unregisterPage], ); const eventProps = useMemo( diff --git a/packages/react-pdf/src/OptionalContentService.ts b/packages/react-pdf/src/OptionalContentService.ts index 2fdc4c145..4f04d66e1 100644 --- a/packages/react-pdf/src/OptionalContentService.ts +++ b/packages/react-pdf/src/OptionalContentService.ts @@ -1,5 +1,5 @@ -import type { PDFDocumentProxy } from "pdfjs-dist"; -import type { OptionalContentConfig } from "pdfjs-dist/types/src/display/optional_content_config.js"; +import type { PDFDocumentProxy } from 'pdfjs-dist'; +import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; /** * A service responsible for managing the optional content configuration (OCC) of a PDF document @@ -34,13 +34,13 @@ export default class OptionalContentService { */ public async loadOptionalContentConfig(): Promise { if (!this.pdfDocument) { - throw new Error("The PDF document is not set. Call setDocument() first."); + throw new Error('The PDF document is not set. Call setDocument() first.'); } this.optionalContentConfig = await this.pdfDocument.getOptionalContentConfig(); if (!this.optionalContentConfig) { - throw new Error("The optional content configuration could not be loaded."); + throw new Error('The optional content configuration could not be loaded.'); } return this.optionalContentConfig; @@ -54,7 +54,9 @@ export default class OptionalContentService { */ public getOptionalContentConfig(): OptionalContentConfig { if (!this.optionalContentConfig) { - throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + throw new Error( + 'The optional content configuration is not loaded. Call loadOptionalContentConfig() first.', + ); } return this.optionalContentConfig; @@ -69,7 +71,9 @@ export default class OptionalContentService { */ public setVisibility(id: string, visible?: boolean, preserveRB?: boolean): void { if (!this.optionalContentConfig) { - throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + throw new Error( + 'The optional content configuration is not loaded. Call loadOptionalContentConfig() first.', + ); } this.optionalContentConfig.setVisibility(id, visible, preserveRB); @@ -90,7 +94,9 @@ export default class OptionalContentService { */ public isVisible(id: string): boolean { if (!this.optionalContentConfig) { - throw new Error("The optional content configuration is not loaded. Call loadOptionalContentConfig() first."); + throw new Error( + 'The optional content configuration is not loaded. Call loadOptionalContentConfig() first.', + ); } return this.optionalContentConfig.getGroup(id).visible; @@ -122,4 +128,4 @@ export default class OptionalContentService { } } } -} \ No newline at end of file +} diff --git a/packages/react-pdf/src/Page.spec.tsx b/packages/react-pdf/src/Page.spec.tsx index 102318081..dc668b068 100644 --- a/packages/react-pdf/src/Page.spec.tsx +++ b/packages/react-pdf/src/Page.spec.tsx @@ -15,7 +15,7 @@ import DocumentContext from './DocumentContext.js'; import type { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist'; import type { DocumentContextType, PageCallback } from './shared/types.js'; -import OptionalContentService from "./OptionalContentService.js"; +import OptionalContentService from './OptionalContentService.js'; const pdfFile = await loadPDF('../../__mocks__/_pdf.pdf'); const pdfFile2 = await loadPDF('../../__mocks__/_pdf2.pdf'); @@ -740,7 +740,8 @@ describe('Page', () => { }); it('requests page to be rendered with default visibility given no optionalContentConfig', async () => { - const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = + makeAsyncCallback<[PageCallback]>(); const { container } = renderWithContext( , @@ -767,22 +768,27 @@ describe('Page', () => { it('requests page to be changed when updating with optionalContentService', async () => { let isFirstRender: boolean = true; - const { func: onRenderSuccess, promise: onRenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); - const { func: onRerenderSuccess, promise: onRerenderSuccessPromise } = makeAsyncCallback<[PageCallback]>(); + const { func: onRenderSuccess, promise: onRenderSuccessPromise } = + makeAsyncCallback<[PageCallback]>(); + const { func: onRerenderSuccess, promise: onRerenderSuccessPromise } = + makeAsyncCallback<[PageCallback]>(); const optionalContentService = new OptionalContentService(); optionalContentService.setDocument(pdf5); await optionalContentService.loadOptionalContentConfig(); const { container } = renderWithContext( - { - if (isFirstRender) { - isFirstRender = false; - onRenderSuccess(page); - } else { - onRerenderSuccess(page); - } - }} pageIndex={0} />, + { + if (isFirstRender) { + isFirstRender = false; + onRenderSuccess(page); + } else { + onRerenderSuccess(page); + } + }} + pageIndex={0} + />, { linkService, optionalContentService, diff --git a/packages/react-pdf/src/Page/Canvas.tsx b/packages/react-pdf/src/Page/Canvas.tsx index 6720f560f..f05c63fb7 100644 --- a/packages/react-pdf/src/Page/Canvas.tsx +++ b/packages/react-pdf/src/Page/Canvas.tsx @@ -50,7 +50,9 @@ export default function Canvas(props: CanvasProps): React.ReactElement { const canvasElement = useRef(null); - const [optionalContentConfigLastUpdate, setOptionalContentConfigLastUpdate] = useState(new Date()); + const [optionalContentConfigLastUpdate, setOptionalContentConfigLastUpdate] = useState( + new Date(), + ); const onLayerVisibilityChange = useCallback((): void => { if (!optionalContentService) { diff --git a/packages/react-pdf/src/shared/types.ts b/packages/react-pdf/src/shared/types.ts index 097b125a2..905037ae5 100644 --- a/packages/react-pdf/src/shared/types.ts +++ b/packages/react-pdf/src/shared/types.ts @@ -16,7 +16,7 @@ import type { import type { AnnotationLayerParameters } from 'pdfjs-dist/types/src/display/annotation_layer.js'; import type { OptionalContentConfig } from 'pdfjs-dist/types/src/display/optional_content_config.js'; import type LinkService from '../LinkService.js'; -import type OptionalContentService from "../OptionalContentService.js"; +import type OptionalContentService from '../OptionalContentService.js'; export type { OptionalContentConfig, From 0c98ca7bf0b6ea348e97296fb7b43cc0cb450c85 Mon Sep 17 00:00:00 2001 From: Sebastian Marlog Date: Mon, 21 Jul 2025 16:03:53 +0200 Subject: [PATCH 10/10] Add error handling for uninitialized optional content service in Document tests --- packages/react-pdf/src/Document.spec.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-pdf/src/Document.spec.tsx b/packages/react-pdf/src/Document.spec.tsx index 22b45ddfe..d3da39767 100644 --- a/packages/react-pdf/src/Document.spec.tsx +++ b/packages/react-pdf/src/Document.spec.tsx @@ -498,6 +498,11 @@ describe('Document', () => { await onLoadSuccessPromise; const optionalContentService = instance.current.optionalContentService.current; + + if (!optionalContentService) { + throw new Error('optional content service is not initialized'); + } + optionalContentService.setVisibility('1R', false); if (!documentContext) {