diff --git a/example/src/App.tsx b/example/src/App.tsx index 9d6b477..bb803bd 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -2,34 +2,54 @@ import React, {useState} from 'react'; import pdfWorkerSource from 'pdfjs-dist/build/pdf.worker.min.mjs'; import * as pdfjs from 'pdfjs-dist'; import ReactFastPDF, {PDFPreviewer} from 'react-fast-pdf'; +import type {RotationDegrees} from 'react-fast-pdf'; import './index.css'; pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(new Blob([pdfWorkerSource], {type: 'text/javascript'})); 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 aa1cebf..bb3fb11 100644 --- a/src/PDFPreviewer.tsx +++ b/src/PDFPreviewer.tsx @@ -6,7 +6,7 @@ import {Document} 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'; @@ -27,6 +27,7 @@ type Props = { onLoadError?: () => void; containerStyle?: CSSProperties; contentContainerStyle?: CSSProperties; + rotation?: RotationDegrees; }; type OnPasswordCallback = (password: string | null) => void; @@ -48,7 +49,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); @@ -100,6 +102,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) => { @@ -109,12 +112,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); @@ -204,7 +213,15 @@ function PDFPreviewer({ if (containerWidth > 0 && containerHeight > 0) { listRef.current?.resetAfterIndex(0); } - }, [containerWidth, containerHeight]); + }, [containerWidth, containerHeight, rotation]); + + /** + * Scroll back to the top whenever rotation changes so the list offset + * is consistent regardless of how page dimensions change. + */ + useLayoutEffect(() => { + listRef.current?.scrollTo(0); + }, [rotation]); useLayoutEffect(() => { if (!containerRef.current) { @@ -227,7 +244,12 @@ function PDFPreviewer({ ref={containerRef} style={{...styles.container, ...containerStyle}} > -
+
@@ -244,11 +267,17 @@ function PDFPreviewer({ style={{...styles.list, ...contentContainerStyle}} outerRef={setListAttributes} width={isSmallScreen ? pageWidth : containerWidth} - height={containerHeight} + height={numPages === 1 && estimatedPageHeight < containerHeight ? estimatedPageHeight : containerHeight} itemCount={numPages} itemSize={calculatePageHeight} estimatedItemSize={calculatePageHeight(0)} - itemData={{pageWidth, estimatedPageHeight, calculatePageHeight, getDevicePixelRatio, containerHeight, numPages}} + itemData={{ + pageWidth, + estimatedPageHeight, + calculatePageHeight, + getDevicePixelRatio, + numPages, + }} > {PageRenderer} @@ -261,6 +290,6 @@ function PDFPreviewer({ ); } -PDFPasswordForm.displayName = 'PDFPreviewer'; +PDFPreviewer.displayName = 'PDFPreviewer'; export default memo(PDFPreviewer); diff --git a/src/PageRenderer.tsx b/src/PageRenderer.tsx index cbfd97e..5b37a5a 100644 --- a/src/PageRenderer.tsx +++ b/src/PageRenderer.tsx @@ -12,21 +12,19 @@ type Props = { calculatePageHeight: (pageIndex: number) => number; getDevicePixelRatio: (width: number, height: number) => number | undefined; numPages: number; - containerHeight: number; }; }; function PageRenderer({index, style, data}: Props) { - const {pageWidth, estimatedPageHeight, calculatePageHeight, getDevicePixelRatio, numPages, containerHeight} = data; + const {pageWidth, estimatedPageHeight, calculatePageHeight, getDevicePixelRatio, numPages} = data; /** * Render a specific page based on its index. * The method includes a wrapper to apply virtualized styles. */ const pageHeight = calculatePageHeight(index); const devicePixelRatio = getDevicePixelRatio(pageWidth, pageHeight); - const parsedHeight = parseFloat(style.height as unknown as string); const parsedTop = parseFloat(style.top as unknown as string); - const topPadding = numPages > 1 || parsedHeight > containerHeight ? parsedTop + PAGE_BORDER : (containerHeight - parsedHeight) / 2; + const topPadding = numPages === 1 ? parsedTop : parsedTop + PAGE_BORDER; return (