diff --git a/package-lock.json b/package-lock.json index bad7696a36a5..97d30f244c67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", + "canvas-size": "^1.2.6", "core-js": "^3.32.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", @@ -24471,6 +24472,11 @@ "node": ">=6" } }, + "node_modules/canvas-size": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/canvas-size/-/canvas-size-1.2.6.tgz", + "integrity": "sha512-x2iVHOrZ5x9V0Hwx6kBz+Yxf/VCAII+jrD6WLjJbytJLozHq/oDJjEva432Os0eHxWMFR0vYlLJwTr6QxyxQqw==" + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "dev": true, @@ -67382,6 +67388,11 @@ "simple-get": "^3.0.3" } }, + "canvas-size": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/canvas-size/-/canvas-size-1.2.6.tgz", + "integrity": "sha512-x2iVHOrZ5x9V0Hwx6kBz+Yxf/VCAII+jrD6WLjJbytJLozHq/oDJjEva432Os0eHxWMFR0vYlLJwTr6QxyxQqw==" + }, "case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "dev": true diff --git a/package.json b/package.json index a6f79c4a8cd4..8ca7073d3b3e 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "awesome-phonenumber": "^5.4.0", "babel-plugin-transform-remove-console": "^6.9.4", "babel-polyfill": "^6.26.0", + "canvas-size": "^1.2.6", "core-js": "^3.32.0", "date-fns": "^2.30.0", "date-fns-tz": "^2.0.0", diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index d4d2ab1f90a6..fb86a97bd4f6 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -224,6 +224,15 @@ const ONYXKEYS = { // Information on any active demos being run DEMO_INFO: 'demoInfo', + // Max area supported for HTML element + MAX_CANVAS_AREA: 'maxCanvasArea', + + // Max height supported for HTML element + MAX_CANVAS_HEIGHT: 'maxCanvasHeight', + + // Max width supported for HTML element + MAX_CANVAS_WIDTH: 'maxCanvasWidth', + /** Collection Keys */ COLLECTION: { DOWNLOAD: 'download_', @@ -352,6 +361,9 @@ type OnyxValues = { [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: number; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; + [ONYXKEYS.MAX_CANVAS_AREA]: number; + [ONYXKEYS.MAX_CANVAS_HEIGHT]: number; + [ONYXKEYS.MAX_CANVAS_WIDTH]: number; // Collections [ONYXKEYS.COLLECTION.DOWNLOAD]: OnyxTypes.Download; diff --git a/src/components/PDFView/index.js b/src/components/PDFView/index.js index 2aede78d3780..bd5fe8162d2e 100644 --- a/src/components/PDFView/index.js +++ b/src/components/PDFView/index.js @@ -5,6 +5,8 @@ import 'core-js/features/array/at'; import {Document, Page, pdfjs} from 'react-pdf/dist/esm/entry.webpack'; import pdfWorkerSource from 'pdfjs-dist/legacy/build/pdf.worker'; import {VariableSizeList as List} from 'react-window'; +import {withOnyx} from 'react-native-onyx'; +import * as CanvasSize from '../../libs/actions/CanvasSize'; import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator'; import styles from '../../styles/styles'; import variables from '../../styles/variables'; @@ -17,6 +19,7 @@ import Text from '../Text'; import compose from '../../libs/compose'; import PressableWithoutFeedback from '../Pressable/PressableWithoutFeedback'; import Log from '../../libs/Log'; +import ONYXKEYS from '../../ONYXKEYS'; /** * Each page has a default border. The app should take this size into account @@ -48,10 +51,12 @@ class PDFView extends Component { this.calculatePageHeight = this.calculatePageHeight.bind(this); this.calculatePageWidth = this.calculatePageWidth.bind(this); this.renderPage = this.renderPage.bind(this); + this.getDevicePixelRatio = _.memoize(this.getDevicePixelRatio); this.setListAttributes = this.setListAttributes.bind(this); const workerBlob = new Blob([pdfWorkerSource], {type: 'text/javascript'}); pdfjs.GlobalWorkerOptions.workerSrc = URL.createObjectURL(workerBlob); + this.retrieveCanvasLimits(); } componentDidUpdate(prevProps) { @@ -114,6 +119,23 @@ class PDFView extends Component { ref.tabIndex = -1; } + /** + * Calculate the devicePixelRatio the page should be rendered with + * Each platform has a different default devicePixelRatio and different canvas limits, we need to verify that + * with the default devicePixelRatio it will be able to diplay the pdf correctly, if not we must change the devicePixelRatio. + * @param {Number} width of the page + * @param {Number} height of the page + * @returns {Number} devicePixelRatio for this page on this platform + */ + getDevicePixelRatio(width, height) { + const nbPixels = width * height; + const ratioHeight = this.props.maxCanvasHeight / height; + const ratioWidth = this.props.maxCanvasWidth / width; + const ratioArea = Math.sqrt(this.props.maxCanvasArea / nbPixels); + const ratio = Math.min(ratioHeight, ratioArea, ratioWidth); + return ratio > window.devicePixelRatio ? undefined : ratio; + } + /** * 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. @@ -194,6 +216,23 @@ class PDFView extends Component { this.props.onToggleKeyboard(isKeyboardOpen); } + /** + * Verify that the canvas limits have been calculated already, if not calculate them and put them in Onyx + */ + retrieveCanvasLimits() { + if (!this.props.maxCanvasArea) { + CanvasSize.retrieveMaxCanvasArea(); + } + + if (!this.props.maxCanvasHeight) { + CanvasSize.retrieveMaxCanvasHeight(); + } + + if (!this.props.maxCanvasWidth) { + CanvasSize.retrieveMaxCanvasWidth(); + } + } + /** * Render a specific page based on its index. * The method includes a wrapper to apply virtualized styles. @@ -204,6 +243,8 @@ class PDFView extends Component { */ renderPage({index, style}) { const pageWidth = this.calculatePageWidth(); + const pageHeight = this.calculatePageHeight(index); + const devicePixelRatio = this.getDevicePixelRatio(pageWidth, pageHeight); return ( @@ -214,6 +255,7 @@ class PDFView extends Component { // 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} /> ); @@ -299,4 +341,18 @@ class PDFView extends Component { PDFView.propTypes = pdfViewPropTypes.propTypes; PDFView.defaultProps = pdfViewPropTypes.defaultProps; -export default compose(withLocalize, withWindowDimensions)(PDFView); +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + maxCanvasArea: { + key: ONYXKEYS.MAX_CANVAS_AREA, + }, + maxCanvasHeight: { + key: ONYXKEYS.MAX_CANVAS_HEIGHT, + }, + maxCanvasWidth: { + key: ONYXKEYS.MAX_CANVAS_WIDTH, + }, + }), +)(PDFView); diff --git a/src/libs/actions/CanvasSize.js b/src/libs/actions/CanvasSize.js new file mode 100644 index 000000000000..ed83562a3e43 --- /dev/null +++ b/src/libs/actions/CanvasSize.js @@ -0,0 +1,43 @@ +import Onyx from 'react-native-onyx'; +import canvasSize from 'canvas-size'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * Calculate the max area of canvas on this specific platform and save it in onyx + */ +function retrieveMaxCanvasArea() { + canvasSize.maxArea({ + onSuccess: (width, height) => { + Onyx.merge(ONYXKEYS.MAX_CANVAS_AREA, width * height); + }, + }); +} + +/** + * Calculate the max height of canvas on this specific platform and save it in onyx + */ +function retrieveMaxCanvasHeight() { + canvasSize.maxHeight({ + onSuccess: (width, height) => { + Onyx.merge(ONYXKEYS.MAX_CANVAS_HEIGHT, height); + }, + }); +} + +/** + * Calculate the max width of canvas on this specific platform and save it in onyx + */ +function retrieveMaxCanvasWidth() { + canvasSize.maxWidth({ + onSuccess: (width) => { + Onyx.merge(ONYXKEYS.MAX_CANVAS_WIDTH, width); + }, + }); +} + +export { + // eslint-disable-next-line import/prefer-default-export + retrieveMaxCanvasArea, + retrieveMaxCanvasHeight, + retrieveMaxCanvasWidth, +};