Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: electron-vite (node) Cannot read properties of undefined (reading 'createCanvas') #19047

Closed
juansebastianl opened this issue Nov 15, 2024 Discussed in #18981 · 4 comments

Comments

@juansebastianl
Copy link

Discussed in #18981

There is a bug with the node import code in pdfjs-dist that is causing the imports to fail for canvas in node. This is observed working in node with vite/sveltekit. The source of the bug is in the code in pdfjs-dist which does dynamic imports for node. In my case it fails to define an import for canvas or path2d, and calling NodeCanvasFactory (as is done in the node example) gives the error reported in the original issue.

if (isNodeJS) {
  var packageCapability = Promise.withResolvers();
  var packageMap = null;
  const loadPackages = async () => {
    const fs = await import(/*webpackIgnore: true*/"fs"),
      http = await import(/*webpackIgnore: true*/"http"),
      https = await import(/*webpackIgnore: true*/"https"),
      url = await import(/*webpackIgnore: true*/"url");
    let canvas, path2d;
    return new Map(Object.entries({
      fs,
      http,
      https,
      url,
      canvas,
      path2d
    }));
  };
  loadPackages().then(map => {
    packageMap = map;
    packageCapability.resolve();
  }, reason => {
    warn(`loadPackages: ${reason}`);
    packageMap = new Map();
    packageCapability.resolve();
  });
}
class NodePackages {
  static get promise() {
    return packageCapability.promise;
  }
  static get(name) {
    return packageMap?.get(name);
  }
}
const node_utils_fetchData = function (url) {
  const fs = NodePackages.get("fs");
  return fs.promises.readFile(url).then(data => new Uint8Array(data));
};
class NodeFilterFactory extends BaseFilterFactory {}
class NodeCanvasFactory extends BaseCanvasFactory {
  _createCanvas(width, height) {
    const canvas = NodePackages.get("canvas");
    return canvas.createCanvas(width, height);
  }
}

Originally posted by fenicento October 30, 2024

Attach (recommended) or Link to PDF file

sample.pdf

Web browser and its version

nodejs

Operating system and its version

windows 11

PDF.js version

4.7.76

Is the bug present in the latest PDF.js version?

Yes

Is a browser extension

No

Steps to reproduce the problem

when trying to render a pdf page on canvas, i get the following error:


Error during conversion: TypeError: Cannot read properties of undefined (reading 'createCanvas')
    at NodeCanvasFactory._createCanvas (file:///C:/Users/fenic/projects/db-image-optimizer-electron/node_modules/.pnpm/[email protected][email protected]/node_modules/pdfjs-dist/legacy/build/pdf.mjs:10627:19)
    at NodeCanvasFactory.create (file:///C:/Users/fenic/projects/db-image-optimizer-electron/node_modules/.pnpm/[email protected][email protected]/node_modules/pdfjs-dist/legacy/build/pdf.mjs:5596:25)
    at convertPdfToImages (file:///C:/Users/fenic/projects/db-image-optimizer-electron/out/main/index.js:68:46)
    at async file:///C:/Users/fenic/projects/db-image-optimizer-electron/out/main/index.js:271:20
    at async WebContents.<anonymous> (node:electron/js2c/browser_init:2:83724)
Error occurred in handler for 'convert-pdf': TypeError: Cannot read properties of undefined (reading 'createCanvas')
    at NodeCanvasFactory._createCanvas (file:///C:/Users/fenic/projects/db-image-optimizer-electron/node_modules/.pnpm/[email protected][email protected]/node_modules/pdfjs-dist/legacy/build/pdf.mjs:10627:19)
    at NodeCanvasFactory.create (file:///C:/Users/fenic/projects/db-image-optimizer-electron/node_modules/.pnpm/[email protected][email protected]/node_modules/pdfjs-dist/legacy/build/pdf.mjs:5596:25)
    at convertPdfToImages (file:///C:/Users/fenic/projects/db-image-optimizer-electron/out/main/index.js:68:46)
    at async file:///C:/Users/fenic/projects/db-image-optimizer-electron/out/main/index.js:271:20
    at async WebContents.<anonymous> (node:electron/js2c/browser_init:2:83724)

What is the expected behavior?

it should correctly render the page and save it to a buffer

What went wrong?

file is correctly read, number of pages and viewport are correctly reported, but the render call fails:

const renderTask = page.render(renderContext);
await renderTask.promise;

Link to a viewer

No response

Additional context

this is the conversion function I wrote:

import sharp from 'sharp'
import path from 'path'
import fs from 'fs'

import { getDocument, GlobalWorkerOptions } from "pdfjs-dist/legacy/build/pdf.mjs";

GlobalWorkerOptions.workerSrc = 'pdfjs-dist/legacy/build/pdf.worker.mjs';

export const convertPdfToImages = async (pdfPath, destinationFolder, outputFormat = 'avif', resizeWidth) => {

    // Create output directory if it doesn't exist
    if (!fs.existsSync(destinationFolder)) {
        fs.mkdirSync(destinationFolder, { recursive: true });
    }

    // Create output directory path using PDF filename
    const pdfFilename = path.basename(pdfPath, '.pdf')
    const outputDir = path.join(destinationFolder, pdfFilename)

    try {
        // Load the PDF file
        const data = new Uint8Array(fs.readFileSync(pdfPath));
        const loadingTask = getDocument({ data });
        const pdf = await loadingTask.promise;
        
        console.log(`PDF loaded. Number of pages: ${pdf.numPages}`);

        // Process each page
        for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {
            const page = await pdf.getPage(pageNumber);
            
            const canvasFactory = pdf.canvasFactory;
            const viewport = page.getViewport({ scale: 1.0 });
            const canvasAndContext = canvasFactory.create(
                viewport.width,
                viewport.height
            );
            const renderContext = {
                canvasContext: canvasAndContext.context,
                viewport,
            };

            const renderTask = page.render(renderContext);
            await renderTask.promise; 
            // Convert the canvas to an image buffer.
            const buffer = canvasAndContext.canvas.toBuffer();

            const outputPath = path.join(outputDir, `page-${String(pageNumber).padStart(3, '0')}.${outputFormat}`);

            // Process with Sharp and save
            let sharpInstance = sharp(buffer);

            // Resize if width specified
            if (resizeWidth) {
                sharpInstance = sharpInstance.resize({ 
                    width: resizeWidth,
                    withoutEnlargement: true,
                    fit: 'inside'
                });
            }

            await sharpInstance
                .toFormat(outputFormat)
                .toFile(outputPath);

            // Clean up canvas resources
            canvasFactory.destroy(canvasAndContext);

            console.log(`Page ${pageNumber} saved as ${outputPath}`);
        }

        console.log('Conversion completed successfully!');
        return destinationFolder;
    } catch (error) {
        console.error('Error during conversion:', error);
        throw error;
    }
}

this is the package.json:

{
  "name": "db-image-optimizer-electron",
  "version": "1.0.0",
  "description": "An Electron application with Svelte",
  "main": "./out/main/index.js",
  "type": "module",
  "author": "example.com",
  "homepage": "https://electron-vite.org",
  "scripts": {
    "format": "prettier --plugin prettier-plugin-svelte --write .",
    "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "start": "electron-vite preview",
    "dev": "electron-vite dev",
    "build": "electron-vite build",
    "postinstall": "electron-builder install-app-deps",
    "build:unpack": "npm run build && electron-builder --dir",
    "build:win": "npm run build && electron-builder --win",
    "build:mac": "npm run build && electron-builder --mac",
    "build:linux": "npm run build && electron-builder --linux"
  },
  "dependencies": {
    "@electron-toolkit/preload": "^3.0.1",
    "@electron-toolkit/utils": "^3.0.0",
    "bootstrap": "^5.3.3",
    "bootstrap-icons": "^1.11.3",
    "idb-keyval": "^6.2.1",
    "pdfjs-dist": "^4.7.76",
    "sass": "^1.80.4",
    "sharp": "^0.33.5",
    "svelte-spa-router": "^4.0.1",
    "tinybase": "^5.3.5"
  },
  "devDependencies": {
    "@electron-toolkit/eslint-config": "^1.0.2",
    "@electron-toolkit/eslint-config-prettier": "^2.0.0",
    "@electron/rebuild": "^3.7.0",
    "@sveltejs/vite-plugin-svelte": "^3.1.1",
    "electron": "^31.0.2",
    "electron-builder": "^24.13.3",
    "electron-vite": "^2.3.0",
    "eslint": "^8.57.0",
    "eslint-plugin-svelte": "^2.41.0",
    "prettier": "^3.3.2",
    "prettier-plugin-svelte": "^3.2.5",
    "svelte": "^5.1.2",
    "vite": "^5.3.1"
  }
}
@Snuffleupagus
Copy link
Collaborator

Snuffleupagus commented Nov 15, 2024

  • First of all, please note that we don't (and never have) officially supported Electron since it's not really feasibly for the few regular (unpaid) contributors to provide support for the plethora of frameworks that exist.
  • Secondly, the code you're quoting above no longer exists in the latest version of that file; see https://github.com/mozilla/pdf.js/blob/master/src/display/node_utils.js
  • Finally, please don't knowingly open duplicate issues.

@juansebastianl
Copy link
Author

Thanks for your PR rewriting this part of the code with the newer dependency, I hope it solves the issue. If it doesn't I'm happy to make a PR if y'all welcome them, although I'm new to the codebase.

This isn't about electron, which is why I opened the new issue. It's about sveltekit running a normal web app, although the error points to an issue that likely spans nextjs or any SSR node based framework where you're doing server side calls to the canvas factory.

@danwritecode
Copy link

danwritecode commented Nov 16, 2024

I'm confused why this is being closed and why the other issue is being moved to a discussion. There is an example here: https://github.com/mozilla/pdf.js/blob/master/examples/node/pdf2png/pdf2png.mjs that reproduces this exact error on an express server.

Edit, looks like the issue was fixed here: #19015

Is there a timeline for the next release?

@juansebastianl
Copy link
Author

juansebastianl commented Nov 16, 2024

Agree, if it's helpful we ended up downgrading to 4.2.67 (the oldest version without a severe vulnerability) and using the slightly older API to do what we needed. We will probably upgrade on next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants