Skip to content

Commit

Permalink
Merge pull request #33083 from artus9033/proposal/32502
Browse files Browse the repository at this point in the history
fix: upgrade to newer react-pdf and pdf.js to render high-resolution images properly on the web
  • Loading branch information
danieldoglas authored Jan 19, 2024
2 parents 4f17b41 + bd83d53 commit 41f051b
Show file tree
Hide file tree
Showing 9 changed files with 410 additions and 4,289 deletions.
4,292 changes: 130 additions & 4,162 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@
"react-native-web": "^0.19.9",
"react-native-web-linear-gradient": "^1.1.2",
"react-native-webview": "13.6.3",
"react-pdf": "^6.2.2",
"react-pdf": "7.3.3",
"react-plaid-link": "3.3.2",
"react-web-config": "^1.0.0",
"react-webcam": "^7.1.1",
Expand Down Expand Up @@ -222,7 +222,6 @@
"@types/react-beautiful-dnd": "^13.1.4",
"@types/react-collapse": "^5.0.1",
"@types/react-dom": "^18.2.4",
"@types/react-pdf": "^5.7.2",
"@types/react-test-renderer": "^18.0.0",
"@types/semver": "^7.5.4",
"@types/setimmediate": "^1.0.2",
Expand Down
28 changes: 0 additions & 28 deletions patches/react-pdf+6.2.2.patch

This file was deleted.

12 changes: 12 additions & 0 deletions patches/react-pdf+7.3.3.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/node_modules/react-pdf/dist/esm/Document.js b/node_modules/react-pdf/dist/esm/Document.js
index ac7ca31..56bc766 100644
--- a/node_modules/react-pdf/dist/esm/Document.js
+++ b/node_modules/react-pdf/dist/esm/Document.js
@@ -240,6 +240,7 @@ const Document = forwardRef(function Document(_a, ref) {
pdfDispatch({ type: 'REJECT', error });
});
return () => {
+ loadingTask._worker.destroy();
loadingTask.destroy();
};
}
132 changes: 132 additions & 0 deletions src/components/PDFView/WebPDFDocument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import 'core-js/features/array/at';
import PropTypes from 'prop-types';
import React, {memo, useCallback} from 'react';
import {Document} from 'react-pdf';
import {VariableSizeList as List} from 'react-window';
import _ from 'underscore';
import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator';
import Text from '@components/Text';
import stylePropTypes from '@styles/stylePropTypes';
import CONST from '@src/CONST';
import PageRenderer from './WebPDFPageRenderer';

const propTypes = {
/** Index of the PDF page to be displayed passed by VariableSizeList */
errorLabelStyles: stylePropTypes,
/** Returns translated string for given locale and phrase */
translate: PropTypes.func.isRequired,
/** The source URL from which to load PDF file to be displayed */
sourceURL: PropTypes.string.isRequired,
/** Callback invoked when the PDF document is loaded successfully */
onDocumentLoadSuccess: PropTypes.func.isRequired,
/** Viewport info of all PDF pages */
pageViewportsLength: PropTypes.number.isRequired,
/** Sets attributes to list container */
setListAttributes: PropTypes.func.isRequired,
/** Indicates, whether the screen is of small width */
isSmallScreenWidth: PropTypes.bool.isRequired,
/** Height of PDF document container view */
containerHeight: PropTypes.number.isRequired,
/** Width of PDF document container view */
containerWidth: PropTypes.number.isRequired,
/** The number of pages of the PDF file to be rendered */
numPages: PropTypes.number,
/** Function that calculates the height of a page of the PDF document */
calculatePageHeight: PropTypes.func.isRequired,
/** Function that calculates the devicePixelRatio the page should be rendered with */
getDevicePixelRatio: PropTypes.func.isRequired,
/** The estimated height of a single PDF page for virtualized rendering purposes */
estimatedItemSize: PropTypes.number.isRequired,
/** The width of a page in the PDF file */
pageWidth: PropTypes.number.isRequired,
/** The style applied to the list component */
listStyle: stylePropTypes,
/** Function that should initiate that the user should be prompted for password to the PDF file */
initiatePasswordChallenge: PropTypes.func.isRequired,
/** Either:
* - `string` - the password provided by the user to unlock the PDF file
* - `undefined` if password isn't needed to view the PDF file
* - `null` if the password is required but hasn't been provided yet */
password: PropTypes.string,
};

const defaultProps = {
errorLabelStyles: [],
numPages: null,
listStyle: undefined,
password: undefined,
};

const WebPDFDocument = memo(
({
errorLabelStyles,
translate,
sourceURL,
onDocumentLoadSuccess,
pageViewportsLength,
setListAttributes,
isSmallScreenWidth,
containerHeight,
containerWidth,
numPages,
calculatePageHeight,
getDevicePixelRatio,
estimatedItemSize,
pageWidth,
listStyle,
initiatePasswordChallenge,
password,
}) => {
const onPassword = useCallback(
(callback, reason) => {
if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.NEED_PASSWORD) {
if (password) {
callback(password);
} else {
initiatePasswordChallenge(reason);
}
} else if (reason === CONST.PDF_PASSWORD_FORM.REACT_PDF_PASSWORD_RESPONSES.INCORRECT_PASSWORD) {
initiatePasswordChallenge(reason);
}
},
[password, initiatePasswordChallenge],
);

return (
<Document
loading={<FullScreenLoadingIndicator />}
error={<Text style={errorLabelStyles}>{translate('attachmentView.failedToLoadPDF')}</Text>}
file={sourceURL}
options={{
cMapUrl: 'cmaps/',
cMapPacked: true,
}}
externalLinkTarget="_blank"
onLoadSuccess={onDocumentLoadSuccess}
onPassword={onPassword}
>
{!!pageViewportsLength && (
<List
outerRef={setListAttributes}
style={listStyle}
width={isSmallScreenWidth ? pageWidth : containerWidth}
height={containerHeight}
estimatedItemSize={estimatedItemSize}
itemCount={numPages}
itemSize={calculatePageHeight}
itemData={{pageWidth, calculatePageHeight, getDevicePixelRatio, estimatedItemSize}}
>
{PageRenderer}
</List>
)}
</Document>
);
},
(prevProps, nextProps) => _.isEqual(prevProps, nextProps),
);

WebPDFDocument.displayName = 'WebPDFDocument';
WebPDFDocument.propTypes = propTypes;
WebPDFDocument.defaultProps = defaultProps;

export default WebPDFDocument;
57 changes: 57 additions & 0 deletions src/components/PDFView/WebPDFPageRenderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import PropTypes from 'prop-types';
import React, {memo} from 'react';
import {View} from 'react-native';
import {Page} from 'react-pdf';
import _ from 'underscore';
import stylePropTypes from '@styles/stylePropTypes';
import PDFViewConstants from './constants';

const propTypes = {
/** Index of the PDF page to be displayed passed by VariableSizeList */
index: PropTypes.number.isRequired,

/** Page extra data passed by VariableSizeList's data prop */
data: PropTypes.shape({
/** Width of a single page in the document */
pageWidth: PropTypes.number.isRequired,
/** Function that calculates the height of a page given its index */
calculatePageHeight: PropTypes.func.isRequired,
/** Function that calculates the pixel ratio for a page given its calculated width and height */
getDevicePixelRatio: PropTypes.func.isRequired,
/** The estimated height of a single page in the document */
estimatedItemSize: PropTypes.number.isRequired,
}).isRequired,

/** Additional style props passed by VariableSizeList */
style: stylePropTypes.isRequired,
};

const WebPDFPageRenderer = memo(
({index: pageIndex, data, style}) => {
const {pageWidth, calculatePageHeight, getDevicePixelRatio, estimatedItemSize} = data;

const pageHeight = calculatePageHeight(pageIndex);
const devicePixelRatio = getDevicePixelRatio(pageWidth, pageHeight);

return (
<View style={{...style, top: `${parseFloat(style.top) + PDFViewConstants.PAGE_BORDER}px`}}>
<Page
key={`page_${pageIndex}`}
width={pageWidth}
height={pageHeight || estimatedItemSize}
pageIndex={pageIndex}
// This needs to be empty to avoid multiple loading texts which show per page and look ugly
// See https://github.com/Expensify/App/issues/14358 for more details
loading=""
devicePixelRatio={devicePixelRatio}
/>
</View>
);
},
(prevProps, nextProps) => _.isEqual(prevProps, nextProps),
);

WebPDFPageRenderer.displayName = 'WebPDFPageRenderer';
WebPDFPageRenderer.propTypes = propTypes;

export default WebPDFPageRenderer;
15 changes: 15 additions & 0 deletions src/components/PDFView/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Each page has a default border. The app should take this size into account
* when calculates the page width and height.
*/
const PAGE_BORDER = 9;

/**
* Pages should be more narrow than the container on large screens. The app should take this size into account
* when calculates the page width.
*/
const LARGE_SCREEN_SIDE_SPACING = 40;

const REQUIRED_PASSWORD_MISSING = null;

export default {PAGE_BORDER, LARGE_SCREEN_SIDE_SPACING, REQUIRED_PASSWORD_MISSING};
Loading

0 comments on commit 41f051b

Please sign in to comment.