react-pdf icon indicating copy to clipboard operation
react-pdf copied to clipboard

Total canvas memory use exceeds the maximum limit (384 MB)

Open walpolsh opened this issue 2 years ago • 2 comments

Before you start - checklist

  • [X] I followed instructions in documentation written for my React-PDF version
  • [X] I have checked if this bug is not already reported
  • [X] I have checked if an issue is not listed in Known issues
  • [X] If I have a problem with PDF rendering, I checked if my PDF renders properly in PDF.js demo

Description

Loading a 15mb pdf on iOS triggers this error ^ over and over, locking up the whole application for a minute or two. Scrolling on the PDF seems to trigger it? It also might be zooming in and out. Hard to tell at this point.

Steps to reproduce

Here's how I'm using react-pdf in a component:

import { IconButton, Toolbar } from "@mui/material";
import React, { useState } from "react";
import { pdfjs, Document, Page } from "react-pdf";
import { Add, Remove, GetApp } from "@mui/icons-material/";
import styled from "styled-components";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.min.js`;
const PDFDocument = styled(Document)`
  overflow: auto;
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
`;
const PDFPage = styled(Page)`
  margin: 0.5em;
`;
export function PDFViewer({ file }) {
  const [numPages, setNumPages] = useState(null);
  const [scale, setScale] = useState(1);
  function onDocumentLoadSuccess({ numPages }) {
    setNumPages(numPages);
  }

  return (
    <div style={{ height: "100%", width: "100%" }}>
      <Toolbar
        id="pdfToolbar"
        style={{
          justifyContent: "center",
          backgroundColor: "rgb(51 51 51)",
          color: "white",
        }}
      >
        <IconButton
          style={{ color: "white" }}
          disabled={scale === 0.1}
          onClick={() => setScale(parseFloat((scale - 0.1).toFixed(1)))}
        >
          <Remove />
        </IconButton>
        <span>{Math.round(scale * 100)}%</span>
        <IconButton
          style={{ color: "white" }}
          disabled={scale === 10}
          onClick={() => setScale(parseFloat((scale + 0.1).toFixed(1)))}
        >
          <Add />
        </IconButton>
        <IconButton
          component={"a"}
          href={file}
          target="_blank"
          style={{ color: "white" }}
          onClick={(e) => e.stopPropagation()}
        >
          <GetApp />
        </IconButton>
      </Toolbar>
      <div
        style={{
          backgroundColor: "rgb(82, 86, 89)",
          height: `calc(100% - 60px)`,
          width: "100%",
        }}
      >
        <PDFDocument file={file} onLoadSuccess={onDocumentLoadSuccess}>
          {Array.from(new Array(numPages), (el, index) => (
            <PDFPage key={`page_${index + 1}`} pageNumber={index + 1} scale={scale} />
          ))}
        </PDFDocument>
      </div>
    </div>
  );
}

^ Maybe I'm implementing this wrong or inefficiently?

Expected behavior

Should just work without locking up I suppose?

Actual behavior

Here's a screenshot of the error:

image

Additional information

No response

Environment

  • Device: iPhone 11 Pro and iPad 7th gen
  • OS: iOS 15.4.1
  • Browser: iOS Safari
  • React-pdf version: 5.2.0 and bumping to 5.7.2 didn't help it

walpolsh avatar Jun 28 '22 15:06 walpolsh

We are experiencing the same issue with iOS users. It seems Safari holds on to canvases for a while. In our testing, the document usually loaded initially but if we tried to refresh the page a couple times it would fail. The scenario being that when the document is big, a person might get impatient waiting for it to download/render and then try to refresh several times to get it to load. We figure this then creates multiple canvases which causes the browser to exceed the memory limit. If that is the case, a solution could be to clear the canvas on an unload event (like on refresh or navigating away from the page or closing the tab etc).

bng41986 avatar Jun 28 '22 22:06 bng41986

Probably the canvas exceeds the size allowed by Safari and retina displays. Checkout this links for more info: https://github.com/jhildenbiddle/canvas-size https://stackoverflow.com/questions/6081483/maximum-size-of-a-canvas-element/53677532#53677532

obeces avatar Jul 28 '22 08:07 obeces

This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this issue will be closed in 14 days.

github-actions[bot] avatar Oct 31 '22 00:10 github-actions[bot]

This issue was closed because it has been stalled for 14 days with no activity.

github-actions[bot] avatar Nov 21 '22 00:11 github-actions[bot]

In 45a19b6f0871cfd1fd48d5155532a5e54515467a, I've added a way to override devicePixelRatio canvas is rendering with. By default, it matches screen device pixel ratio, so for example 3 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, and thus also lowering memory usage.

For example, sample document on an iPhone renders 4,508,910 pixels, setting devicePixelRatio to 2 more than halves it to 2,003,960 pixels, and lowering it all the way to 1, 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!

wojtekmaj avatar Nov 22 '22 11:11 wojtekmaj

@wojtekmaj Hi, I've tried adding the devicePixelRatio and fixing it at 1, 2 and using the Math.min formula but it's still freezing on large PDFs. There's no more error message but i think it locks up before having a chance to throw something.

walpolsh avatar Dec 13 '22 18:12 walpolsh

@wojtekmaj I think I've fixed it by using your suggestion to add react-window for large PDFs. Thanks!

walpolsh avatar Dec 16 '22 14:12 walpolsh