[Electron][TypeScript][PDF.js] Create images from PDF
Tags: typescript, electron, pdfjs
Published: 2021-04-14 18:36:06

## Intro
* [[TypeScript] Try Electron](https://dev.to/masanori_msl/typescript-try-electron-43a0)

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
```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'
    }
};
```

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'"
```

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

#### index.html
```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>
...
```

#### main.page.js
```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?");

/***/ })

/******/ 	});
...
```

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

#### webpack.config.js
```js
var path = require('path');

module.exports = {
    mode: 'production',
    entry: {
        'main.page': './clients/main.page.ts',
    },
...
```

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

* [PDF.js](https://mozilla.github.io/pdf.js/)

First, I drew the first page into a canvas.

#### main.ts
```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));
}
...
```
#### index.html
```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>
```
#### main.page.ts
```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);    
}
```
#### Result
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/np627f63wzl7nadt0hnq.png)
* [pdf.js/pdf2png.js at master · mozilla/pdf.js · GitHub](https://github.com/mozilla/pdf.js/blob/master/examples/node/pdf2png/pdf2png.js)
* [JavaScriptでPDFファイルの各ページを画像にする | 山本隆の開発日誌](https://www.gesource.jp/weblog/?p=8409)

### 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
```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);
    }
}
```
#### main.page.ts
```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);   
}
```
* [Examples](https://mozilla.github.io/pdf.js/examples/)
* [javascript - How to determine natural page size of a PDF using PDF.js - Stack Overflow](https://stackoverflow.com/questions/56429896/how-to-determine-natural-page-size-of-a-pdf-using-pdf-js)

## 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
```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);
}
```
#### main.ts
```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");
    }
  });
});
```
* [HTMLCanvasElement.toBlob() - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob)
* [javascript - Write file to disk from blob in electron application - Stack Overflow](https://stackoverflow.com/questions/43562192/write-file-to-disk-from-blob-in-electron-application)