"Canvas area exceeds the maximum limit (width * height > 16777216)." on iOS
See original GitHub issueBefore you start - checklist
- I followed instructions in documentation written for my React-PDF version
- I have checked if this bug is not already reported
- I have checked if an issue is not listed in Known issues
- If I have a problem with PDF rendering, I checked if my PDF renders properly in PDF.js demo
Description
On iOS mobile + Safari (bug has been reported on iPhone 12, 13, 14), it can happen that the PDF does not display at all. We tracked down the issue and here is the reason:
Safari simply cannot draw large canvas elements. The limit is set at 16.777.216 pixels. Create a canvas with more pixels and iOS Safari will tell us it exceeds the memory limit. When we open this page on iOS Safari, it’ll show the following warning in the developer console.
More information about the issue: https://pqina.nl/blog/canvas-area-exceeds-the-maximum-limit/
Steps to reproduce
Here is some dummy code to produce the error:
const canvas = document.createElement('canvas');
canvas.width = 4097;
canvas.height = 4096;
const ctx = canvas.getContext('2d');
ctx.fillRect(0, 0, 100, 100);
Here is our actual code:
import React, { useState } from "react";
import PropTypes from "prop-types";
import { Document, Page } from "react-pdf/dist/esm/entry.webpack";
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
const PDFViewer = ({ file, renderRightNav, renderLeftNav }) => {
const [pages, setPages] = useState(null);
const [pageNumber, setPageNumber] = useState(1);
const onDocumentLoadSuccess = ({ numPages }) => {
setPages(numPages);
};
const handleNext = () => {
setPageNumber(pageNumber + 1);
};
const handlePrev = () => {
setPageNumber(pageNumber - 1);
};
return (
<div className="pdf-viewer">
<Document file={file} onLoadSuccess={onDocumentLoadSuccess}>
<Page pageNumber={pageNumber} />
</Document>
<p className="page-number">Page {pageNumber} of {pages}</p>
{renderRightNav(handleNext, pageNumber === pages)}
{renderLeftNav(handlePrev, pageNumber === 1)}
</div>
);
};
PDFViewer.propTypes = {
file : PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
renderRightNav : PropTypes.func,
renderLeftNav : PropTypes.func
};
export default PDFViewer;
Expected behavior
The PDF should display fine. Like this:
Actual behavior
PDF doesn’t display and we’re getting the following error in the console:
Attaching the incriminated PDF that doesn’t display on Safari / iOS: Trashless Business Plan 10-28-22.pdf
Additional information
No response
Environment
- Browser (if applicable): Safari on iOS
- React-PDF version: v6
- React version: 18
- Webpack version (if applicable):
Issue Analytics
- State:
- Created 10 months ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
In 45a19b6f0871cfd1fd48d5155532a5e54515467a, I’ve added a way to override
devicePixelRatio
canvas is rendering with. By default, it matches screen device pixel ratio, so for example3
on most iPhones,1
on standard resolution office monitor.By setting this value manually we can lower the resolution of the canvas rendered, vastly reducing the number of pixels rendered, without changing actual physical canvas size.
For example, sample document on an iPhone renders 4,508,910 pixels, setting
devicePixelRatio
to2
more than halves it to 2,003,960 pixels, and lowering it all the way to1
, while quite making things quite blurry, renders only 500,990 pixels.Bonus tip: you could cap
devicePixelRatio
by passing e.g.Math.min(2, window.devicePixelRatio)
, preventing obscenely large pixel density, while maintaining good looks on most devices.This might help you out!
There’s an NPM package that people can use to detect the maximum canvas size supported by the web browser:
Users of
react-pdf
may be able to use that package to influence the size-related props they pass to the<Page>
element, thereby influencing the size of the underlying<canvas>
element.I have never used it, myself.
In my case, I was running into issues on iOS when trying to render a PDF using this element:
As a temporary workaround, I am using the following “smaller” element, which works on iOS:
I plan to employ the above package in the future, but haven’t gotten around to it yet (due to some higher priorities).
Edit: Changed
Document
toPage
(was a typo in original message).