aboutsummaryrefslogtreecommitdiff
path: root/src/server/ApiManagers/PDFManager.ts
blob: e028d628d0e00cd59795d6b3ca3a0ad76537d7e0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import ApiManager, { Registration } from "./ApiManager";
import { Method } from "../RouteManager";
import RouteSubscriber from "../RouteSubscriber";
import { existsSync, createReadStream, createWriteStream } from "fs";
const pdfjs = require('pdfjs-dist/es5/build/pdf.js');
import * as Pdfjs from 'pdfjs-dist';
import { createCanvas } from "canvas";
const imageSize = require("probe-image-size");
import * as express from "express";
import * as path from "path";
import { Directory, serverPathToFile, clientPathToFile, pathToDirectory } from "./UploadManager";
import { red } from "colors";
import { resolve } from "path";

export default class PDFManager extends ApiManager {

    protected initialize(register: Registration): void {

        register({
            method: Method.POST,
            subscription: new RouteSubscriber("thumbnail"),
            secureHandler: async ({ req, res }) => {
                const { coreFilename, pageNum, subtree } = req.body;
                return getOrCreateThumbnail(coreFilename, pageNum, res, subtree);
            }
        });

    }

}

async function getOrCreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string): Promise<void> {
    const resolved = `${coreFilename}-${pageNum}.png`;
    return new Promise<void>(async resolve => {
        const path = serverPathToFile(Directory.pdf_thumbnails, resolved);
        if (existsSync(path)) {
            const existingThumbnail = createReadStream(path);
            const { err, viewport } = await new Promise<any>(resolve => {
                imageSize(existingThumbnail, (err: any, viewport: any) => resolve({ err, viewport }));
            });
            if (err) {
                console.log(red(`In PDF thumbnail response, unable to determine dimensions of ${resolved}:`));
                console.log(err);
                return;
            }
            dispatchThumbnail(res, viewport, resolved);
        } else {
            await CreateThumbnail(coreFilename, pageNum, res, subtree);
        }
        resolve();
    });
}

async function CreateThumbnail(coreFilename: string, pageNum: number, res: express.Response, subtree?: string) {
    const part1 = subtree ?? "";
    const filename = `${part1}${coreFilename}.pdf`;
    const sourcePath = resolve(pathToDirectory(Directory.pdfs), filename);
    const documentProxy = await Pdfjs.getDocument(sourcePath).promise;
    const factory = new NodeCanvasFactory();
    const page = await documentProxy.getPage(pageNum);
    const viewport = page.getViewport({ scale: 1, rotation: 0, dontFlip: false });
    const { canvas, context } = factory.create(viewport.width, viewport.height);
    const renderContext = {
        canvasContext: context,
        canvasFactory: factory,
        viewport
    };
    await page.render(renderContext).promise;
    const pngStream = canvas.createPNGStream();
    const resolved = `${coreFilename}-${pageNum}.png`;
    const pngFile = serverPathToFile(Directory.pdf_thumbnails, resolved);
    const out = createWriteStream(pngFile);
    pngStream.pipe(out);
    return new Promise<void>((resolve, reject) => {
        out.on("finish", () => {
            dispatchThumbnail(res, viewport, resolved);
            resolve();
        });
        out.on("error", error => {
            console.log(red(`In PDF thumbnail creation, encountered the following error when piping ${pngFile}:`));
            console.log(error);
            reject();
        });
    });
}

function dispatchThumbnail(res: express.Response, { width, height }: Pdfjs.PDFPageViewport, thumbnailName: string) {
    res.send({
        path: clientPathToFile(Directory.pdf_thumbnails, thumbnailName),
        width,
        height
    });
}

class NodeCanvasFactory {

    create = (width: number, height: number) => {
        const canvas = createCanvas(width, height);
        const context = canvas.getContext('2d');
        return {
            canvas,
            context
        };
    }

    reset = (canvasAndContext: any, width: number, height: number) => {
        canvasAndContext.canvas.width = width;
        canvasAndContext.canvas.height = height;
    }

    destroy = (canvasAndContext: any) => {
        canvasAndContext.canvas.width = 0;
        canvasAndContext.canvas.height = 0;
        canvasAndContext.canvas = null;
        canvasAndContext.context = null;
    }
}