DEV Community

Masui Masanori
Masui Masanori

Posted on

[Electron][TypeScript][PDF.js] Create images from PDF

Intro

This time, I will try printing PDF files with the Electron application.
Before printing, I want to get images from the PDF file.

Environments

  • electron ver.12.0.2
  • typescript ver.4.2.4
  • webpack ver.5.31.2
  • webpack-cli ver.4.6.0
  • pdfjs-dist ver.2.7.570

Use Webpack in client-side

Because I want to use Webpack in client-side as same as when I create ASP.NET Core applications, I add Webpack and bundle the client-side TypeScript code.

webpack.config.js

var path = require('path'); module.exports = { mode: 'development', entry: { 'main.page': './clients/main.page.ts', }, module: { rules: [ { test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ } ] }, resolve: { extensions: [ '.tsx', '.ts', '.js' ] }, output: { filename: '[name].js', path: path.resolve(__dirname, 'js/clients'), library: 'Page', libraryTarget: 'umd' } }; 
Enter fullscreen mode Exit fullscreen mode

And I got a script error.

Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self'" 
Enter fullscreen mode Exit fullscreen mode

This error came from "Content-Security-Policy" for electron and bundled script for development use "eval()".

index.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> </head> ... 
Enter fullscreen mode Exit fullscreen mode

main.page.js

/* * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). * This devtool is neither made for production nor for readable output files. * It uses "eval()" calls to create a separate source file in the browser devtools. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) * or disable the default devtool with "devtool: false". * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). */ ... /***/ ((__unused_webpack_module, exports) => { eval("\r\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\r\nexports.greet = void 0;\r\nfunction greet(message) {\r\n alert(message);\r\n return 'OK';\r\n}\r\nexports.greet = greet;\r\n\n\n//# sourceURL=webpack://Page/./clients/main.page.ts?"); /***/ }) /******/ }); ... 
Enter fullscreen mode Exit fullscreen mode

I think the easiest way to resolve is change mode of Webpack to "production".

webpack.config.js

var path = require('path'); module.exports = { mode: 'production', entry: { 'main.page': './clients/main.page.ts', }, ... 
Enter fullscreen mode Exit fullscreen mode

Get images from PDF files

To get images from PDF files, I will use PDF.js.

First, I drew the first page into a canvas.

main.ts

... async function createWindow () { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js') } }); win.loadFile('./views/index.html'); const filePath = `C:/Users/example/sample600.pdf`; win.webContents.executeJavaScript(`Page.load('${filePath}')`) .catch(error => console.error(error)); } ... 
Enter fullscreen mode Exit fullscreen mode

index.html

<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello World!</title> <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> </head> <body style="background: white;"> <div> <div id="area"></div> <button id="save_button">Click</button> <div id="result"></div> </div> <script src="../js/clients/main.page.js"></script> </body> </html> 
Enter fullscreen mode Exit fullscreen mode

main.page.ts

import * as pdf from 'pdfjs-dist'; let canvas: HTMLCanvasElement; export async function load(filePath: string) { const rootElement = document.getElementById('area') as HTMLElement; pdf.GlobalWorkerOptions.workerSrc = '../node_modules/pdfjs-dist/build/pdf.worker.js'; const pdfDocument = await pdf.getDocument(filePath).promise; // Request a first page const pdfPage = await pdfDocument.getPage(1); // Display page on the existing canvas with 100% scale. const viewport = pdfPage.getViewport({ scale: 1.0, rotation: 0 }); canvas = document.createElement('canvas'); canvas.id = 'sample_page'; canvas.width = viewport.width; canvas.height = viewport.height; const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; await pdfPage.render({ canvasContext: ctx, viewport }).promise; rootElement.appendChild(canvas); } 
Enter fullscreen mode Exit fullscreen mode

Result

Alt Text

Resolution

Now I have a problem.
The created image size was { width:1190, height: 841 }.
Its DPI was 72.

How can I change to high resolution?

According to the document, I should change the scale of ViewPort.
Because I don't know the natural page size of PDF files, so I convert from pixel size of 72DPI to millimeter, after that I convert to pixel size of 300DPI.

sizeConverter.ts

export class SizeConverter { public static ConvertFromMmToPx(sizeMm: number, dpi: number): number { if(sizeMm <= 0 || dpi <= 0) { return 0; } const sizeInch = sizeMm / 25.4; return Math.round(sizeInch * dpi); } public static ConvertFromPxToMm(sizePx: number, dpi: number): number { if(sizePx <= 0 || dpi <= 0) { return 0; } const sizeInch = sizePx / dpi; return Math.round(sizeInch * 25.4); } } 
Enter fullscreen mode Exit fullscreen mode

main.page.ts

import { SizeConverter } from './sizeConverter'; ... export async function load(filePath: string) { ... const pdfDocument = await pdf.getDocument(filePath).promise; // Request a first page const pdfPage = await pdfDocument.getPage(1); // Display page on the existing canvas with 100% scale. const viewport = pdfPage.getViewport({ scale: 1.0, rotation: 0 }); canvas = document.createElement('canvas'); canvas.id = 'sample_page'; const horizontalMm = SizeConverter.ConvertFromPxToMm(viewport.width, 72); const verticalMm = SizeConverter.ConvertFromPxToMm(viewport.height, 72); const actualWidth = SizeConverter.ConvertFromMmToPx(horizontalMm, 300); const actualHeight = SizeConverter.ConvertFromMmToPx(verticalMm, 300); canvas.width = actualWidth; canvas.height = actualHeight; canvas.style.width = `${viewport.width}px`; canvas.style.height = `${viewport.height}px`; const scale = Math.min(actualWidth / viewport.width, actualHeight / viewport.height); const ctx = canvas.getContext("2d") as CanvasRenderingContext2D; await pdfPage.render({ canvasContext: ctx, viewport: pdfPage.getViewport({ scale: scale, rotation: 0 }), }).promise; rootElement.appendChild(canvas); } 
Enter fullscreen mode Exit fullscreen mode

Save images

To save file silently, I send Blob data to electron-side.
Because I can't use "Blob" in electron-side, so I have to convert Blob to Buffer before sending to electron-side.

preload.ts

import { ipcRenderer } from 'electron'; window.addEventListener('DOMContentLoaded', () => { const button = document.getElementById('save_button'); if(button != null) { button.addEventListener('click', (ev) => { saveFile(); }); } }); function saveFile() { const canvas = document.getElementById('sample_page') as HTMLCanvasElement; canvas.toBlob((blob) => { const fileReader = new FileReader(); fileReader.onload = async (ev) => { const buffer = Buffer.from(fileReader.result as ArrayBuffer); ipcRenderer.send('save_file', "sample.png", buffer); }; fileReader.readAsArrayBuffer(blob!); }, 'image/png', 1); } 
Enter fullscreen mode Exit fullscreen mode

main.ts

... import * as fs from 'fs'; ... ipcMain.on('save_file', (_, fileName, buffer) => { // get ArrayBuffer fs.writeFile(fileName, buffer, error => { if(error) { console.error(error); } else { console.log("OK"); } }); }); 
Enter fullscreen mode Exit fullscreen mode

Top comments (0)