[Electron][TypeScript][PDF.js] Create images from PDF
Masui Masanori
Posted on April 14, 2021
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'
}
};
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
<!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
/*
* 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
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.
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));
}
...
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>
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);
}
Result
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);
}
}
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);
}
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);
}
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");
}
});
});
Posted on April 14, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.