From 51089ea18afcf9988a42a852b0203a2392f0bbd8 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Tue, 17 Mar 2026 20:28:06 +0100 Subject: [PATCH 1/4] feat: add rotation --- example/src/App.tsx | 34 +++++++++++++++++++++++++++------- src/PDFPreviewer.tsx | 31 +++++++++++++++++++++---------- src/index.ts | 2 ++ src/types.ts | 7 ++++++- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/example/src/App.tsx b/example/src/App.tsx index 88c3bad..77d4089 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,31 +1,51 @@ import React, {useState} from 'react'; import ReactFastPDF, {PDFPreviewer} from 'react-fast-pdf'; +import type {RotationDegrees} from 'react-fast-pdf'; import './index.css'; function App() { const [file, setFile] = useState(null); + const [rotation, setRotation] = useState(0); // `.default` is required when referencing the legacy CJS package. const packageName = ('default' in ReactFastPDF ? (ReactFastPDF.default as {PackageName: string}) : ReactFastPDF).PackageName; + const handleRotate = () => { + setRotation((prev) => ((prev + 90) % 360) as RotationDegrees); + }; + return (

Hello, I am {packageName}!

{file ? ( <> - +
+ + + +
) : ( diff --git a/src/PDFPreviewer.tsx b/src/PDFPreviewer.tsx index 8b88ad1..8682d71 100644 --- a/src/PDFPreviewer.tsx +++ b/src/PDFPreviewer.tsx @@ -1,13 +1,13 @@ import pdfWorkerSource from 'pdfjs-dist/build/pdf.worker.min.mjs'; -import React, {memo, useCallback, useLayoutEffect, useRef, useState} from 'react'; -import type {CSSProperties, ReactNode} from 'react'; -import times from 'lodash/times.js'; +import React, {useCallback, useLayoutEffect, useRef, useState} from 'react'; +import type {CSSProperties, ReactNode, JSX} from 'react'; +import {times} from 'lodash'; import {VariableSizeList as List} from 'react-window'; import {Document, pdfjs} from 'react-pdf'; import 'react-pdf/dist/Page/AnnotationLayer.css'; import 'react-pdf/dist/Page/TextLayer.css'; -import type {PDFDocument, PageViewport} from './types.js'; +import type {PDFDocument, PageViewport, RotationDegrees} from './types.js'; import {pdfPreviewerStyles as styles} from './styles.js'; import PDFPasswordForm, {type PDFPasswordFormProps} from './PDFPasswordForm.js'; import PageRenderer from './PageRenderer.js'; @@ -28,6 +28,8 @@ type Props = { onLoadError?: () => void; containerStyle?: CSSProperties; contentContainerStyle?: CSSProperties; + /** Rotation angle for all pages (0, 90, 180, 270 degrees) */ + rotation?: RotationDegrees; }; type OnPasswordCallback = (password: string | null) => void; @@ -51,7 +53,8 @@ function PDFPreviewer({ contentContainerStyle, shouldShowErrorComponent = true, onLoadError, -}: Props) { + rotation = 0, +}: Props): JSX.Element { const [pageViewports, setPageViewports] = useState([]); const [numPages, setNumPages] = useState(0); const [containerWidth, setContainerWidth] = useState(0); @@ -103,6 +106,7 @@ function PDFPreviewer({ * Calculates a proper page height. The method should be called only when there are page viewports. * It is based on a ratio between the specific page viewport width and provided page width. * Also, the app should take into account the page borders. + * When rotation is 90 or 270 degrees, width and height are swapped. */ const calculatePageHeight = useCallback( (pageIndex: number) => { @@ -112,12 +116,18 @@ function PDFPreviewer({ const pageWidth = calculatePageWidth(); - const {width: pageViewportWidth, height: pageViewportHeight} = pageViewports[pageIndex]; + const {width: originalWidth, height: originalHeight} = pageViewports[pageIndex]; + + // Swap dimensions when rotated 90 or 270 degrees + const isRotated90or270 = rotation === 90 || rotation === 270; + const pageViewportWidth = isRotated90or270 ? originalHeight : originalWidth; + const pageViewportHeight = isRotated90or270 ? originalWidth : originalHeight; + const scale = pageWidth / pageViewportWidth; return pageViewportHeight * scale + PAGE_BORDER * 2; }, - [pageViewports, calculatePageWidth], + [pageViewports, calculatePageWidth, rotation], ); const estimatedPageHeight = calculatePageHeight(0); @@ -207,7 +217,7 @@ function PDFPreviewer({ if (containerWidth > 0 && containerHeight > 0) { listRef.current?.resetAfterIndex(0); } - }, [containerWidth, containerHeight]); + }, [containerWidth, containerHeight, rotation]); useLayoutEffect(() => { if (!containerRef.current) { @@ -238,6 +248,7 @@ function PDFPreviewer({ error={shouldShowErrorComponent ? ErrorComponent : null} onLoadError={onLoadError} loading={LoadingComponent} + rotate={rotation} onLoadSuccess={onDocumentLoadSuccess} onPassword={initiatePasswordChallenge} > @@ -264,6 +275,6 @@ function PDFPreviewer({ ); } -PDFPasswordForm.displayName = 'PDFPreviewer'; +PDFPreviewer.displayName = 'PDFPreviewer'; -export default memo(PDFPreviewer); +export default PDFPreviewer; diff --git a/src/index.ts b/src/index.ts index 7fb5b72..a638ea9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,10 @@ import PDFPreviewer from './PDFPreviewer.js'; +import type {RotationDegrees} from './types.js'; const PACKAGE_NAME = 'react-fast-pdf'; export {PDFPreviewer}; +export type {RotationDegrees}; export default { PackageName: PACKAGE_NAME, diff --git a/src/types.ts b/src/types.ts index 2c5255f..ca6996e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -18,4 +18,9 @@ type ComponentStyles = { [key: string]: CSSProperties; }; -export type {PDFDocument, PageViewport, ComponentStyles}; +/** + * Valid rotation angles for PDF pages (in degrees clockwise) + */ +type RotationDegrees = 0 | 90 | 180 | 270; + +export type {PDFDocument, PageViewport, ComponentStyles, RotationDegrees}; From 35b5e362b8e8333de0331fa37a7d60c4bf76f588 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Thu, 19 Mar 2026 21:03:43 +0100 Subject: [PATCH 2/4] feat: add ROTATION_DEGREES constant for valid PDF page rotations --- src/constants.ts | 7 ++++++- src/index.ts | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index 52ba7ba..a59f0fc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -33,4 +33,9 @@ const PDF_PASSWORD_FORM_RESPONSES = { INCORRECT_PASSWORD: 2, }; -export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES}; +/** + * Valid rotation angles for PDF pages, in degrees clockwise. + */ +const ROTATION_DEGREES = [0, 90, 180, 270] as const; + +export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION_DEGREES}; diff --git a/src/index.ts b/src/index.ts index a638ea9..5d5a6a8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,10 @@ import PDFPreviewer from './PDFPreviewer.js'; +import {ROTATION_DEGREES} from './constants.js'; import type {RotationDegrees} from './types.js'; const PACKAGE_NAME = 'react-fast-pdf'; -export {PDFPreviewer}; +export {PDFPreviewer, ROTATION_DEGREES}; export type {RotationDegrees}; export default { From 0d3d30c63e65eea48652dc4a7f1c51218b4d9b9d Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 20 Mar 2026 17:34:37 +0100 Subject: [PATCH 3/4] refactor: replace ROTATION_DEGREES array with ROTATION object and update related imports --- src/PDFPreviewer.tsx | 22 +++++++++++++++++----- src/constants.ts | 11 +++++++++-- src/index.ts | 4 ++-- src/types.ts | 3 ++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/src/PDFPreviewer.tsx b/src/PDFPreviewer.tsx index 8682d71..8ad69ad 100644 --- a/src/PDFPreviewer.tsx +++ b/src/PDFPreviewer.tsx @@ -11,7 +11,7 @@ import type {PDFDocument, PageViewport, RotationDegrees} from './types.js'; import {pdfPreviewerStyles as styles} from './styles.js'; import PDFPasswordForm, {type PDFPasswordFormProps} from './PDFPasswordForm.js'; import PageRenderer from './PageRenderer.js'; -import {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES} from './constants.js'; +import {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION} from './constants.js'; import {setListAttributes} from './helpers.js'; type Props = { @@ -53,7 +53,7 @@ function PDFPreviewer({ contentContainerStyle, shouldShowErrorComponent = true, onLoadError, - rotation = 0, + rotation = ROTATION.DEG_0, }: Props): JSX.Element { const [pageViewports, setPageViewports] = useState([]); const [numPages, setNumPages] = useState(0); @@ -119,7 +119,7 @@ function PDFPreviewer({ const {width: originalWidth, height: originalHeight} = pageViewports[pageIndex]; // Swap dimensions when rotated 90 or 270 degrees - const isRotated90or270 = rotation === 90 || rotation === 270; + const isRotated90or270 = rotation === ROTATION.DEG_90 || rotation === ROTATION.DEG_270; const pageViewportWidth = isRotated90or270 ? originalHeight : originalWidth; const pageViewportHeight = isRotated90or270 ? originalWidth : originalHeight; @@ -240,7 +240,12 @@ function PDFPreviewer({ ref={containerRef} style={{...styles.container, ...containerStyle}} > -
+
{PageRenderer} diff --git a/src/constants.ts b/src/constants.ts index a59f0fc..92f34b8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -36,6 +36,13 @@ const PDF_PASSWORD_FORM_RESPONSES = { /** * Valid rotation angles for PDF pages, in degrees clockwise. */ -const ROTATION_DEGREES = [0, 90, 180, 270] as const; +const ROTATION = { + DEG_0: 0, + DEG_90: 90, + DEG_180: 180, + DEG_270: 270, +} as const; -export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION_DEGREES}; +const ROTATION_DEGREES_STEPS = [ROTATION.DEG_0, ROTATION.DEG_90, ROTATION.DEG_180, ROTATION.DEG_270] as const; + +export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION, ROTATION_DEGREES_STEPS}; diff --git a/src/index.ts b/src/index.ts index 5d5a6a8..4ca0e93 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ import PDFPreviewer from './PDFPreviewer.js'; -import {ROTATION_DEGREES} from './constants.js'; +import {ROTATION, ROTATION_DEGREES_STEPS} from './constants.js'; import type {RotationDegrees} from './types.js'; const PACKAGE_NAME = 'react-fast-pdf'; -export {PDFPreviewer, ROTATION_DEGREES}; +export {PDFPreviewer, ROTATION, ROTATION_DEGREES_STEPS}; export type {RotationDegrees}; export default { diff --git a/src/types.ts b/src/types.ts index ca6996e..836fdc3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ import {CSSProperties} from 'react'; +import {ROTATION} from './constants.js'; type Page = { getViewport: ({scale}: {scale: number}) => PageViewport; @@ -21,6 +22,6 @@ type ComponentStyles = { /** * Valid rotation angles for PDF pages (in degrees clockwise) */ -type RotationDegrees = 0 | 90 | 180 | 270; +type RotationDegrees = typeof ROTATION.DEG_0 | typeof ROTATION.DEG_90 | typeof ROTATION.DEG_180 | typeof ROTATION.DEG_270; export type {PDFDocument, PageViewport, ComponentStyles, RotationDegrees}; From 873e37d1e7a6e6262ff052a84a8198d87b193c30 Mon Sep 17 00:00:00 2001 From: Yauheni Pasiukevich Date: Fri, 20 Mar 2026 17:59:10 +0100 Subject: [PATCH 4/4] refactor: remove ROTATION constants and simplify rotation handling in PDFPreviewer --- src/PDFPreviewer.tsx | 10 +++++----- src/constants.ts | 14 +------------- src/index.ts | 3 +-- src/types.ts | 3 +-- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/PDFPreviewer.tsx b/src/PDFPreviewer.tsx index 8ad69ad..3cce600 100644 --- a/src/PDFPreviewer.tsx +++ b/src/PDFPreviewer.tsx @@ -1,5 +1,5 @@ import pdfWorkerSource from 'pdfjs-dist/build/pdf.worker.min.mjs'; -import React, {useCallback, useLayoutEffect, useRef, useState} from 'react'; +import React, {useCallback, useLayoutEffect, useRef, useState, memo} from 'react'; import type {CSSProperties, ReactNode, JSX} from 'react'; import {times} from 'lodash'; import {VariableSizeList as List} from 'react-window'; @@ -11,7 +11,7 @@ import type {PDFDocument, PageViewport, RotationDegrees} from './types.js'; import {pdfPreviewerStyles as styles} from './styles.js'; import PDFPasswordForm, {type PDFPasswordFormProps} from './PDFPasswordForm.js'; import PageRenderer from './PageRenderer.js'; -import {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION} from './constants.js'; +import {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES} from './constants.js'; import {setListAttributes} from './helpers.js'; type Props = { @@ -53,7 +53,7 @@ function PDFPreviewer({ contentContainerStyle, shouldShowErrorComponent = true, onLoadError, - rotation = ROTATION.DEG_0, + rotation = 0, }: Props): JSX.Element { const [pageViewports, setPageViewports] = useState([]); const [numPages, setNumPages] = useState(0); @@ -119,7 +119,7 @@ function PDFPreviewer({ const {width: originalWidth, height: originalHeight} = pageViewports[pageIndex]; // Swap dimensions when rotated 90 or 270 degrees - const isRotated90or270 = rotation === ROTATION.DEG_90 || rotation === ROTATION.DEG_270; + const isRotated90or270 = rotation === 90 || rotation === 270; const pageViewportWidth = isRotated90or270 ? originalHeight : originalWidth; const pageViewportHeight = isRotated90or270 ? originalWidth : originalHeight; @@ -289,4 +289,4 @@ function PDFPreviewer({ PDFPreviewer.displayName = 'PDFPreviewer'; -export default PDFPreviewer; +export default memo(PDFPreviewer); diff --git a/src/constants.ts b/src/constants.ts index 92f34b8..52ba7ba 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -33,16 +33,4 @@ const PDF_PASSWORD_FORM_RESPONSES = { INCORRECT_PASSWORD: 2, }; -/** - * Valid rotation angles for PDF pages, in degrees clockwise. - */ -const ROTATION = { - DEG_0: 0, - DEG_90: 90, - DEG_180: 180, - DEG_270: 270, -} as const; - -const ROTATION_DEGREES_STEPS = [ROTATION.DEG_0, ROTATION.DEG_90, ROTATION.DEG_180, ROTATION.DEG_270] as const; - -export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES, ROTATION, ROTATION_DEGREES_STEPS}; +export {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, DEFAULT_DOCUMENT_OPTIONS, DEFAULT_EXTERNAL_LINK_TARGET, PDF_PASSWORD_FORM_RESPONSES}; diff --git a/src/index.ts b/src/index.ts index 4ca0e93..a638ea9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,9 @@ import PDFPreviewer from './PDFPreviewer.js'; -import {ROTATION, ROTATION_DEGREES_STEPS} from './constants.js'; import type {RotationDegrees} from './types.js'; const PACKAGE_NAME = 'react-fast-pdf'; -export {PDFPreviewer, ROTATION, ROTATION_DEGREES_STEPS}; +export {PDFPreviewer}; export type {RotationDegrees}; export default { diff --git a/src/types.ts b/src/types.ts index 836fdc3..ca6996e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,4 @@ import {CSSProperties} from 'react'; -import {ROTATION} from './constants.js'; type Page = { getViewport: ({scale}: {scale: number}) => PageViewport; @@ -22,6 +21,6 @@ type ComponentStyles = { /** * Valid rotation angles for PDF pages (in degrees clockwise) */ -type RotationDegrees = typeof ROTATION.DEG_0 | typeof ROTATION.DEG_90 | typeof ROTATION.DEG_180 | typeof ROTATION.DEG_270; +type RotationDegrees = 0 | 90 | 180 | 270; export type {PDFDocument, PageViewport, ComponentStyles, RotationDegrees};